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