Skip to content

Commit 689dc02

Browse files
frasertweedaleRoman Joost
authored andcommitted
tests: use "keepalive" tmux session and unique session names
We have experienced problems with the `tmux new-session` command: a race of server availability when the server is being torn down after the previous session gets killed. Create a "keepalive" session whose lifetime is the whole of the acceptance tests, to prevent the server from being killed. Also sequentially number the test sessions to avoid any potential races when sessions are being killed and started. Fixes: #123
1 parent 8427f4e commit 689dc02

1 file changed

Lines changed: 79 additions & 51 deletions

File tree

test/TestUserAcceptance.hs

Lines changed: 79 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ module TestUserAcceptance where
88

99
import qualified Data.Text as T
1010
import System.IO.Temp (createTempDirectory, getCanonicalTemporaryDirectory)
11+
import Data.Functor (($>))
1112
import Data.Ini (parseIni, writeIniFileWith, KeySeparator(..), WriteIniSettings(..))
1213
import Data.Semigroup ((<>))
1314
import Control.Concurrent (threadDelay)
@@ -19,7 +20,7 @@ import Data.Maybe (isJust)
1920
import Control.Monad.IO.Class (liftIO)
2021
import Control.Monad.Reader (runReaderT, ask, ReaderT)
2122

22-
import Control.Lens (view, _3, _2)
23+
import Control.Lens (Lens', view)
2324
import Data.List (isInfixOf, intercalate)
2425
import System.Process (callProcess, readProcess)
2526
import System.Directory
@@ -37,20 +38,24 @@ data Condition
3738
systemTests ::
3839
TestTree
3940
systemTests =
40-
testGroup
41-
"user acceptance tests"
42-
[ testUserViewsMailSuccessfully
43-
, testUserCanManipulateNMQuery
44-
, testUserCanSwitchBackToIndex
45-
, testCanToggleHeaders
46-
, testSetsMailToRead
47-
, testErrorHandling
48-
, testHelp
49-
, testManageTagsOnMails
50-
, testManageTagsOnThreads
51-
]
52-
53-
testManageTagsOnMails :: TestTree
41+
withResource pre post $ \_ ->
42+
testGroup "user acceptance tests" $ zipWith ($) tests [0..]
43+
where
44+
pre = let n = "keepalive" in setUpTmuxSession n $> n
45+
post = cleanUpTmuxSession
46+
tests =
47+
[ testUserViewsMailSuccessfully
48+
, testUserCanManipulateNMQuery
49+
, testUserCanSwitchBackToIndex
50+
, testCanToggleHeaders
51+
, testSetsMailToRead
52+
, testErrorHandling
53+
, testHelp
54+
, testManageTagsOnMails
55+
, testManageTagsOnThreads
56+
]
57+
58+
testManageTagsOnMails :: Int -> TestTree
5459
testManageTagsOnMails = withTmuxSession "manage tags on mails" $
5560
\step -> do
5661
startApplication
@@ -95,7 +100,7 @@ testManageTagsOnMails = withTmuxSession "manage tags on mails" $
95100

96101
pure ()
97102

98-
testManageTagsOnThreads :: TestTree
103+
testManageTagsOnThreads :: Int -> TestTree
99104
testManageTagsOnThreads = withTmuxSession "manage tags on threads" $
100105
\step -> do
101106
startApplication
@@ -117,7 +122,7 @@ testManageTagsOnThreads = withTmuxSession "manage tags on threads" $
117122

118123
pure ()
119124

120-
testHelp :: TestTree
125+
testHelp :: Int -> TestTree
121126
testHelp = withTmuxSession "help view" $
122127
\step -> do
123128
startApplication
@@ -128,8 +133,7 @@ testHelp = withTmuxSession "help view" $
128133
sendKeys "Escape" (Literal "Purebred")
129134
pure ()
130135

131-
testErrorHandling ::
132-
TestTree
136+
testErrorHandling :: Int -> TestTree
133137
testErrorHandling = withTmuxSession "error handling" $
134138
\step -> do
135139
startApplication
@@ -149,8 +153,7 @@ testErrorHandling = withTmuxSession "error handling" $
149153

150154
pure ()
151155

152-
testSetsMailToRead ::
153-
TestTree
156+
testSetsMailToRead :: Int -> TestTree
154157
testSetsMailToRead = withTmuxSession "user can toggle read tag" $
155158
\step -> do
156159
startApplication
@@ -166,8 +169,7 @@ testSetsMailToRead = withTmuxSession "user can toggle read tag" $
166169
sendKeys "t" (Regex (buildAnsiRegex ["1"] ["37"] ["43"] <> ".*Testmail"))
167170
pure ()
168171

169-
testCanToggleHeaders ::
170-
TestTree
172+
testCanToggleHeaders :: Int -> TestTree
171173
testCanToggleHeaders = withTmuxSession "user can toggle Headers" $
172174
\step -> do
173175
startApplication
@@ -184,8 +186,7 @@ testCanToggleHeaders = withTmuxSession "user can toggle Headers" $
184186
out <- sendKeys "h" (Literal "This is a test mail")
185187
assertRegex "Purebred.*\n.*[Ff]rom" out
186188

187-
testUserViewsMailSuccessfully ::
188-
TestTree
189+
testUserViewsMailSuccessfully :: Int -> TestTree
189190
testUserViewsMailSuccessfully = withTmuxSession "user can view mail" $
190191
\step -> do
191192
startApplication
@@ -200,8 +201,7 @@ testUserViewsMailSuccessfully = withTmuxSession "user can view mail" $
200201
sendKeys "Enter" (Literal "This is a test mail")
201202
pure ()
202203

203-
testUserCanManipulateNMQuery ::
204-
TestTree
204+
testUserCanManipulateNMQuery :: Int -> TestTree
205205
testUserCanManipulateNMQuery =
206206
withTmuxSession
207207
"manipulating notmuch search query results in empty index" $
@@ -234,8 +234,7 @@ testUserCanManipulateNMQuery =
234234
sendKeys "Enter" (Literal "HOLY PUREBRED")
235235
pure ()
236236

237-
testUserCanSwitchBackToIndex ::
238-
TestTree
237+
testUserCanSwitchBackToIndex :: Int -> TestTree
239238
testUserCanSwitchBackToIndex =
240239
withTmuxSession "user can switch back to mail index during composition" $
241240
\step -> do
@@ -268,8 +267,6 @@ testUserCanSwitchBackToIndex =
268267
sendKeys "Tab" (Literal "test subject")
269268
pure ()
270269

271-
type Env = (String, String, String)
272-
273270
assertSubstrInOutput :: String -> String -> ReaderT Env IO ()
274271
assertSubstrInOutput substr out = liftIO $ assertBool (substr <> " not found in\n\n" <> out) $ substr `isInfixOf` out
275272

@@ -282,16 +279,35 @@ assertRegex regex out = liftIO $ assertBool
282279
defaultSessionName :: String
283280
defaultSessionName = "purebredtest"
284281

285-
tearDown :: (String, String, String) -> IO ()
286-
tearDown (testdir, _, _)= do
282+
data Env = Env
283+
{ _envDir :: FilePath
284+
, _envMaildir :: FilePath
285+
, _envSessionName :: String
286+
}
287+
288+
envDir :: Lens' Env FilePath
289+
envDir f (Env a b c) = fmap (\a' -> Env a' b c) (f a)
290+
291+
envMaildir :: Lens' Env FilePath
292+
envMaildir f (Env a b c) = fmap (\b' -> Env a b' c) (f b)
293+
294+
envSessionName :: Lens' Env String
295+
envSessionName f (Env a b c) = fmap (\c' -> Env a b c') (f c)
296+
{-# ANN envSessionName ("HLint: ignore Avoid lambda" :: String) #-}
297+
298+
-- | Tear down a test session
299+
tearDown :: Env -> IO ()
300+
tearDown (Env testdir _ sessionName) = do
287301
removeDirectoryRecursive testdir
288-
cleanUpTmuxSession defaultSessionName
302+
cleanUpTmuxSession sessionName
289303

290-
setUp :: IO (String, String, String)
291-
setUp = do
292-
let sessionname = defaultSessionName
293-
(testdir, testmdir) <- setUpTmuxSession sessionname >> setUpTempMaildir
294-
pure (testdir, testmdir, sessionname)
304+
-- | Set up a test session.
305+
setUp :: Int -> IO Env
306+
setUp i = do
307+
let sessionName = defaultSessionName <> show i
308+
setUpTmuxSession sessionName
309+
(testdir, maildir) <- setUpTempMaildir
310+
pure $ Env testdir maildir sessionName
295311

296312
setUpTempMaildir :: IO (String, String)
297313
setUpTempMaildir = do
@@ -358,22 +374,28 @@ cleanUpTmuxSession sessionname =
358374

359375

360376
-- | Run all application steps in a session defined by session name.
361-
withTmuxSession :: TestName -> ((String -> IO ()) -> ReaderT Env IO ()) -> TestTree
362-
withTmuxSession tcname testfx =
363-
withResource setUp tearDown $
377+
withTmuxSession
378+
:: TestName
379+
-> ((String -> IO ()) -> ReaderT Env IO ())
380+
-> Int -- ^ session sequence number (will be appended to session name)
381+
-> TestTree
382+
withTmuxSession tcname testfx i =
383+
withResource (setUp i) tearDown $
364384
\env -> testCaseSteps tcname $ \stepfx -> env >>= runReaderT (testfx stepfx)
365385

366386
-- | Send keys into the program and wait for the condition to be
367387
-- met, failing the test if the condition is not met after some
368388
-- time.
369389
sendKeys :: String -> Condition -> ReaderT Env IO String
370390
sendKeys keys expect = do
371-
liftIO $ callProcess "tmux" $ communicateSessionArgs keys False
391+
sessionName <- getSessionName
392+
liftIO $ callProcess "tmux" $ communicateSessionArgs sessionName keys False
372393
waitForCondition expect defaultCountdown
373394

374395
sendLiteralKeys :: String -> ReaderT Env IO String
375396
sendLiteralKeys keys = do
376-
liftIO $ callProcess "tmux" $ communicateSessionArgs keys True
397+
sessionName <- getSessionName
398+
liftIO $ callProcess "tmux" $ communicateSessionArgs sessionName keys True
377399
waitForString keys defaultCountdown
378400

379401
capture :: ReaderT Env IO String
@@ -382,10 +404,10 @@ capture = do
382404
liftIO $ readProcess "tmux" ["capture-pane", "-e", "-p", "-t", sessionname] []
383405

384406
getSessionName :: ReaderT Env IO String
385-
getSessionName = view (_3 . ask)
407+
getSessionName = view (envSessionName . ask)
386408

387-
getTestMaildir :: ReaderT Env IO String
388-
getTestMaildir = view (_2 . ask)
409+
getTestMaildir :: ReaderT Env IO FilePath
410+
getTestMaildir = view (envMaildir . ask)
389411

390412
holdOffTime :: Int
391413
holdOffTime = 10^6
@@ -436,12 +458,18 @@ defaultCountdown = 5
436458
startApplication :: ReaderT Env IO ()
437459
startApplication = do
438460
testmdir <- getTestMaildir
439-
liftIO $ callProcess "tmux" $ communicateSessionArgs ("purebred --database " <> testmdir <> "\r") False
461+
sessionName <- getSessionName
462+
liftIO $ callProcess "tmux" $
463+
communicateSessionArgs sessionName ("purebred --database " <> testmdir <> "\r") False
440464
void $ waitForString "Purebred: Item" defaultCountdown
441465

442-
communicateSessionArgs :: String -> Bool -> [String]
443-
communicateSessionArgs keys asLiteral =
444-
["send-keys", "-t", defaultSessionName] <> ["-l" | asLiteral] <> [keys]
466+
communicateSessionArgs
467+
:: String -- ^ session name
468+
-> String -- ^ keys
469+
-> Bool -- ^ send the keys literally
470+
-> [String]
471+
communicateSessionArgs sessionName keys asLiteral =
472+
["send-keys", "-t", sessionName] <> ["-l" | asLiteral] <> [keys]
445473

446474

447475
type AnsiAttrParam = String

0 commit comments

Comments
 (0)