Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WBP-9499 Create Performance Test Suite #4335

Draft
wants to merge 16 commits into
base: develop
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -125,14 +125,22 @@ ci-safe:
ci:
@echo -en "\n\n\nplease choose between goals ci-fast and ci-safe.\n\n\n"

.PHONY: perf-test
perf-test:
make c package=all
./dist/run-services --mode performance -- ./dist/integration --suite performance

# Compile and run services
# Usage: make crun `OR` make crun package=galley
.PHONY: cr
cr: c db-migrate
./dist/run-services

crm: c db-migrate
./dist/run-services -m
./dist/run-services --mode manual

crp: c db-migrate
./dist/run-services --mode performance

# Run integration from new test suite
# Usage: make devtest
Expand Down
20 changes: 13 additions & 7 deletions integration/Setup.hs
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,9 @@ import System.Directory
import System.FilePath
import Prelude

collectTests :: FilePath -> [FilePath] -> IO [(String, String, String, String)]
collectTests pkgRoot roots =
concat <$> traverse (findAllTests . (<> "/Test")) roots
collectTests :: FilePath -> FilePath -> [FilePath] -> IO [(String, String, String, String)]
collectTests pkgRoot topModule roots =
concat <$> traverse (findAllTests . (</> topModule)) roots
where
findAllTests :: FilePath -> IO [(String, String, String, String)]
findAllTests root = do
Expand All @@ -47,7 +47,7 @@ collectTests pkgRoot roots =

findModuleTests :: FilePath -> FilePath -> IO [(String, String, String, String)]
findModuleTests root path = do
let modl = "Test." <> toModule root path
let modl = topModule <> "." <> toModule root path
tests <- collectTestsInModule pkgRoot path
pure $ map (\(testName, summary, full) -> (modl, testName, summary, full)) tests

Expand Down Expand Up @@ -182,8 +182,11 @@ testHooks hooks =
for_ (Map.lookup cname (componentNameMap l)) $ \compBIs -> do
for_ compBIs $ \compBI -> do
let dest = autogenComponentModulesDir l compBI </> "RunAllTests.hs"
tests <- collectTests (dataDir p) roots
let modules = Set.toList (Set.fromList (map (\(m, _, _, _) -> m) tests))
tests <- collectTests (dataDir p) "Test" roots
perfTests <- collectTests (dataDir p) "Performance" roots
let modules = Set.toList (Set.fromList (map (\(m, _, _, _) -> m) (tests <> perfTests)))
mkYieldTests testList =
unlines (map (\(m, n, s, f) -> " yieldTests " <> unwords [show m, show n, show s, show f, m <> "." <> n]) testList)
createDirectoryIfMissing True (takeDirectory dest)
writeFile
dest
Expand All @@ -195,7 +198,10 @@ testHooks hooks =
unlines (map ("import qualified " <>) modules),
"mkAllTests :: IO [Test]",
"mkAllTests = execWriterT $ do",
unlines (map (\(m, n, s, f) -> " yieldTests " <> unwords [show m, show n, show s, show f, m <> "." <> n]) tests)
mkYieldTests tests,
"mkAllPerfTests :: IO [Test]",
"mkAllPerfTests = execWriterT $ do",
mkYieldTests perfTests
]
)
pure ()
Expand Down
2 changes: 2 additions & 0 deletions integration/integration.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ library
API.Spar
MLS.Util
Notifications
Performance.BigConversation
RunAllTests
SetupHelpers
Test.AccessUpdate
Expand Down Expand Up @@ -178,6 +179,7 @@ library
Testlib.App
Testlib.Assertions
Testlib.Cannon
Testlib.Cannon.ConsumableNotifications
Testlib.Certs
Testlib.Env
Testlib.HTTP
Expand Down
202 changes: 164 additions & 38 deletions integration/test/MLS/Util.hs
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,12 @@ import System.IO.Temp
import System.Posix.Files
import System.Process
import Testlib.Assertions
import qualified Testlib.Cannon.ConsumableNotifications as CN
import Testlib.HTTP
import Testlib.JSON
import Testlib.Prelude
import Testlib.Printing
import Data.Time (getCurrentTime, diffUTCTime)

mkClientIdentity :: (MakesValue u, MakesValue c) => u -> c -> App ClientIdentity
mkClientIdentity u c = do
Expand Down Expand Up @@ -570,8 +572,8 @@ createExternalCommit convId cid mgi = do
data MLSNotificationTag = MLSNotificationMessageTag | MLSNotificationWelcomeTag
deriving (Show, Eq, Ord)

