-
Notifications
You must be signed in to change notification settings - Fork 120
Expand file tree
/
Copy pathIngredients.hs
More file actions
162 lines (153 loc) · 5.99 KB
/
Ingredients.hs
File metadata and controls
162 lines (153 loc) · 5.99 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
-- | This module contains the core definitions related to ingredients.
--
-- Ingredients themselves are provided by other modules (usually under
-- the @Test.Tasty.Ingredients.*@ hierarchy).
--
-- @since 0.8
module Test.Tasty.Ingredients
( Ingredient(..)
, tryIngredients
, ingredientOptions
, ingredientsOptions
, suiteOptions
, composeReporters
) where
import Control.Monad
import Data.Proxy
import qualified Data.Foldable as F
import Test.Tasty.Core
import Test.Tasty.Run
import Test.Tasty.Options
import Test.Tasty.Options.Core
import Control.Concurrent.Async (concurrently)
-- | 'Ingredient's make your test suite tasty.
--
-- Ingredients represent different actions that you can perform on your
-- test suite. One obvious ingredient that you want to include is
-- one that runs tests and reports the progress and results.
--
-- Another standard ingredient is one that simply prints the names of all
-- tests.
--
-- Similar to test providers (see 'IsTest'), every ingredient may specify
-- which options it cares about, so that those options are presented to
-- the user if the ingredient is included in the test suite.
--
-- An ingredient can choose, typically based on the t'OptionSet', whether to
-- run. That's what the 'Maybe' is for. The first ingredient that agreed to
-- run does its work, and the remaining ingredients are ignored. Thus, the
-- order in which you arrange the ingredients may matter.
--
-- Usually, the ingredient which runs the tests is unconditional and thus
-- should be placed last in the list. Other ingredients usually run only
-- if explicitly requested via an option. Their relative order thus doesn't
-- matter.
--
-- That's all you need to know from an (advanced) user perspective. Read
-- on if you want to create a new ingredient.
--
-- There are two kinds of ingredients.
--
-- The first kind is 'TestReporter'. If the ingredient that agrees to run
-- is a 'TestReporter', then tasty will automatically launch the tests and
-- pass a 'StatusMap' to the ingredient. All the ingredient needs to do
-- then is to process the test results and probably report them to the user
-- in some way (hence the name).
--
-- 'TestManager' is the second kind of ingredient. It is typically used for
-- test management purposes (such as listing the test names), although it
-- can also be used for running tests (but, unlike 'TestReporter', it has
-- to launch the tests manually if it wants them to be run). It is
-- therefore more general than 'TestReporter'. 'TestReporter' is provided
-- just for convenience.
--
-- The function's result should indicate whether all the tests passed.
--
-- In the 'TestManager' case, it's up to the ingredient author to decide
-- what the result should be. When no tests are run, the result should
-- probably be 'True'. Sometimes, even if some tests run and fail, it still
-- makes sense to return 'True'.
--
-- @since 0.4
data Ingredient
= TestReporter
[OptionDescription]
(OptionSet -> TestTree -> Maybe (StatusMap -> IO (Time -> IO Bool)))
-- ^ For the explanation on how the callback works, see the
-- documentation for 'launchTestTree'.
--
-- @since 0.10
| TestManager
[OptionDescription]
(OptionSet -> TestTree -> Maybe (IO Bool))
-- ^ @since 0.4
-- | Try to run an 'Ingredient'.
--
-- If the ingredient refuses to run (usually based on the t'OptionSet'),
-- the function returns 'Nothing'.
--
-- For a 'TestReporter', this function automatically starts running the
-- tests in the background.
--
-- This function is not publicly exposed.
tryIngredient :: Ingredient -> OptionSet -> TestTree -> Maybe (IO Bool)
tryIngredient (TestReporter _ report) opts testTree = do -- Maybe monad
reportFn <- report opts testTree
return $ launchTestTree opts testTree $ \smap -> reportFn smap
tryIngredient (TestManager _ manage) opts testTree =
manage opts testTree
-- | Run the first 'Ingredient' that agrees to be run.
--
-- If no one accepts the task, return 'Nothing'. This is usually a sign of
-- misconfiguration.
--
-- @since 0.4
tryIngredients :: [Ingredient] -> OptionSet -> TestTree -> Maybe (IO Bool)
tryIngredients ins opts' tree' =
msum $ map (\i -> tryIngredient i opts tree) ins
where
(opts, tree) = applyTopLevelPlusTestOptions opts' tree'
-- | Return the options which are relevant for the given ingredient.
--
-- Note that this isn't the same as simply pattern-matching on
-- 'Ingredient'. E.g. options for a 'TestReporter' automatically include
-- t'NumThreads'.
--
-- @since 0.4
ingredientOptions :: Ingredient -> [OptionDescription]
ingredientOptions (TestReporter opts _) =
Option (Proxy :: Proxy NumThreads) : opts
ingredientOptions (TestManager opts _) = opts
-- | Like 'ingredientOptions', but folds over multiple ingredients.
--
-- @since 0.4
ingredientsOptions :: [Ingredient] -> [OptionDescription]
ingredientsOptions = uniqueOptionDescriptions . F.foldMap ingredientOptions
-- | All the options relevant for this test suite. This includes the
-- options for the test tree and ingredients, and the core options.
--
-- @since 0.4
suiteOptions :: [Ingredient] -> TestTree -> [OptionDescription]
suiteOptions ins tree = uniqueOptionDescriptions $
coreOptions ++
ingredientsOptions ins ++
treeOptions tree
-- | Compose two 'TestReporter' ingredients which are then executed
-- in parallel. This can be useful if you want to have two reporters
-- active at the same time, e.g., one which prints to the console and
-- one which writes the test results to a file.
--
-- Be aware that it is not possible to use 'composeReporters' with a 'TestManager',
-- it only works for 'TestReporter' ingredients.
--
-- @since 0.11.2
composeReporters :: Ingredient -> Ingredient -> Ingredient
composeReporters (TestReporter o1 f1) (TestReporter o2 f2) =
TestReporter (o1 ++ o2) $ \o t ->
case (f1 o t, f2 o t) of
(g, Nothing) -> g
(Nothing, g) -> g
(Just g1, Just g2) -> Just $ \s -> do
(h1, h2) <- concurrently (g1 s) (g2 s)
return $ \x -> fmap (uncurry (&&)) $ concurrently (h1 x) (h2 x)
composeReporters _ _ = error "Only TestReporters can be composed"