1{-# LANGUAGE CPP #-}
2{-# LANGUAGE FlexibleContexts #-}
3
4#if __GLASGOW_HASKELL__ >= 704
5{-# LANGUAGE ConstraintKinds #-}
6#define HasCallStack_ HasCallStack =>
7#else
8#define HasCallStack_
9#endif
10
11-- | Basic definitions for the HUnit library.
12--
13--   This module contains what you need to create assertions and test cases and
14--   combine them into test suites.
15--
16--   This module also provides infrastructure for
17--   implementing test controllers (which are used to execute tests).
18--   See "Test.HUnit.Text" for a great example of how to implement a test
19--   controller.
20
21module Test.HUnit.Base
22(
23  -- ** Declaring tests
24  Test(..),
25  (~=?), (~?=), (~:), (~?),
26
27  -- ** Making assertions
28  assertFailure, {- from Test.HUnit.Lang: -}
29  assertBool, assertEqual, assertString,
30  Assertion, {- from Test.HUnit.Lang: -}
31  (@=?), (@?=), (@?),
32
33  -- ** Extending the assertion functionality
34  Assertable(..), ListAssertable(..),
35  AssertionPredicate, AssertionPredicable(..),
36  Testable(..),
37
38  -- ** Test execution
39  -- $testExecutionNote
40  State(..), Counts(..),
41  Path, Node(..),
42  testCasePaths,
43  testCaseCount,
44  ReportStart, ReportProblem,
45  performTest
46) where
47
48import Control.Monad (unless, foldM)
49import Data.CallStack
50
51
52-- Assertion Definition
53-- ====================
54
55import Test.HUnit.Lang
56
57
58-- Conditional Assertion Functions
59-- -------------------------------
60
61-- | Asserts that the specified condition holds.
62assertBool :: HasCallStack_
63              String    -- ^ The message that is displayed if the assertion fails
64           -> Bool      -- ^ The condition
65           -> Assertion
66assertBool msg b = unless b (assertFailure msg)
67
68-- | Signals an assertion failure if a non-empty message (i.e., a message
69-- other than @\"\"@) is passed.
70assertString :: HasCallStack_
71                String    -- ^ The message that is displayed with the assertion failure
72             -> Assertion
73assertString s = unless (null s) (assertFailure s)
74
75-- Overloaded `assert` Function
76-- ----------------------------
77
78-- | Allows the extension of the assertion mechanism.
79--
80-- Since an 'Assertion' can be a sequence of @Assertion@s and @IO@ actions,
81-- there is a fair amount of flexibility of what can be achieved.  As a rule,
82-- the resulting @Assertion@ should be the body of a 'TestCase' or part of
83-- a @TestCase@; it should not be used to assert multiple, independent
84-- conditions.
85--
86-- If more complex arrangements of assertions are needed, 'Test's and
87-- 'Testable' should be used.
88class Assertable t
89 where assert :: HasCallStack_ t -> Assertion
90
91instance Assertable ()
92 where assert = return
93
94instance Assertable Bool
95 where assert = assertBool ""
96
97instance (ListAssertable t) => Assertable [t]
98 where assert = listAssert
99
100instance (Assertable t) => Assertable (IO t)
101 where assert = (>>= assert)
102
103-- | A specialized form of 'Assertable' to handle lists.
104class ListAssertable t
105 where listAssert :: HasCallStack_ [t] -> Assertion
106
107instance ListAssertable Char
108 where listAssert = assertString
109
110
111-- Overloaded `assertionPredicate` Function
112-- ----------------------------------------
113
114-- | The result of an assertion that hasn't been evaluated yet.
115--
116-- Most test cases follow the following steps:
117--
118-- 1. Do some processing or an action.
119--
120-- 2. Assert certain conditions.
121--
122-- However, this flow is not always suitable.  @AssertionPredicate@ allows for
123-- additional steps to be inserted without the initial action to be affected
124-- by side effects.  Additionally, clean-up can be done before the test case
125-- has a chance to end.  A potential work flow is:
126--
127-- 1. Write data to a file.
128--
129-- 2. Read data from a file, evaluate conditions.
130--
131-- 3. Clean up the file.
132--
133-- 4. Assert that the side effects of the read operation meet certain conditions.
134--
135-- 5. Assert that the conditions evaluated in step 2 are met.
136type AssertionPredicate = IO Bool
137
138-- | Used to signify that a data type can be converted to an assertion
139-- predicate.
140class AssertionPredicable t
141 where assertionPredicate :: t -> AssertionPredicate
142
143instance AssertionPredicable Bool
144 where assertionPredicate = return
145
146instance (AssertionPredicable t) => AssertionPredicable (IO t)
147 where assertionPredicate = (>>= assertionPredicate)
148
149
150-- Assertion Construction Operators
151-- --------------------------------
152
153infix  1 @?, @=?, @?=
154
155-- | Asserts that the condition obtained from the specified
156--   'AssertionPredicable' holds.
157(@?) :: HasCallStack_ AssertionPredicable t
158                                => t          -- ^ A value of which the asserted condition is predicated
159                                -> String     -- ^ A message that is displayed if the assertion fails
160                                -> Assertion
161predi @? msg = assertionPredicate predi >>= assertBool msg
162
163-- | Asserts that the specified actual value is equal to the expected value
164--   (with the expected value on the left-hand side).
165(@=?) :: HasCallStack_ (Eq a, Show a)
166                        => a -- ^ The expected value
167                        -> a -- ^ The actual value
168                        -> Assertion
169expected @=? actual = assertEqual "" expected actual
170
171-- | Asserts that the specified actual value is equal to the expected value
172--   (with the actual value on the left-hand side).
173(@?=) :: HasCallStack_ (Eq a, Show a)
174                        => a -- ^ The actual value
175                        -> a -- ^ The expected value
176                        -> Assertion
177actual @?= expected = assertEqual "" expected actual
178
179
180
181-- Test Definition
182-- ===============
183
184-- | The basic structure used to create an annotated tree of test cases.
185data Test
186    -- | A single, independent test case composed.
187    = TestCase Assertion
188    -- | A set of @Test@s sharing the same level in the hierarchy.
189    | TestList [Test]
190    -- | A name or description for a subtree of the @Test@s.
191    | TestLabel String Test
192
193instance Show Test where
194  showsPrec _ (TestCase _)    = showString "TestCase _"
195  showsPrec _ (TestList ts)   = showString "TestList " . showList ts
196  showsPrec p (TestLabel l t) = showString "TestLabel " . showString l
197                                . showChar ' ' . showsPrec p t
198
199-- Overloaded `test` Function
200-- --------------------------
201
202-- | Provides a way to convert data into a @Test@ or set of @Test@.
203class Testable t
204 where test :: HasCallStack_ t -> Test
205
206instance Testable Test
207 where test = id
208
209instance (Assertable t) => Testable (IO t)
210 where test = TestCase . assert
211
212instance (Testable t) => Testable [t]
213 where test = TestList . map test
214
215
216-- Test Construction Operators
217-- ---------------------------
218
219infix  1 ~?, ~=?, ~?=
220infixr 0 ~:
221
222-- | Creates a test case resulting from asserting the condition obtained
223--   from the specified 'AssertionPredicable'.
224(~?) :: HasCallStack_ AssertionPredicable t
225                                => t       -- ^ A value of which the asserted condition is predicated
226                                -> String  -- ^ A message that is displayed on test failure
227                                -> Test
228predi ~? msg = TestCase (predi @? msg)
229
230-- | Shorthand for a test case that asserts equality (with the expected
231--   value on the left-hand side, and the actual value on the right-hand
232--   side).
233(~=?) :: HasCallStack_ (Eq a, Show a)
234                        => a     -- ^ The expected value
235                        -> a     -- ^ The actual value
236                        -> Test
237expected ~=? actual = TestCase (expected @=? actual)
238
239-- | Shorthand for a test case that asserts equality (with the actual
240--   value on the left-hand side, and the expected value on the right-hand
241--   side).
242(~?=) :: HasCallStack_ (Eq a, Show a)
243                        => a     -- ^ The actual value
244                        -> a     -- ^ The expected value
245                        -> Test
246actual ~?= expected = TestCase (actual @?= expected)
247
248-- | Creates a test from the specified 'Testable', with the specified
249--   label attached to it.
250--
251-- Since 'Test' is @Testable@, this can be used as a shorthand way of attaching
252-- a 'TestLabel' to one or more tests.
253(~:) :: HasCallStack_ Testable t => String -> t -> Test
254label ~: t = TestLabel label (test t)
255
256
257
258-- Test Execution
259-- ==============
260
261-- $testExecutionNote
262-- Note: the rest of the functionality in this module is intended for
263-- implementors of test controllers. If you just want to run your tests cases,
264-- simply use a test controller, such as the text-based controller in
265-- "Test.HUnit.Text".
266
267-- | A data structure that hold the results of tests that have been performed
268-- up until this point.
269data Counts = Counts { cases, tried, errors, failures :: Int }
270  deriving (Eq, Show, Read)
271
272-- | Keeps track of the remaining tests and the results of the performed tests.
273-- As each test is performed, the path is removed and the counts are
274-- updated as appropriate.
275data State = State { path :: Path, counts :: Counts }
276  deriving (Eq, Show, Read)
277
278-- | Report generator for reporting the start of a test run.
279type ReportStart us = State -> us -> IO us
280
281-- | Report generator for reporting problems that have occurred during
282--   a test run. Problems may be errors or assertion failures.
283type ReportProblem us = Maybe SrcLoc -> String -> State -> us -> IO us
284
285-- | Uniquely describes the location of a test within a test hierarchy.
286-- Node order is from test case to root.
287type Path = [Node]
288
289-- | Composed into 'Path's.
290data Node  = ListItem Int | Label String
291  deriving (Eq, Show, Read)
292
293-- | Determines the paths for all 'TestCase's in a tree of @Test@s.
294testCasePaths :: Test -> [Path]
295testCasePaths t0 = tcp t0 []
296 where tcp (TestCase _) p = [p]
297       tcp (TestList ts) p =
298         concat [ tcp t (ListItem n : p) | (t,n) <- zip ts [0..] ]
299       tcp (TestLabel l t) p = tcp t (Label l : p)
300
301-- | Counts the number of 'TestCase's in a tree of @Test@s.
302testCaseCount :: Test -> Int
303testCaseCount (TestCase _)    = 1
304testCaseCount (TestList ts)   = sum (map testCaseCount ts)
305testCaseCount (TestLabel _ t) = testCaseCount t
306
307-- | Performs a test run with the specified report generators.
308--
309-- This handles the actual running of the tests.  Most developers will want
310-- to use @HUnit.Text.runTestTT@ instead.  A developer could use this function
311-- to execute tests via another IO system, such as a GUI, or to output the
312-- results in a different manner (e.g., upload XML-formatted results to a
313-- webservice).
314--
315-- Note that the counts in a start report do not include the test case
316-- being started, whereas the counts in a problem report do include the
317-- test case just finished.  The principle is that the counts are sampled
318-- only between test case executions.  As a result, the number of test
319-- case successes always equals the difference of test cases tried and
320-- the sum of test case errors and failures.
321performTest :: ReportStart us   -- ^ report generator for the test run start
322            -> ReportProblem us -- ^ report generator for errors during the test run
323            -> ReportProblem us -- ^ report generator for assertion failures during the test run
324            -> us
325            -> Test             -- ^ the test to be executed
326            -> IO (Counts, us)
327performTest reportStart reportError reportFailure initialUs initialT = do
328  (ss', us') <- pt initState initialUs initialT
329  unless (null (path ss')) $ error "performTest: Final path is nonnull"
330  return (counts ss', us')
331 where
332  initState  = State{ path = [], counts = initCounts }
333  initCounts = Counts{ cases = testCaseCount initialT, tried = 0,
334                       errors = 0, failures = 0}
335
336  pt ss us (TestCase a) = do
337    us' <- reportStart ss us
338    r <- performTestCase a
339    case r of
340      Success -> do
341        return (ss', us')
342      Failure loc m -> do
343        usF <- reportFailure loc m ssF us'
344        return (ssF, usF)
345      Error loc m -> do
346        usE <- reportError loc m ssE us'
347        return (ssE, usE)
348   where c@Counts{ tried = n } = counts ss
349         ss' = ss{ counts = c{ tried = n + 1 } }
350         ssF = ss{ counts = c{ tried = n + 1, failures = failures c + 1 } }
351         ssE = ss{ counts = c{ tried = n + 1, errors   = errors   c + 1 } }
352
353  pt ss us (TestList ts) = foldM f (ss, us) (zip ts [0..])
354   where f (ss', us') (t, n) = withNode (ListItem n) ss' us' t
355
356  pt ss us (TestLabel label t) = withNode (Label label) ss us t
357
358  withNode node ss0 us0 t = do (ss2, us1) <- pt ss1 us0 t
359                               return (ss2{ path = path0 }, us1)
360   where path0 = path ss0
361         ss1 = ss0{ path = node : path0 }
362