consumingMessages :: (HasCallStack) => MLSProtocol -> MessagePackage -> Codensity App ()
consumingMessages mlsProtocol mp = Codensity $ \k -> do
consumingMassagesViaNewCapability :: (HasCallStack) => MLSProtocol -> MessagePackage -> Codensity App ()
consumingMassagesViaNewCapability mlsProtocol mp = Codensity $ \k -> do
conv <- getMLSConv mp.convId
-- clients that should receive the message itself
let oldClients = Set.delete mp.sender conv.members
Expand All @@ -588,30 +590,104 @@ consumingMessages mlsProtocol mp = Codensity $ \k -> do
Set.difference
(Set.map (.user) newClients)
(Set.map (.user) oldClients)
withWebSockets (map fst clients) $ \wss -> do

let uidsWithClients =
fmap
((\c -> (c.user, (object ["domain" .= c.domain, "id" .= c.user], c.client))) . fst)
clients

CN.withEventsWebSockets (fmap snd uidsWithClients) $ \chans -> do
r <- k ()

-- if the conversation is actually MLS (and not mixed), pick one client for
-- each new user and wait for its join event. In Mixed protocol, the user is
-- already in the conversation so they do not get a member-join
-- notification.
when (mlsProtocol == MLSProtocolMLS) $
when (mlsProtocol == MLSProtocolMLS) $ do
let uidsWithChannels = zip uidsWithClients chans
let newUserChans = uidsWithChannels & filter (\((uid, _), _) -> Set.member uid newUsers) & fmap snd
let assertJoin e = do
eventType <- e %. "data.event.payload.0.type" & asString
pure $ eventType == "conversation.member-join"

