43
43
||| + `idris2` The path of the executable we are testing.
44
44
||| + `codegen` The backend to use for code generation.
45
45
||| + `onlyNames` The tests to run relative to the generated executable.
46
+ ||| + `onlyFile` The file listing the tests to run relative to the generated executable.
46
47
||| + `interactive` Whether to offer to update the expected file or not.
47
48
||| + `timing` Whether to display time taken for each test.
48
49
||| + `threads` The maximum numbers to use (default: number of cores).
50
+ ||| + `failureFile` The file in which to write the list of failing tests.
49
51
|||
50
52
||| We provide an options parser (`options`) that takes the list of command line
51
53
||| arguments and constructs this for you.
62
64
63
65
module Test.Golden
64
66
67
+ import Data.Either
65
68
import Data.Maybe
66
69
import Data.List
67
70
import Data.List1
@@ -93,6 +96,8 @@ record Options where
93
96
timing : Bool
94
97
||| How many threads should we use?
95
98
threads : Nat
99
+ ||| Should we write the list of failing cases from a file?
100
+ failureFile : Maybe String
96
101
97
102
export
98
103
initOptions : String -> Options
@@ -103,6 +108,7 @@ initOptions exe
103
108
False
104
109
False
105
110
1
111
+ Nothing
106
112
107
113
export
108
114
usage : String -> String
@@ -113,37 +119,48 @@ usage exe = unwords
113
119
, " [--interactive]"
114
120
, " [--cg CODEGEN]"
115
121
, " [--threads N]"
122
+ , " [--failure-file PATH]"
123
+ , " [--only-file PATH]"
116
124
, " [--only [NAMES]]"
117
125
]
118
126
127
+ export
128
+ fail : String -> IO a
129
+ fail err
130
+ = do putStrLn err
131
+ exitWith (ExitFailure 1 )
132
+
119
133
||| Process the command line options.
120
134
export
121
- options : List String -> Maybe Options
135
+ options : List String -> IO ( Maybe Options)
122
136
options args = case args of
123
- (_ :: exe :: rest) => go rest (initOptions exe)
124
- _ => Nothing
137
+ (_ :: exe :: rest) => mkOptions exe rest
138
+ _ => pure Nothing
125
139
126
140
where
127
141
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
137
153
_ => Nothing
138
154
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
147
164
148
165
||| Normalise strings between different OS.
149
166
|||
@@ -156,13 +173,18 @@ normalize str =
156
173
then pack $ filter (\ ch => ch /= ' /' && ch /= ' \\ ' ) (unpack str)
157
174
else str
158
175
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
+
159
181
||| Run the specified Golden test with the supplied options.
160
182
|||
161
183
||| See the module documentation for more information.
162
184
|||
163
185
||| @testPath the directory that contains the test.
164
186
export
165
- runTest : Options -> String -> IO (Future Bool )
187
+ runTest : Options -> String -> IO (Future Result )
166
188
runTest opts testPath = forkIO $ do
167
189
start <- clockTime Thread
168
190
let cg = case codegen opts of
@@ -174,16 +196,16 @@ runTest opts testPath = forkIO $ do
174
196
175
197
Right out <- readFile $ testPath ++ " /output"
176
198
| Left err => do print err
177
- pure False
199
+ pure ( Left testPath)
178
200
179
201
Right exp <- readFile $ testPath ++ " /expected"
180
202
| Left FileNotFound => do
181
203
if interactive opts
182
204
then mayOverwrite Nothing out
183
205
else print FileNotFound
184
- pure False
206
+ pure ( Left testPath)
185
207
| Left err => do print err
186
- pure False
208
+ pure ( Left testPath)
187
209
188
210
let result = normalize out == normalize exp
189
211
let time = timeDifference end start
@@ -196,7 +218,7 @@ runTest opts testPath = forkIO $ do
196
218
then mayOverwrite (Just exp ) out
197
219
else putStrLn . unlines $ expVsOut exp out
198
220
199
- pure result
221
+ pure $ if result then Right testPath else Left testPath
200
222
201
223
where
202
224
getAnswer : IO Bool
@@ -298,6 +320,7 @@ findCG
298
320
public export
299
321
record TestPool where
300
322
constructor MkTestPool
323
+ poolName : String
301
324
constraints : List Requirement
302
325
testCases : List String
303
326
@@ -308,14 +331,43 @@ filterTests opts = case onlyNames opts of
308
331
[] => id
309
332
xs => filter (\ name => any (`isInfixOf` name) xs)
310
333
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
+
311
362
||| A runner for a test pool
312
363
export
313
- poolRunner : Options -> TestPool -> IO ( List Bool )
364
+ poolRunner : Options -> TestPool -> IO Summary
314
365
poolRunner opts pool
315
366
= do -- check that we indeed want to run some of these tests
316
367
let tests = filterTests opts (testCases pool)
317
368
let (_ :: _ ) = tests
318
- | [] => pure []
369
+ | [] => pure initSummary
370
+ putStrLn banner
319
371
-- if so make sure the constraints are satisfied
320
372
cs <- for (constraints pool) $ \ req => do
321
373
mfp <- checkRequirement req
@@ -324,37 +376,60 @@ poolRunner opts pool
324
376
Just fp => " Found " ++ show req ++ " at " ++ fp
325
377
pure mfp
326
378
let Just _ = the (Maybe (List String )) (sequence cs)
327
- | Nothing => pure []
379
+ | Nothing => pure initSummary
328
380
-- if so run them all!
329
- loop [] tests
381
+ loop initSummary tests
330
382
331
383
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
334
392
loop acc tests
335
393
= do let (now, later) = splitAt opts. threads tests
336
394
bs <- map await <$> traverse (runTest opts) now
337
- loop (bs :: acc) later
395
+ loop (updateSummary bs acc) later
338
396
339
397
340
398
||| A runner for a whole test suite
341
399
export
342
400
runner : List TestPool -> IO ()
343
401
runner tests
344
402
= 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" )
348
406
-- if no CG has been set, find a sensible default based on what is available
349
407
opts <- case codegen opts of
350
408
Nothing => pure $ record { codegen = ! findCG } opts
351
409
Just _ => pure opts
352
410
-- run the tests
353
411
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 )
359
434
360
435
-- [ EOF ]
0 commit comments