Skip to content

Commit 004cc45

Browse files
authored
[ test ] cosmetic changes & retest capability (#1394)
* Banners for test pools * Summary with the list of failing tests at the end * Option to write the list of failing tests to a file * Option to read the list of tests to run from a file * Using these two latest features to add a new make target to rerun precisely the tests that failed last time
1 parent 1d70a83 commit 004cc45

File tree

7 files changed

+166
-68
lines changed

7 files changed

+166
-68
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ idris2docs_venv
2121
/tests/**/*.so
2222
/tests/**/*.dylib
2323
/tests/**/*.dll
24+
/tests/failures
2425

2526
/benchmark/**/build
2627
/benchmark/*.csv

Makefile

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,13 @@ test: testenv
112112
@echo
113113
@${MAKE} -C tests only=$(only) IDRIS2=${TARGET} IDRIS2_PREFIX=${TEST_PREFIX}
114114

115+
retest: testenv
116+
@echo
117+
@echo "NOTE: \`${MAKE} retest\` does not rebuild Idris or the libraries packaged with it; to do that run \`${MAKE}\`"
118+
@if [ ! -x "${TARGET}" ]; then echo "ERROR: Missing IDRIS2 executable. Cannot run tests!\n"; exit 1; fi
119+
@echo
120+
@${MAKE} -C tests retest only=$(only) IDRIS2=${TARGET} IDRIS2_PREFIX=${TEST_PREFIX}
121+
115122

116123
support:
117124
@${MAKE} -C support/c

libs/prelude/Prelude/Interfaces.idr

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -221,12 +221,17 @@ public export
221221
guard : Alternative f => Bool -> f ()
222222
guard x = if x then pure () else empty
223223

224-
||| Conditionally execute an applicative expression.
224+
||| Conditionally execute an applicative expression when the boolean is true.
225225
public export
226226
when : Applicative f => Bool -> Lazy (f ()) -> f ()
227227
when True f = f
228228
when False f = pure ()
229229

230+
||| Execute an applicative expression unless the boolean is true.
231+
%inline public export
232+
unless : Applicative f => Bool -> Lazy (f ()) -> f ()
233+
unless = when . not
234+
230235
---------------------------
231236
-- FOLDABLE, TRAVERSABLE --
232237
---------------------------

libs/prelude/Prelude/Types.idr

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,12 @@ maybe : Lazy b -> Lazy (a -> b) -> Maybe a -> b
157157
maybe n j Nothing = n
158158
maybe n j (Just x) = j x
159159

160+
||| Execute an applicative expression when the Maybe is Just
161+
%inline public export
162+
whenJust : Applicative f => Maybe a -> (a -> f ()) -> f ()
163+
whenJust (Just a) k = k a
164+
whenJust Nothing k = pure ()
165+
160166
public export
161167
Eq a => Eq (Maybe a) where
162168
Nothing == Nothing = True

libs/test/Test/Golden.idr

Lines changed: 115 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -43,9 +43,11 @@
4343
||| + `idris2` The path of the executable we are testing.
4444
||| + `codegen` The backend to use for code generation.
4545
||| + `onlyNames` The tests to run relative to the generated executable.
46+
||| + `onlyFile` The file listing the tests to run relative to the generated executable.
4647
||| + `interactive` Whether to offer to update the expected file or not.
4748
||| + `timing` Whether to display time taken for each test.
4849
||| + `threads` The maximum numbers to use (default: number of cores).
50+
||| + `failureFile` The file in which to write the list of failing tests.
4951
|||
5052
||| We provide an options parser (`options`) that takes the list of command line
5153
||| arguments and constructs this for you.
@@ -62,6 +64,7 @@
6264

6365
module Test.Golden
6466

67+
import Data.Either
6568
import Data.Maybe
6669
import Data.List
6770
import Data.List1
@@ -93,6 +96,8 @@ record Options where
9396
timing : Bool
9497
||| How many threads should we use?
9598
threads : Nat
99+
||| Should we write the list of failing cases from a file?
100+
failureFile : Maybe String
96101

97102
export
98103
initOptions : String -> Options
@@ -103,6 +108,7 @@ initOptions exe
103108
False
104109
False
105110
1
111+
Nothing
106112

107113
export
108114
usage : String -> String
@@ -113,37 +119,48 @@ usage exe = unwords
113119
, "[--interactive]"
114120
, "[--cg CODEGEN]"
115121
, "[--threads N]"
122+
, "[--failure-file PATH]"
123+
, "[--only-file PATH]"
116124
, "[--only [NAMES]]"
117125
]
118126

127+
export
128+
fail : String -> IO a
129+
fail err
130+
= do putStrLn err
131+
exitWith (ExitFailure 1)
132+
119133
||| Process the command line options.
120134
export
121-
options : List String -> Maybe Options
135+
options : List String -> IO (Maybe Options)
122136
options args = case args of
123-
(_ :: exe :: rest) => go rest (initOptions exe)
124-
_ => Nothing
137+
(_ :: exe :: rest) => mkOptions exe rest
138+
_ => pure Nothing
125139

126140
where
127141

128-
go : List String -> Options -> Maybe Options
129-
go rest opts = case rest of
130-
[] => pure opts
131-
("--timing" :: xs) => go xs (record { timing = True} opts)
132-
("--interactive" :: xs) => go xs (record { interactive = True } opts)
133-
("--cg" :: cg :: xs) => go xs (record { codegen = Just cg } opts)
134-
("--threads" :: n :: xs) => do let pos : Nat = !(parsePositive n)
135-
go xs (record { threads = pos } opts)
136-
("--only" :: xs) => pure $ record { onlyNames = xs } opts
142+
go : List String -> Maybe String -> Options -> Maybe (Maybe String, Options)
143+
go rest only opts = case rest of
144+
[] => pure (only, opts)
145+
("--timing" :: xs) => go xs only (record { timing = True} opts)
146+
("--interactive" :: xs) => go xs only (record { interactive = True } opts)
147+
("--cg" :: cg :: xs) => go xs only (record { codegen = Just cg } opts)
148+
("--threads" :: n :: xs) => do let pos : Nat = !(parsePositive n)
149+
go xs only (record { threads = pos } opts)
150+
("--failure-file" :: p :: xs) => go xs only (record { failureFile = Just p } opts)
151+
("--only" :: xs) => pure (only, record { onlyNames = xs } opts)
152+
("--only-file" :: p :: xs) => go xs (Just p) opts
137153
_ => Nothing
138154

139-
-- [ Core ]
140-
141-
export
142-
fail : String -> IO ()
143-
fail err
144-
= do putStrLn err
145-
exitWith (ExitFailure 1)
146-
155+
mkOptions : String -> List String -> IO (Maybe Options)
156+
mkOptions exe rest
157+
= do let Just (mfp, opts) = go rest Nothing (initOptions exe)
158+
| Nothing => pure Nothing
159+
let Just fp = mfp
160+
| Nothing => pure (Just opts)
161+
Right only <- readFile fp
162+
| Left err => fail (show err)
163+
pure $ Just $ record { onlyNames $= (forget (lines only) ++) } opts
147164

148165
||| Normalise strings between different OS.
149166
|||
@@ -156,13 +173,18 @@ normalize str =
156173
then pack $ filter (\ch => ch /= '/' && ch /= '\\') (unpack str)
157174
else str
158175

176+
||| The result of a test run
177+
||| `Left` corresponds to a failure, and `Right` to a success
178+
Result : Type
179+
Result = Either String String
180+
159181
||| Run the specified Golden test with the supplied options.
160182
|||
161183
||| See the module documentation for more information.
162184
|||
163185
||| @testPath the directory that contains the test.
164186
export
165-
runTest : Options -> String -> IO (Future Bool)
187+
runTest : Options -> String -> IO (Future Result)
166188
runTest opts testPath = forkIO $ do
167189
start <- clockTime Thread
168190
let cg = case codegen opts of
@@ -174,16 +196,16 @@ runTest opts testPath = forkIO $ do
174196

175197
Right out <- readFile $ testPath ++ "/output"
176198
| Left err => do print err
177-
pure False
199+
pure (Left testPath)
178200

179201
Right exp <- readFile $ testPath ++ "/expected"
180202
| Left FileNotFound => do
181203
if interactive opts
182204
then mayOverwrite Nothing out
183205
else print FileNotFound
184-
pure False
206+
pure (Left testPath)
185207
| Left err => do print err
186-
pure False
208+
pure (Left testPath)
187209

188210
let result = normalize out == normalize exp
189211
let time = timeDifference end start
@@ -196,7 +218,7 @@ runTest opts testPath = forkIO $ do
196218
then mayOverwrite (Just exp) out
197219
else putStrLn . unlines $ expVsOut exp out
198220

199-
pure result
221+
pure $ if result then Right testPath else Left testPath
200222

201223
where
202224
getAnswer : IO Bool
@@ -298,6 +320,7 @@ findCG
298320
public export
299321
record TestPool where
300322
constructor MkTestPool
323+
poolName : String
301324
constraints : List Requirement
302325
testCases : List String
303326

@@ -308,14 +331,43 @@ filterTests opts = case onlyNames opts of
308331
[] => id
309332
xs => filter (\ name => any (`isInfixOf` name) xs)
310333

334+
||| The summary of a test pool run
335+
public export
336+
record Summary where
337+
constructor MkSummary
338+
success : List String
339+
failure : List String
340+
341+
export
342+
initSummary : Summary
343+
initSummary = MkSummary [] []
344+
345+
export
346+
updateSummary : List Result -> Summary -> Summary
347+
updateSummary res =
348+
let (ls, ws) = partitionEithers res in
349+
{ success $= (ws ++)
350+
, failure $= (ls ++)
351+
}
352+
353+
export
354+
Semigroup Summary where
355+
MkSummary ws1 ls1 <+> MkSummary ws2 ls2
356+
= MkSummary (ws1 ++ ws2) (ls1 ++ ls2)
357+
358+
export
359+
Monoid Summary where
360+
neutral = initSummary
361+
311362
||| A runner for a test pool
312363
export
313-
poolRunner : Options -> TestPool -> IO (List Bool)
364+
poolRunner : Options -> TestPool -> IO Summary
314365
poolRunner opts pool
315366
= do -- check that we indeed want to run some of these tests
316367
let tests = filterTests opts (testCases pool)
317368
let (_ :: _) = tests
318-
| [] => pure []
369+
| [] => pure initSummary
370+
putStrLn banner
319371
-- if so make sure the constraints are satisfied
320372
cs <- for (constraints pool) $ \ req => do
321373
mfp <- checkRequirement req
@@ -324,37 +376,60 @@ poolRunner opts pool
324376
Just fp => "Found " ++ show req ++ " at " ++ fp
325377
pure mfp
326378
let Just _ = the (Maybe (List String)) (sequence cs)
327-
| Nothing => pure []
379+
| Nothing => pure initSummary
328380
-- if so run them all!
329-
loop [] tests
381+
loop initSummary tests
330382

331383
where
332-
loop : List (List Bool) -> List String -> IO (List Bool)
333-
loop acc [] = pure (concat $ reverse acc)
384+
385+
banner : String
386+
banner =
387+
let separator = fastPack $ replicate 72 '-' in
388+
fastUnlines [ "", separator, pool.poolName, separator, "" ]
389+
390+
loop : Summary -> List String -> IO Summary
391+
loop acc [] = pure acc
334392
loop acc tests
335393
= do let (now, later) = splitAt opts.threads tests
336394
bs <- map await <$> traverse (runTest opts) now
337-
loop (bs :: acc) later
395+
loop (updateSummary bs acc) later
338396

339397

340398
||| A runner for a whole test suite
341399
export
342400
runner : List TestPool -> IO ()
343401
runner tests
344402
= do args <- getArgs
345-
let (Just opts) = options args
346-
| _ => do print args
347-
putStrLn (usage "runtests")
403+
Just opts <- options args
404+
| _ => do print args
405+
putStrLn (usage "runtests")
348406
-- if no CG has been set, find a sensible default based on what is available
349407
opts <- case codegen opts of
350408
Nothing => pure $ record { codegen = !findCG } opts
351409
Just _ => pure opts
352410
-- run the tests
353411
res <- concat <$> traverse (poolRunner opts) tests
354-
putStrLn (show (length (filter id res)) ++ "/" ++ show (length res)
355-
++ " tests successful")
356-
if (any not res)
357-
then exitWith (ExitFailure 1)
358-
else exitWith ExitSuccess
412+
413+
-- report the result
414+
let nsucc = length res.success
415+
let nfail = length res.failure
416+
let ntotal = nsucc + nfail
417+
putStrLn (show nsucc ++ "/" ++ show ntotal ++ " tests successful")
418+
419+
-- deal with failures
420+
let list = fastUnlines res.failure
421+
when (nfail > 0) $
422+
do putStrLn "Failing tests:"
423+
putStrLn list
424+
-- always overwrite the failure file, if it is given
425+
whenJust opts.failureFile $ \ path =>
426+
do Right _ <- writeFile path list
427+
| Left err => fail (show err)
428+
pure ()
429+
430+
-- exit
431+
if nfail == 0
432+
then exitWith ExitSuccess
433+
else exitWith (ExitFailure 1)
359434

360435
-- [ EOF ]

0 commit comments

Comments
 (0)