traverse_
(awaitMatch (\n -> isMemberJoinNotif n))
( flip Map.restrictKeys newUsers
. Map.mapKeys ((.user) . fst)
. Map.fromList
. toList
$ zip clients wss
( \(eventChan, ackChan) ->
CN.awaitMatch assertJoin eventChan
>>= CN.ackEvent ackChan
)
newUserChans

-- at this point we know that every new user has been added to the
-- conversation
for_ (zip clients wss) $ \((cid, t), ws) -> case t of
MLSNotificationMessageTag -> void $ consumeMessageNoExternal conv.ciphersuite cid mp ws
MLSNotificationWelcomeTag -> consumeWelcome cid mp ws
for_ (zip clients chans) $ \((cid, t), (eventChan, ackChan)) -> case t of
MLSNotificationMessageTag -> do
event <-
CN.awaitMatch
( \e -> do
eventType <- e %. "data.event.payload.0.type" & asString
pure $ eventType == "conversation.mls-message-add"
)
eventChan
CN.ackEvent ackChan event
eventData <- event %. "data.event.payload.0.data" & asByteString
void $ mlsCliConsume mp.convId conv.ciphersuite cid eventData
MLSNotificationWelcomeTag -> do
event <-
CN.awaitMatch
( \e -> do
eventType <- e %. "data.event.payload.0.type" & asString
pure $ eventType == "conversation.mls-welcome"
)
eventChan
CN.ackEvent ackChan event
eventData <- event %. "data.event.payload.0.data" & asByteString
void $ fromWelcome mp.convId conv.ciphersuite cid eventData
pure r

consumingMessages :: (HasCallStack) => MLSProtocol -> MessagePackage -> Codensity App ()
consumingMessages mlsProtocol mp = Codensity $ \k -> do
conv <- getMLSConv mp.convId
-- clients that should receive the message itself
let oldClients = Set.delete mp.sender conv.members
-- clients that should receive a welcome message
let newClients = Set.delete mp.sender conv.newMembers
-- all clients that should receive some MLS notification, together with the
-- expected notification tag
let clients =
map (,MLSNotificationMessageTag) (toList oldClients)
<> map (,MLSNotificationWelcomeTag) (toList newClients)

let newUsers =
Set.delete mp.sender.user $
Set.difference
(Set.map (.user) newClients)
(Set.map (.user) oldClients)

withWebSockets (map fst clients) $ \wss -> do
r <- k ()

logTime ("await and consume MLS notifications for " <> show (length clients)) $ do
-- if the conversation is actually MLS (and not mixed), pick one client for
-- each new user and wait for its join event. In Mixed protocol, the user is
-- already in the conversation so they do not get a member-join
-- notification.
when (mlsProtocol == MLSProtocolMLS) $
traverse_
(awaitMatch (\n -> isMemberJoinNotif n))
( flip Map.restrictKeys newUsers
. Map.mapKeys ((.user) . fst)
. Map.fromList
. toList
$ zip clients wss
)

-- at this point we know that every new user has been added to the
-- conversation
for_ (zip clients wss) $ \((cid, t), ws) -> case t of
MLSNotificationMessageTag -> void $ consumeMessageNoExternal conv.ciphersuite cid mp ws
MLSNotificationWelcomeTag -> consumeWelcome cid mp ws
pure r

consumeMessageWithPredicate :: (HasCallStack) => (Value -> App Bool) -> ConvId -> Ciphersuite -> ClientIdentity -> Maybe MessagePackage -> WebSocket -> App Value
consumeMessageWithPredicate p convId cs cid mmp ws = do
notif <- awaitMatch p ws
Expand Down Expand Up @@ -685,32 +761,33 @@ sendAndConsumeCommitBundleWithProtocol protocol mp = do
lowerCodensity $ do
consumingMessages protocol mp
lift $ do
r <- postMLSCommitBundle mp.sender (mkBundle mp) >>= getJSON 201

-- if the sender is a new member (i.e. it's an external commit), then
-- process the welcome message directly
do
conv <- getMLSConv mp.convId
when (Set.member mp.sender conv.newMembers) $
traverse_ (fromWelcome mp.convId conv.ciphersuite mp.sender) mp.welcome

-- increment epoch and add new clients
modifyMLSState $ \mls ->
mls
{ convs =
Map.adjust
( \conv ->
conv
{ epoch = conv.epoch + 1,
members = conv.members <> conv.newMembers,
newMembers = mempty
}
)
mp.convId
mls.convs
}
r <- logTime "POST /mls/commit-bundles" $ postMLSCommitBundle mp.sender (mkBundle mp) >>= getJSON 201

logTime "modify local state" $ do
-- if the sender is a new member (i.e. it's an external commit), then
-- process the welcome message directly
do
conv <- getMLSConv mp.convId
when (Set.member mp.sender conv.newMembers) $
traverse_ (fromWelcome mp.convId conv.ciphersuite mp.sender) mp.welcome

-- increment epoch and add new clients
modifyMLSState $ \mls ->
mls
{ convs =
Map.adjust
( \conv ->
conv
{ epoch = conv.epoch + 1,
members = conv.members <> conv.newMembers,
newMembers = mempty
}
)
mp.convId
mls.convs
}

pure r
pure r

consumeWelcome :: (HasCallStack) => ClientIdentity -> MessagePackage -> WebSocket -> App ()
consumeWelcome cid mp ws = do
Expand Down Expand Up @@ -863,3 +940,52 @@ getSubConvId user convId subConvName =
getSubConversation user convId subConvName
>>= getJSON 200
>>= objConvId

-- FUTUREWORK: we assume all clients in the conversation have the new consumable-notification capability
-- to support both the legacy and the new capability,
-- we need to add it to the client identity and store it in the local MLS state
sendAndConsumeCommitBundleNew :: (HasCallStack) => MessagePackage -> App Value
sendAndConsumeCommitBundleNew = sendAndConsumeCommitBundleWithProtocolNew MLSProtocolMLS

-- | Send an MLS commit bundle, wait for clients to receive it, consume it, and
-- update the test state accordingly.
sendAndConsumeCommitBundleWithProtocolNew :: (HasCallStack) => MLSProtocol -> MessagePackage -> App Value
sendAndConsumeCommitBundleWithProtocolNew protocol mp = do
lowerCodensity $ do
consumingMassagesViaNewCapability protocol mp
lift $ do
r <- postMLSCommitBundle mp.sender (mkBundle mp) >>= getJSON 201

-- if the sender is a new member (i.e. it's an external commit), then
-- process the welcome message directly
do
conv <- getMLSConv mp.convId
when (Set.member mp.sender conv.newMembers) $
traverse_ (fromWelcome mp.convId conv.ciphersuite mp.sender) mp.welcome

-- increment epoch and add new clients
modifyMLSState $ \mls ->
mls
{ convs =
Map.adjust
( \conv ->
conv
{ epoch = conv.epoch + 1,
members = conv.members <> conv.newMembers,
newMembers = mempty
}
)
mp.convId
mls.convs
}

pure r

logTime :: (HasCallStack) => String -> App a -> App a
logTime desc action = do
start <- liftIO getCurrentTime
res <- action
end <- liftIO getCurrentTime
let diff = diffUTCTime end start
liftIO $ putStrLn $ desc <> " took " <> show diff
pure res
Loading