10 | import Evince.Report
11 | import Evince.Reporter
12 | import Evince.Reporter.Console
13 | import Evince.Reporter.JUnit
16 | hasFocused : List (SpecTree m a) -> Bool
17 | hasFocused [] = False
18 | hasFocused (Focused _ :: _) = True
19 | hasFocused (Describe _ children :: rest) = hasFocused children || hasFocused rest
20 | hasFocused (WithCleanup _ children :: rest) = hasFocused children || hasFocused rest
21 | hasFocused (_ :: rest) = hasFocused rest
24 | filterFocused : List (SpecTree m a) -> List (SpecTree m a)
25 | filterFocused [] = []
26 | filterFocused (Focused t :: rest) = t :: filterFocused rest
27 | filterFocused (Describe label children :: rest) =
28 | focusedInto (Describe label) children (filterFocused rest)
29 | filterFocused (WithCleanup cleanup children :: rest) =
30 | focusedInto (WithCleanup cleanup) children (filterFocused rest)
31 | filterFocused (_ :: rest) = filterFocused rest
33 | focusedInto : (List (SpecTree m a) -> SpecTree m a) -> List (SpecTree m a) -> List (SpecTree m a) -> List (SpecTree m a)
34 | focusedInto wrap children rest =
35 | case filterFocused children of
37 | filtered => wrap filtered :: rest
39 | applyFocus : List (SpecTree m a) -> List (SpecTree m a)
40 | applyFocus trees = if hasFocused trees then filterFocused trees else trees
44 | filterByLabel : (keep : String -> Bool) -> List (SpecTree m a) -> List (SpecTree m a)
45 | filterByLabel keep [] = []
46 | filterByLabel keep (It label loc test :: rest) =
48 | then It label loc test :: filterByLabel keep rest
49 | else filterByLabel keep rest
50 | filterByLabel keep (Describe label children :: rest) =
52 | then Describe label children :: filterByLabel keep rest
53 | else let filtered = filterByLabel keep children
55 | [] => filterByLabel keep rest
56 | _ => Describe label filtered :: filterByLabel keep rest
57 | filterByLabel keep (Pending label reason :: rest) =
59 | then Pending label reason :: filterByLabel keep rest
60 | else filterByLabel keep rest
61 | filterByLabel keep (Focused t :: rest) =
62 | case filterByLabel keep [t] of
63 | [t'] => Focused t' :: filterByLabel keep rest
64 | _ => filterByLabel keep rest
65 | filterByLabel keep (WithCleanup cleanup children :: rest) =
66 | case filterByLabel keep children of
67 | [] => filterByLabel keep rest
68 | filtered => WithCleanup cleanup filtered :: filterByLabel keep rest
72 | dropByLabel : (matches : String -> Bool) -> List (SpecTree m a) -> List (SpecTree m a)
73 | dropByLabel matches [] = []
74 | dropByLabel matches (It label loc test :: rest) =
76 | then dropByLabel matches rest
77 | else It label loc test :: dropByLabel matches rest
78 | dropByLabel matches (Describe label children :: rest) =
80 | then dropByLabel matches rest
81 | else case dropByLabel matches children of
82 | [] => dropByLabel matches rest
83 | filtered => Describe label filtered :: dropByLabel matches rest
84 | dropByLabel matches (Pending label reason :: rest) =
86 | then dropByLabel matches rest
87 | else Pending label reason :: dropByLabel matches rest
88 | dropByLabel matches (Focused t :: rest) =
89 | case dropByLabel matches [t] of
90 | [t'] => Focused t' :: dropByLabel matches rest
91 | _ => dropByLabel matches rest
92 | dropByLabel matches (WithCleanup cleanup children :: rest) =
93 | case dropByLabel matches children of
94 | [] => dropByLabel matches rest
95 | filtered => WithCleanup cleanup filtered :: dropByLabel matches rest
97 | filterByMatch : String -> List (SpecTree m a) -> List (SpecTree m a)
98 | filterByMatch pat = filterByLabel (isInfixOf pat)
100 | filterBySkip : String -> List (SpecTree m a) -> List (SpecTree m a)
101 | filterBySkip pat = dropByLabel (isInfixOf pat)
103 | filterByPaths : List String -> List String -> List (SpecTree m a) -> List (SpecTree m a)
104 | filterByPaths _ _ [] = []
105 | filterByPaths paths ctx (It label loc test :: rest) =
106 | if joinPath (ctx ++ [label]) `elem` paths
107 | then It label loc test :: filterByPaths paths ctx rest
108 | else filterByPaths paths ctx rest
109 | filterByPaths paths ctx (Describe label children :: rest) =
110 | let filtered = filterByPaths paths (ctx ++ [label]) children
111 | in case filtered of
112 | [] => filterByPaths paths ctx rest
113 | _ => Describe label filtered :: filterByPaths paths ctx rest
114 | filterByPaths paths ctx (Pending label reason :: rest) =
115 | if joinPath (ctx ++ [label]) `elem` paths
116 | then Pending label reason :: filterByPaths paths ctx rest
117 | else filterByPaths paths ctx rest
118 | filterByPaths paths ctx (Focused t :: rest) =
119 | case filterByPaths paths ctx [t] of
120 | [t'] => Focused t' :: filterByPaths paths ctx rest
121 | _ => filterByPaths paths ctx rest
122 | filterByPaths paths ctx (WithCleanup cleanup children :: rest) =
123 | case filterByPaths paths ctx children of
124 | [] => filterByPaths paths ctx rest
125 | filtered => WithCleanup cleanup filtered :: filterByPaths paths ctx rest
127 | shuffleTrees : Nat -> List (SpecTree m a) -> List (SpecTree m a)
128 | shuffleTrees seed [] = []
129 | shuffleTrees seed trees = shuffle seed (go (nextSeed seed) trees)
131 | goTree : Nat -> SpecTree m a -> SpecTree m a
132 | goTree s (Describe label children) = Describe label (shuffleTrees s children)
133 | goTree s (WithCleanup cleanup children) = WithCleanup cleanup (shuffleTrees s children)
134 | goTree s (Focused t) = Focused (goTree s t)
139 | go : Nat -> List (SpecTree m a) -> List (SpecTree m a)
141 | go s (t :: ts) = goTree s t :: go (nextSeed s) ts
143 | applyFilters : RunConfig -> List (SpecTree m a) -> List (SpecTree m a)
144 | applyFilters cfg trees =
145 | let t1 = applyFocus trees
146 | t2 = maybe t1 (\p => filterByMatch p t1) cfg.match
147 | t3 = maybe t2 (\p => filterBySkip p t2) cfg.skip
148 | t4 = if cfg.randomize
149 | then let s = maybe 42 id cfg.seed in shuffleTrees s t3
157 | EvalResult = (Summary, SnocList TestReport)
162 | emptyResult : EvalResult
163 | emptyResult = (neutral, [<])
168 | mergeResults : EvalResult -> EvalResult -> EvalResult
169 | mergeResults (s1, r1) (s2, r2) = (s1 <+> s2, r1 ++ r2)
176 | evalTree : HasIO m => Reporter m -> RunConfig -> IORef Bool -> List String -> SpecTree m () -> Nat -> m EvalResult
177 | evalTree reporter cfg abortRef path (Describe label children) level = do
178 | reporter.onEvent (GroupStarted label level)
179 | r <- evalForest reporter cfg abortRef (path ++ [label]) children (S level)
180 | reporter.onEvent (GroupDone label)
182 | evalTree reporter cfg abortRef path (It label loc test) level = do
183 | abort <- liftIO (readIORef abortRef)
185 | then pure emptyResult
187 | start <- liftIO (clockTime Monotonic)
189 | end <- liftIO (clockTime Monotonic)
190 | let elapsed = toNano (timeDifference end start)
191 | let testPath = path ++ [label]
192 | let s = case result of
193 | Pass _ => { passed := 1, duration := elapsed } neutral
194 | Fail _ => { failed := 1, duration := elapsed } neutral
195 | Skip _ => { pending := 1 } neutral
196 | let report = case result of
197 | Pass _ => MkTestReport testPath loc (Passed elapsed)
198 | Fail info => MkTestReport testPath loc (Failed info elapsed)
199 | Skip reason => MkTestReport testPath loc (Skipped reason)
200 | reporter.onEvent (TestDone report level)
201 | when (cfg.failFast && s.failed > 0) (liftIO (writeIORef abortRef True))
202 | pure (s, [< report])
203 | evalTree reporter cfg abortRef path (Pending label reason) level = do
204 | reporter.onEvent (PendingTest label reason level)
205 | let report = MkTestReport (path ++ [label]) Nothing (Skipped reason)
206 | pure ({ pending := 1 } neutral, [< report])
207 | evalTree reporter cfg abortRef path (Focused tree) level =
208 | evalTree reporter cfg abortRef path tree level
209 | evalTree reporter cfg abortRef path (WithCleanup cleanup children) level = do
210 | r <- evalForest reporter cfg abortRef path children level
217 | evalForest : HasIO m => Reporter m -> RunConfig -> IORef Bool -> List String -> List (SpecTree m ()) -> Nat -> m EvalResult
218 | evalForest _ _ _ _ [] _ = pure emptyResult
219 | evalForest reporter cfg abortRef path (t :: ts) level = do
220 | abort <- liftIO (readIORef abortRef)
222 | then pure emptyResult
224 | r1 <- evalTree reporter cfg abortRef path t level
225 | r2 <- evalForest reporter cfg abortRef path ts level
226 | pure (mergeResults r1 r2)
232 | makeReporter : HasIO m => RunConfig -> m (Reporter m)
233 | makeReporter cfg0 = do
234 | noColor <- getEnv "NO_COLOR"
235 | let cfg = if maybe False (/= "") noColor then { color := False } cfg0 else cfg0
236 | let console = consoleReporter cfg
237 | case cfg.junitOutput of
239 | junit <- junitReporter path
240 | pure (combineReporters [console, junit])
241 | Nothing => pure console
245 | failedPaths : SnocList TestReport -> List (List String)
246 | failedPaths = foldl (\acc, r => case r.outcome of Failed _ _ => r.path :: acc;
_ => acc) []
255 | -> (eval : IORef Bool -> List (SpecTree m ()) -> m EvalResult)
257 | -> List (SpecTree m ())
259 | runForestWith reporter eval cfg trees = do
260 | let filtered = applyFilters cfg trees
261 | rerunFiltered <- if cfg.rerun
263 | Just failures <- liftIO readFailures
264 | | Nothing => pure filtered
265 | pure (filterByPaths failures [] filtered)
267 | abortRef <- liftIO (newIORef False)
268 | reporter.onEvent SuiteStarted
269 | r <- eval abortRef rerunFiltered
270 | reporter.onEvent (SuiteDone (fst r))
275 | runWithConfig : HasIO m => RunConfig -> List (SpecTree m ()) -> m EvalResult
276 | runWithConfig cfg trees = do
277 | reporter <- makeReporter cfg
278 | runForestWith reporter (\abortRef, ts => evalForest reporter cfg abortRef [] ts 0) cfg trees
282 | runSpecWithSummaryAndConfig : RunConfig -> Spec IO () () -> IO Summary
283 | runSpecWithSummaryAndConfig cfg spec = do
284 | (summary, _) <- runWithConfig cfg (getSpecTrees spec)
290 | runSpecWithSummary : Spec IO () () -> IO Summary
291 | runSpecWithSummary = runSpecWithSummaryAndConfig defaultConfig
295 | runSpecWith : RunConfig -> Spec IO () () -> IO ()
296 | runSpecWith cfg spec = do
297 | (summary, reports) <- runWithConfig cfg (getSpecTrees spec)
298 | writeFailures (failedPaths reports)
299 | when (summary.failed > 0) exitFailure
303 | runSpec : Spec IO () () -> IO ()
304 | runSpec = runSpecWith defaultConfig
308 | runSpecFailFast : Spec IO () () -> IO ()
309 | runSpecFailFast = runSpecWith ({ failFast := True } defaultConfig)
313 | runSpecTimed : Spec IO () () -> IO ()
314 | runSpecTimed = runSpecWith ({ showTiming := True } defaultConfig)
319 | runSpecWithArgs : Spec IO () () -> IO ()
320 | runSpecWithArgs spec = do
322 | cfg <- handleArgs (drop 1 args)
323 | runSpecWith cfg spec