Skip to content

Commit f3551f6

Browse files
fix tests with tmux >= 2.5
In tmux < 03d01eabb5c5227f56b6b44d04964c1328802628 (first released in tmux-2.5), the `capture-pane -e' output ran attributes, foreground colour and background colour params together into one escape sequence. After that commit, attributes, foreground colours and background colours are written in separate escape sequences. Therefore for compatibility with different versions of tmux we need to check the ANSI escape sequences in two different ways. Fixes: #78
1 parent 4ab0987 commit f3551f6

File tree

1 file changed

+99
-59
lines changed

1 file changed

+99
-59
lines changed

test/TestUserAcceptance.hs

Lines changed: 99 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
{-# OPTIONS_GHC -fno-warn-name-shadowing #-}
22
{-# OPTIONS_GHC -fno-warn-type-defaults #-}
3+
{-# OPTIONS_GHC -fno-warn-unused-do-bind #-}
4+
35
{-# LANGUAGE OverloadedStrings #-}
6+
47
module TestUserAcceptance where
58

69
import qualified Data.Text as T
@@ -17,14 +20,20 @@ import Control.Monad.IO.Class (liftIO)
1720
import Control.Monad.Reader (runReaderT, ask, ReaderT)
1821

1922
import Control.Lens (view, _3, _2)
20-
import Data.List (isInfixOf)
23+
import Data.List (isInfixOf, intercalate)
2124
import System.Process (callProcess, readProcess)
2225
import System.Directory
2326
(getCurrentDirectory, removeDirectoryRecursive, removeFile)
2427
import Test.Tasty (TestTree, TestName, testGroup, withResource)
2528
import Test.Tasty.HUnit (testCaseSteps, assertBool)
2629
import Text.Regex.Posix ((=~))
2730

31+
-- | A condition to check for in the output of the program
32+
data Condition
33+
= Literal String
34+
| Regex String
35+
deriving (Show)
36+
2837
systemTests ::
2938
TestTree
3039
systemTests =
@@ -47,43 +56,39 @@ testErrorHandling = withTmuxSession "error handling" $
4756
liftIO $ removeFile (testmdir <> "/new/1502941827.R15455991756849358775.url")
4857

4958
liftIO $ step "shows error message"
50-
out <- sendKeys "Enter" "FileReadError"
51-
assertSubstrInOutput "openFile: does not exist" out
59+
sendKeys "Enter" (Literal "FileReadError")
60+
>>= assertSubstrInOutput "openFile: does not exist"
5261

5362
testSetsMailToRead ::
5463
TestTree
5564
testSetsMailToRead = withTmuxSession "user can toggle read tag" $
5665
\step -> do
5766
startApplication
5867
liftIO $ step "mail is shown as unread (bold)"
59-
out <- capture
60-
assertRegex "\ESC\\[1;.*Testmail" out
68+
capture >>= assertRegex (buildAnsiRegex ["1"] ["37"] ["43"] <> ".*Testmail")
6169

62-
liftIO $ step "view mail and purebred sets it to read"
63-
_ <- sendKeys "Enter" "This is a test mail"
64-
out <- sendKeys "Escape" "is Purebred"
65-
assertRegex "\ESC\\[37.*Testmail" out
70+
liftIO $ step "view mail and purebred sets it to read (unbold)"
71+
sendKeys "Enter" (Literal "This is a test mail")
72+
sendKeys "Escape" (Literal "is Purebred")
73+
>>= assertRegex "\ESC\\[37.*Testmail"
6674

67-
liftIO $ step "toggle it back to unread"
68-
-- wait for the screen turns bold
69-
out <- sendKeys "t" "1;37;43m"
70-
assertRegex "\ESC\\[1;.*Testmail" out
75+
liftIO $ step "toggle it back to unread (bold again)"
76+
sendKeys "t" (Regex (buildAnsiRegex ["1"] ["37"] ["43"] <> ".*Testmail"))
77+
pure ()
7178

7279
testCanToggleHeaders ::
7380
TestTree
7481
testCanToggleHeaders = withTmuxSession "user can toggle Headers" $
7582
\step -> do
7683
startApplication
7784
liftIO $ step "view mail"
78-
out <- sendKeys "Enter" "This is a test mail"
79-
assertSubstrInOutput "This is a test mail" out
85+
sendKeys "Enter" (Literal "This is a test mail")
8086

8187
liftIO $ step "toggle to show all headers"
82-
out <- sendKeys "h" "return-path"
83-
assertSubstrInOutput "return-path" out
88+
sendKeys "h" (Literal "return-path")
8489

8590
liftIO $ step "toggle filtered headers"
86-
out <- sendKeys "h" "This is a test mail"
91+
out <- sendKeys "h" (Literal "This is a test mail")
8792
assertRegex "Purebred.*\n.*from" out
8893

8994
testUserViewsMailSuccessfully ::
@@ -96,8 +101,8 @@ testUserViewsMailSuccessfully = withTmuxSession "user can view mail" $
96101
assertSubstrInOutput "inbox" out
97102

98103
liftIO $ step "view mail"
99-
out <- sendKeys "Enter" "This is a test mail"
100-
assertSubstrInOutput "This is a test mail" out
104+
sendKeys "Enter" (Literal "This is a test mail")
105+
pure ()
101106

102107
testUserCanManipulateNMQuery ::
103108
TestTree
@@ -107,23 +112,20 @@ testUserCanManipulateNMQuery =
107112
\step -> do
108113
startApplication
109114
liftIO $ step "focus command"
110-
out <- sendKeys ":" "37;40mtag"
111-
assertSubstrInOutput "37;40mtag" out
115+
sendKeys ":" (Regex (buildAnsiRegex [] ["37"] ["40"] <> "tag"))
112116

113117
liftIO $ step "delete all input"
114-
out <- sendKeys "C-u" "37;40m"
115-
assertSubstrInOutput "37;40m" out
118+
sendKeys "C-u" (Regex (buildAnsiRegex [] ["37"] ["40"]))
116119

117120
liftIO $ step "enter new tag"
118121
_ <- sendLiteralKeys "tag:replied"
119122

120123
liftIO $ step "apply"
121-
out <- sendKeys "Enter" "Item 0 of 1"
122-
assertSubstrInOutput "Item 0 of 1" out
124+
sendKeys "Enter" (Literal "Item 0 of 1")
123125

124126
liftIO $ step "view currently selected mail"
125-
out <- sendKeys "Enter" "HOLY PUREBRED"
126-
assertSubstrInOutput "HOLY PUREBRED" out
127+
sendKeys "Enter" (Literal "HOLY PUREBRED")
128+
pure ()
127129

128130
testUserCanSwitchBackToIndex ::
129131
TestTree
@@ -132,48 +134,40 @@ testUserCanSwitchBackToIndex =
132134
\step -> do
133135
startApplication
134136
liftIO $ step "start composition"
135-
out <- sendKeys "m" "From"
136-
assertSubstrInOutput "From" out
137+
sendKeys "m" (Literal "From")
137138

138139
liftIO $ step "enter from email"
139-
out <- sendKeys "testuser@foo.test\r" "To"
140-
assertSubstrInOutput "To" out
140+
sendKeys "testuser@foo.test\r" (Literal "To")
141141

142142
liftIO $ step "enter to: email"
143-
out <- sendKeys "user@to.test\r" "Subject"
144-
assertSubstrInOutput "Subject" out
143+
sendKeys "user@to.test\r" (Literal "Subject")
145144

146145
liftIO $ step "enter subject"
147-
out <- sendKeys "test subject\r" "~"
148-
assertSubstrInOutput "~" out
146+
sendKeys "test subject\r" (Literal "~")
149147

150148
liftIO $ step "enter mail body"
151-
out <- sendKeys "iThis is a test body" "body"
152-
assertSubstrInOutput "body" out
149+
sendKeys "iThis is a test body" (Literal "body")
153150

154151
liftIO $ step "exit insert mode in vim"
155-
out <- sendKeys "Escape" "body"
156-
assertSubstrInOutput "body" out
152+
sendKeys "Escape" (Literal "body")
157153

158154
liftIO $ step "exit vim"
159-
out <- sendKeys ": x\r" "Attachments"
160-
assertSubstrInOutput "Attachments" out
155+
sendKeys ": x\r" (Literal "Attachments")
161156

162157
liftIO $ step "switch back to index"
163-
out <- sendKeys "Tab" "Testmail"
164-
assertSubstrInOutput "Testmail" out
158+
sendKeys "Tab" (Literal "Testmail")
165159

166160
liftIO $ step "switch back to the compose editor"
167-
out <- sendKeys "Tab" "test subject"
168-
assertSubstrInOutput "test subject" out
161+
sendKeys "Tab" (Literal "test subject")
162+
pure ()
169163

170164
type Env = (String, String, String)
171165

172166
assertSubstrInOutput :: String -> String -> ReaderT Env IO ()
173167
assertSubstrInOutput substr out = liftIO $ assertBool (substr <> " not found in\n\n" <> out) $ substr `isInfixOf` out
174168

175169
assertRegex :: String -> String -> ReaderT Env IO ()
176-
assertRegex regex out = liftIO $ assertBool (regex <> " does not match out\n\n" <> out) $ out =~ (regex :: String)
170+
assertRegex regex out = liftIO $ assertBool (regex <> " does not match out\n\n" <> out <> "\n\n raw:\n\n" <> show out) $ out =~ (regex :: String)
177171

178172
defaultSessionName :: String
179173
defaultSessionName = "purebredtest"
@@ -254,10 +248,13 @@ withTmuxSession tcname testfx =
254248
withResource setUp tearDown $
255249
\env -> testCaseSteps tcname $ \stepfx -> env >>= runReaderT (testfx stepfx)
256250

257-
sendKeys :: String -> String -> ReaderT Env IO (String)
251+
-- | Send keys into the program and wait for the condition to be
252+
-- met, failing the test if the condition is not met after some
253+
-- time.
254+
sendKeys :: String -> Condition -> ReaderT Env IO String
258255
sendKeys keys expect = do
259256
liftIO $ callProcess "tmux" $ communicateSessionArgs keys False
260-
waitForString expect defaultCountdown
257+
waitForCondition expect defaultCountdown
261258

262259
sendLiteralKeys :: String -> ReaderT Env IO (String)
263260
sendLiteralKeys keys = do
@@ -285,24 +282,35 @@ debugOutput out = do
285282
when (isJust d) $ hPutStr stderr ("\n\n" <> out)
286283

287284
-- | wait for the application to render a new interface which we determine with
288-
-- a given substring. If we exceed the number of tries return with the last
289-
-- captured output, but indicate an error by setting the baton to 0
290-
waitForString :: String -> Int -> ReaderT Env IO (String)
291-
waitForString substr n = do
285+
-- a given condition. We check up to @n@ times, waiting a short duration
286+
-- between each check, and failing if the tries exhaust with the condition
287+
-- not met.
288+
waitForCondition :: Condition -> Int -> ReaderT Env IO String
289+
waitForCondition cond n = do
292290
out <- capture >>= checkPane
293-
liftIO $ assertBool ("Wait time exceeded. Expected: '"
294-
<> substr
295-
<> "' last screen shot:\n\n "
296-
<> out) (substr `isInfixOf` out)
291+
liftIO $ assertBool
292+
( "Wait time exceeded. Condition not met: '" <> show cond
293+
<> "' last screen shot:\n\n " <> out <> "\n\n" <> " raw: " <> show out )
294+
(checkCondition cond out)
297295
pure out
298296
where
299297
checkPane :: String -> ReaderT Env IO String
300298
checkPane out
301-
| substr `isInfixOf` out = pure out
299+
| checkCondition cond out = pure out
302300
| n <= 0 = pure out
303301
| otherwise = do
304302
liftIO $ threadDelay holdOffTime
305-
waitForString substr (n - 1)
303+
waitForCondition cond (n - 1)
304+
305+
checkCondition :: Condition -> String -> Bool
306+
checkCondition (Literal s) = (s `isInfixOf`)
307+
checkCondition (Regex re) = (=~ re)
308+
309+
-- | Convenience version of 'waitForCondition' that checks for a
310+
-- literal string.
311+
--
312+
waitForString :: String -> Int -> ReaderT Env IO (String)
313+
waitForString = waitForCondition . Literal
306314

307315
defaultCountdown :: Int
308316
defaultCountdown = 5
@@ -324,3 +332,35 @@ communicateSessionArgs keys asLiteral =
324332
then ["-l"]
325333
else []
326334
in base <> postfix <> [keys]
335+
336+
337+
type AnsiAttrParam = String
338+
type AnsiFGParam = String
339+
type AnsiBGParam = String
340+
341+
-- | Generate a regex for an escape sequence setting the given
342+
-- foreground and background parameters
343+
--
344+
-- tmux < 03d01eabb5c5227f56b6b44d04964c1328802628 (first released
345+
-- in tmux-2.5) ran attributes, foreground colour and background
346+
-- colour params separated by semicolons (foreground first).
347+
--
348+
-- After that commit, attributes, foreground colours and background
349+
-- colours are written in separate escape sequences. Therefore for
350+
-- compatibility with different versions of tmux there are two
351+
-- patterns to check.
352+
--
353+
buildAnsiRegex :: [AnsiAttrParam] -> [AnsiFGParam] -> [AnsiBGParam] -> String
354+
buildAnsiRegex attrs fgs bgs =
355+
let
356+
withSemis = intercalate ";"
357+
wrap [] = ""
358+
wrap xs = "\ESC\\[" <> withSemis xs <> "m"
359+
tmux24 = wrap (attrs <> fgs <> bgs)
360+
tmux25 = wrap attrs <> wrap fgs <> wrap bgs
361+
choice "" "" = ""
362+
choice "" r = r
363+
choice l "" = l
364+
choice l r = "(" <> l <> "|" <> r <> ")"
365+
in
366+
choice tmux24 tmux25

0 commit comments

Comments
 (0)