packages feed

chell 0.2.5 → 0.3

raw patch · 5 files changed

+573/−316 lines, 5 filesdep ~textPVP ok

version bump matches the API change (PVP)

Dependency ranges changed: text

API changes (from Hackage documentation)

- Test.Chell: Assertion :: (IO AssertionResult) -> Assertion
- Test.Chell: AssertionFailed :: Text -> AssertionResult
- Test.Chell: AssertionPassed :: AssertionResult
- Test.Chell: Failure :: (Maybe Location) -> Text -> Failure
- Test.Chell: Location :: Text -> Text -> Integer -> Location
- Test.Chell: Test :: Text -> (TestOptions -> IO TestResult) -> Test
- Test.Chell: assertionsTest :: Text -> Assertions a -> Test
- Test.Chell: data AssertionResult
- Test.Chell: fail :: Q Exp
- Test.Chell: newtype Assertion
+ Test.Chell: assertionFailed :: String -> Assertion
+ Test.Chell: assertionPassed :: Assertion
+ Test.Chell: class BuildSuite a
+ Test.Chell: class SuiteOrTest a
+ Test.Chell: data Assertion
+ Test.Chell: equalLinesWith :: Ord a => (a -> [String]) -> a -> a -> Assertion
+ Test.Chell: failure :: Failure
+ Test.Chell: failureLocation :: Failure -> Maybe Location
+ Test.Chell: failureMessage :: Failure -> String
+ Test.Chell: instance Eq Assertion
+ Test.Chell: instance IsAssertion a => IsAssertion (IO a)
+ Test.Chell: instance Show Assertion
+ Test.Chell: location :: Location
- Test.Chell: TestAborted :: [(Text, Text)] -> Text -> TestResult
+ Test.Chell: TestAborted :: [(String, String)] -> String -> TestResult
- Test.Chell: TestFailed :: [(Text, Text)] -> [Failure] -> TestResult
+ Test.Chell: TestFailed :: [(String, String)] -> [Failure] -> TestResult
- Test.Chell: TestPassed :: [(Text, Text)] -> TestResult
+ Test.Chell: TestPassed :: [(String, String)] -> TestResult
- Test.Chell: assertions :: Text -> Assertions a -> Suite
+ Test.Chell: assertions :: String -> Assertions a -> Test
- Test.Chell: left :: Either a b -> Assertion
+ Test.Chell: left :: Show b => Either a b -> Assertion
- Test.Chell: locationFile :: Location -> Text
+ Test.Chell: locationFile :: Location -> String
- Test.Chell: locationLine :: Location -> Integer
+ Test.Chell: locationLine :: Location -> Maybe Integer
- Test.Chell: locationModule :: Location -> Text
+ Test.Chell: locationModule :: Location -> String
- Test.Chell: note :: Text -> Text -> Assertions ()
+ Test.Chell: note :: String -> String -> Assertions ()
- Test.Chell: nothing :: Maybe a -> Assertion
+ Test.Chell: nothing :: Show a => Maybe a -> Assertion
- Test.Chell: right :: Either a b -> Assertion
+ Test.Chell: right :: Show a => Either a b -> Assertion
- Test.Chell: skipIf :: Bool -> Suite -> Suite
+ Test.Chell: skipIf :: SuiteOrTest a => Bool -> a -> a
- Test.Chell: skipWhen :: IO Bool -> Suite -> Suite
+ Test.Chell: skipWhen :: SuiteOrTest a => IO Bool -> a -> a
- Test.Chell: suite :: Text -> [Suite] -> Suite
+ Test.Chell: suite :: BuildSuite a => String -> a
- Test.Chell: suiteName :: Suite -> Text
+ Test.Chell: suiteName :: Suite -> String
- Test.Chell: test :: Test -> Suite
+ Test.Chell: test :: String -> (TestOptions -> IO TestResult) -> Test
- Test.Chell: testName :: Test -> Text
+ Test.Chell: testName :: Test -> String
- Test.Chell: throws :: Exception err => (err -> Bool) -> IO a -> Assertion
+ Test.Chell: throws :: Exception err => (err -> Bool) -> IO a -> IO Assertion
- Test.Chell: throwsEq :: (Eq err, Exception err, Show err) => err -> IO a -> Assertion
+ Test.Chell: throwsEq :: (Eq err, Exception err, Show err) => err -> IO a -> IO Assertion
- Test.Chell: trace :: Text -> Assertions ()
+ Test.Chell: trace :: Q Exp

Files

chell.cabal view
@@ -1,5 +1,5 @@ name: chell-version: 0.2.5+version: 0.3 license: MIT license-file: license.txt author: John Millikin <jmillikin@gmail.com>@@ -19,26 +19,27 @@   An example test suite, which verifies the behavior of artithmetic operators.   .   @-  &#x7b;-\# LANGUAGE OverloadedStrings \#-&#x7d;   &#x7b;-\# LANGUAGE TemplateHaskell \#-&#x7d;   .   import Test.Chell   .-  test_Math :: Suite-  test_Math = suite \"math\" [test_Addition, test_Subtraction]+  tests_Math :: Suite+  tests_Math = suite \"math\"+  &#x20;   test_Addition+  &#x20;   test_Subtraction   .-  test_Addition :: Suite+  test_Addition :: Test   test_Addition = assertions \"addition\" $ do   &#x20;   $expect (equal (2 + 1) 3)   &#x20;   $expect (equal (1 + 2) 3)   .-  test_Subtraction :: Suite+  test_Subtraction :: Test   test_Subtraction = assertions \"subtraction\" $ do   &#x20;   $expect (equal (2 - 1) 1)   &#x20;   $expect (equal (1 - 2) (-1))   .   main :: IO ()-  main = defaultMain [test_Math]+  main = defaultMain [tests_Math]   @   .   @@@ -49,12 +50,12 @@  source-repository head   type: bazaar-  location: https://john-millikin.com/branches/chell/0.2/+  location: https://john-millikin.com/branches/chell/0.3/  source-repository this   type: bazaar-  location: https://john-millikin.com/branches/chell/0.2/-  tag: chell_0.2.5+  location: https://john-millikin.com/branches/chell/0.3/+  tag: chell_0.3  flag color-output   description: Enable colored output in test results@@ -72,7 +73,7 @@     , random >= 1.0     , system-filepath >= 0.4 && < 0.5     , template-haskell >= 2.3-    , text >= 0.7+    , text     , transformers >= 0.2    if flag(color-output)
lib/Test/Chell.hs view
@@ -1,8 +1,40 @@-{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE TypeSynonymInstances #-} {-# LANGUAGE FlexibleInstances #-} +-- | Chell is a simple and intuitive library for automated testing. It natively+-- supports assertion-based testing, and can use companion libraries+-- such as @chell-quickcheck@ to support more complex testing strategies.+--+-- An example test suite, which verifies the behavior of artithmetic operators.+--+-- @+--{-\# LANGUAGE TemplateHaskell \#-}+--+--import Test.Chell+--+--tests_Math :: Suite+--tests_Math = 'suite' \"math\"+--    test_Addition+--    test_Subtraction+--+--test_Addition :: Test+--test_Addition = 'assertions' \"addition\" $ do+--    $'expect' ('equal' (2 + 1) 3)+--    $'expect' ('equal' (1 + 2) 3)+--+--test_Subtraction :: Test+--test_Subtraction = 'assertions' \"subtraction\" $ do+--    $'expect' ('equal' (2 - 1) 1)+--    $'expect' ('equal' (1 - 2) (-1))+--+--main :: IO ()+--main = 'defaultMain' [tests_Math]+-- @+--+-- >$ ghc --make chell-example.hs+-- >$ ./chell-example+-- >PASS: 2 tests run, 2 tests passed module Test.Chell 	( 	@@ -11,21 +43,25 @@ 	 	-- * Test suites 	, Suite-	, suite 	, suiteName 	, suiteTests-	, test+	+	-- ** Building test suites+	, BuildSuite+	, SuiteOrTest+	, suite+	+	-- ** Skipping some tests 	, skipIf 	, skipWhen 	 	-- * Basic testing library-	-- $doc-basic-testing-	, Assertion (..)-	, AssertionResult (..)-	, IsAssertion 	, Assertions 	, assertions-	, assertionsTest+	, IsAssertion+	, Assertion+	, assertionPassed+	, assertionFailed 	, assert 	, expect 	, die@@ -35,7 +71,7 @@ 	, requireLeft 	, requireRight 	-	-- ** Assertions+	-- ** Built-in assertions 	, equal 	, notEqual 	, equalWithin@@ -53,26 +89,37 @@ 	, equalItems 	, IsText 	, equalLines+	, equalLinesWith 	-	-- * Constructing tests-	, Test (..)+	-- * Custom test types+	, Test+	, test 	, testName 	, runTest 	+	-- ** Test results+	, TestResult (..)+	+	-- *** Failures+	, Failure+	, failure+	, failureLocation+	, failureMessage+	+	-- *** Failure locations+	, Location+	, location+	, locationFile+	, locationModule+	, locationLine+	+	-- ** Test options 	, TestOptions 	, defaultTestOptions 	, testOptionSeed 	, testOptionTimeout-	, TestResult (..)-	, Failure (..)-	, Location (..)-	-	-- * Deprecated-	, fail 	) where -import           Prelude hiding (fail)- import qualified Control.Applicative import qualified Control.Exception import           Control.Exception (Exception)@@ -88,67 +135,45 @@ import qualified Data.Text import           Data.Text (Text) import qualified Data.Text.Lazy-import qualified Data.Text.IO  import qualified Language.Haskell.TH as TH  import           Test.Chell.Main (defaultMain) import           Test.Chell.Types --- | Conditionally skip tests. Use this to avoid commenting out tests--- which are currently broken, or do not work on the current platform.------ @---tests = 'suite' \"tests\"---    [ 'skipIf' builtOnUnix test_WindowsSpecific---    ]--- @----skipIf :: Bool -> Suite -> Suite-skipIf skip = if skip then step else id where-	step (SuiteTest (Test name _)) = SuiteTest-		(Test name (\_ -> return TestSkipped))-	step (Suite name suites) = Suite name (map step suites)---- | Conditionally skip tests, depending on the result of a runtime check. The--- predicate is checked before each test is started.------ @---tests = 'suite' \"tests\"---    [ 'skipWhen' noNetwork test_PingGoogle---    ]--- @-skipWhen :: IO Bool -> Suite -> Suite-skipWhen p = step where-	step (SuiteTest (Test name io)) = SuiteTest (Test name (\opts -> do-		skip <- p-		if skip then return TestSkipped else io opts))-	step (Suite name suites) = Suite name (map step suites)---- $doc-basic-testing------ This library includes a few basic JUnit-style assertions, for use in--- simple test suites where depending on a separate test framework is too--- much trouble.+-- | A single pass/fail assertion. Failed assertions include an explanatory+-- message.+data Assertion+	= AssertionPassed+	| AssertionFailed String+	deriving (Eq, Show) -newtype Assertion = Assertion (IO AssertionResult)+-- | See 'Assertion'.+assertionPassed :: Assertion+assertionPassed = AssertionPassed -data AssertionResult-	= AssertionPassed-	| AssertionFailed Text+-- | See 'Assertion'.+assertionFailed :: String -> Assertion+assertionFailed = AssertionFailed +-- | See 'assert' and 'expect'. class IsAssertion a where-	toAssertion :: a -> Assertion+	runAssertion :: a -> IO Assertion  instance IsAssertion Assertion where-	toAssertion = id+	runAssertion = return  instance IsAssertion Bool where-	toAssertion x = Assertion (return (if x-		then AssertionPassed-		else AssertionFailed "$assert: boolean assertion failed"))+	runAssertion x = return $ if x+		then assertionPassed+		else assertionFailed "boolean assertion failed" -type TestState = (IORef [(Text, Text)], IORef [IO ()], [Failure])+instance IsAssertion a => IsAssertion (IO a) where+	runAssertion x = x >>= runAssertion++type TestState = (IORef [(String, String)], IORef [IO ()], [Failure])++-- | See 'assertions'. newtype Assertions a = Assertions { unAssertions :: TestState -> IO (Maybe a, TestState) }  instance Functor Assertions where@@ -174,25 +199,13 @@ -- | Convert a sequence of pass/fail assertions into a runnable test. -- -- @--- test_Equality :: Suite+-- test_Equality :: Test -- test_Equality = assertions \"equality\" $ do --     $assert (1 == 1) --     $assert (equal 1 1) -- @-assertions :: Text -> Assertions a -> Suite-assertions name io = test (assertionsTest name io)---- | Convert a sequence of pass/fail assertions into a runnable test.------ This is easier to use than 'assertions' when the result is going to be--- modified (eg, by a wrapper) before being used in a test suite.------ Most users should use 'assertions' instead, to avoid the extra wrapping--- step.------ Since: 0.2.3-assertionsTest :: Text -> Assertions a -> Test-assertionsTest name testm = Test name $ \opts -> do+assertions :: String -> Assertions a -> Test+assertions name testm = test name $ \opts -> do 	noteRef <- newIORef [] 	afterTestRef <- newIORef [] 	@@ -217,16 +230,20 @@ 	loop [] = return () 	loop (io:ios) = Control.Exception.finally (loop ios) io -addFailure :: Maybe TH.Loc -> Text -> Assertions ()+addFailure :: Maybe TH.Loc -> String -> Assertions () addFailure maybe_loc msg = Assertions $ \(notes, afterTestRef, fs) -> do 	let loc = do 		th_loc <- maybe_loc-		return $ Location-			{ locationFile = Data.Text.pack (TH.loc_filename th_loc)-			, locationModule = Data.Text.pack (TH.loc_module th_loc)-			, locationLine = toInteger (fst (TH.loc_start th_loc))+		return $ location+			{ locationFile = TH.loc_filename th_loc+			, locationModule = TH.loc_module th_loc+			, locationLine = Just (toInteger (fst (TH.loc_start th_loc))) 			}-	return (Just (), (notes, afterTestRef, Failure loc msg : fs))+	let f = failure+		{ failureLocation = loc+		, failureMessage = msg+		}+	return (Just (), (notes, afterTestRef, f : fs))  -- | Cause a test to immediately fail, with a message. --@@ -236,42 +253,50 @@ -- @ -- $die :: 'String' -> 'Assertions' a -- @------ Since: 0.2.4 die :: TH.Q TH.Exp die = do 	loc <- TH.location 	let qloc = liftLoc loc-	[| \msg -> dieAt $qloc (Data.Text.pack ("die: " ++ msg)) |]+	[| \msg -> dieAt $qloc ("die: " ++ msg) |] -dieAt :: TH.Loc -> Text -> Assertions a+dieAt :: TH.Loc -> String -> Assertions a dieAt loc msg = do 	addFailure (Just loc) msg 	Assertions (\s -> return (Nothing, s)) -{-# DEPRECATED fail "Test.Chell.fail is deprecated; use Test.Chell.die instead." #-}--- | Deprecated in 0.2.4: use 'die' instead.-fail :: TH.Q TH.Exp -- :: Text -> Assertions a-fail = do+-- | Print a message from within a test. This is just a helper for debugging,+-- so you don't have to import @Debug.Trace@. Messages will be prefixed with+-- the filename and line number where @$trace@ was called.+--+-- 'trace' is a Template Haskell macro, to retain the source-file location+-- from which it was used. Its effective type is:+--+-- @+-- $trace :: 'String' -> 'Assertions' ()+-- @+trace :: TH.Q TH.Exp+trace = do 	loc <- TH.location 	let qloc = liftLoc loc-	[| dieAt $qloc |]+	[| traceAt $qloc |] --- | Print a message from within a test. This is just a helper for debugging,--- so you don't have to import @Debug.Trace@.-trace :: Text -> Assertions ()-trace msg = liftIO (Data.Text.IO.putStrLn msg)+traceAt :: TH.Loc -> String -> Assertions ()+traceAt loc msg = liftIO $ do+	let file = TH.loc_filename loc+	let line = fst (TH.loc_start loc)+	putStr ("[" ++ file ++ ":" ++ show line ++ "] ")+	putStrLn msg --- | Attach metadata to a test run. This will be included in reports.-note :: Text -> Text -> Assertions ()+-- | Attach a note to a test run. Notes will be printed to stdout and+-- included in reports, even if the test fails or aborts. Notes are useful for+-- debugging failing tests.+note :: String -> String -> Assertions () note key value = Assertions (\(notes, afterTestRef, fs) -> do 	modifyIORef notes ((key, value) :) 	return (Just (), (notes, afterTestRef, fs)))  -- | Register an IO action to be run after the test completes. This action--- will run even if the test failed or threw an exception.------ Since: 0.2.3+-- will run even if the test failed or aborted. afterTest :: IO () -> Assertions () afterTest io = Assertions (\(notes, ref, fs) -> do 	modifyIORef ref (io :)@@ -286,8 +311,6 @@ -- @ -- $requireLeft :: 'Show' b => 'Either' a b -> 'Assertions' a -- @------ Since: 0.2.4 requireLeft :: TH.Q TH.Exp requireLeft = do 	loc <- TH.location@@ -299,7 +322,7 @@ 	Left a -> return a 	Right b -> do 		let dummy = Right b `asTypeOf` Left ()-		dieAt loc (Data.Text.pack ("requireLeft: received " ++ showsPrec 11 dummy ""))+		dieAt loc ("requireLeft: received " ++ showsPrec 11 dummy "")  -- | Require an 'Either' value to be 'Right', and return its contents. If -- the value is 'Left', fail the test.@@ -310,8 +333,6 @@ -- @ -- $requireRight :: 'Show' a => 'Either' a b -> 'Assertions' b -- @------ Since: 0.2.4 requireRight :: TH.Q TH.Exp requireRight = do 	loc <- TH.location@@ -322,7 +343,7 @@ requireRightAt loc val = case val of 	Left a -> do 		let dummy = Left a `asTypeOf` Right ()-		dieAt loc (Data.Text.pack ("requireRight: received " ++ showsPrec 11 dummy ""))+		dieAt loc ("requireRight: received " ++ showsPrec 11 dummy "") 	Right b -> return b  liftLoc :: TH.Loc -> TH.Q TH.Exp@@ -335,47 +356,52 @@  assertAt :: IsAssertion assertion => TH.Loc -> Bool -> assertion -> Assertions () assertAt loc fatal assertion = do-	let Assertion io = toAssertion assertion-	result <- liftIO io+	result <- liftIO (runAssertion assertion) 	case result of 		AssertionPassed -> return () 		AssertionFailed err -> if fatal 			then dieAt loc err 			else addFailure (Just loc) err --- | Run an 'Assertion'. If the assertion fails, the test will immediately+-- | Check an assertion. If the assertion fails, the test will immediately -- fail. --+-- The assertion to check can be a boolean value, an 'Assertion', or an IO+-- action returning one of the above.+-- -- 'assert' is a Template Haskell macro, to retain the source-file location -- from which it was used. Its effective type is: -- -- @ -- $assert :: 'IsAssertion' assertion => assertion -> 'Assertions' () -- @-assert :: TH.Q TH.Exp -- :: IsAssertion assertion => assertion -> Assertions ()+assert :: TH.Q TH.Exp assert = do 	loc <- TH.location 	let qloc = liftLoc loc 	[| assertAt $qloc True |] --- | Run an 'Assertion'. If the assertion fails, the test will continue to--- run until it finishes (or until an 'assert' fails).+-- | Check an assertion. If the assertion fails, the test will continue to+-- run until it finishes, a call to 'assert' fails, or the test runs 'die'. --+-- The assertion to check can be a boolean value, an 'Assertion', or an IO+-- action returning one of the above.+-- -- 'expect' is a Template Haskell macro, to retain the source-file location -- from which it was used. Its effective type is: -- -- @ -- $expect :: 'IsAssertion' assertion => assertion -> 'Assertions' () -- @-expect :: TH.Q TH.Exp -- :: IsAssertion assertion => assertion -> Assertions ()+expect :: TH.Q TH.Exp expect = do 	loc <- TH.location 	let qloc = liftLoc loc 	[| assertAt $qloc False |]  pure :: Bool -> String -> Assertion-pure True _ = Assertion (return AssertionPassed)-pure False err = Assertion (return (AssertionFailed (Data.Text.pack err)))+pure True  _   = assertionPassed+pure False err = AssertionFailed err  -- | Assert that two values are equal. equal :: (Show a, Eq a) => a -> a -> Assertion@@ -398,45 +424,45 @@ just x = pure (isJust x) ("just: received Nothing")  -- | Assert that some value is @Nothing@.-nothing :: Maybe a -> Assertion-nothing x = pure (isNothing x) ("nothing: received Just")+nothing :: Show a => Maybe a -> Assertion+nothing x = pure (isNothing x) ("nothing: received " ++ showsPrec 11 x "")  -- | Assert that some value is @Left@.-left :: Either a b -> Assertion-left x = pure (isLeft x) ("left: received Right") where-	isLeft (Left _) = True-	isLeft (Right _) = False+left :: Show b => Either a b -> Assertion+left (Left _) = assertionPassed+left (Right b) = assertionFailed ("left: received " ++ showsPrec 11 dummy "") where+	dummy = Right b `asTypeOf` Left ()  -- | Assert that some value is @Right@.-right :: Either a b -> Assertion-right x = pure (isRight x) ("right: received Left") where-	isRight (Left _) = False-	isRight (Right _) = True+right :: Show a => Either a b -> Assertion+right (Right _) = assertionPassed+right (Left a) = assertionFailed ("right: received " ++ showsPrec 11 dummy "") where+	dummy = Left a `asTypeOf` Right ()  -- | Assert that some computation throws an exception matching the provided -- predicate. This is mostly useful for exception types which do not have an -- instance for @Eq@, such as @'Control.Exception.ErrorCall'@.-throws :: Exception err => (err -> Bool) -> IO a -> Assertion-throws p io = Assertion (do+throws :: Exception err => (err -> Bool) -> IO a -> IO Assertion+throws p io = do 	either_exc <- Control.Exception.try io-	return (case either_exc of+	return $ case either_exc of 		Left exc -> if p exc-			then AssertionPassed-			else AssertionFailed (Data.Text.pack ("throws: exception " ++ show exc ++ " did not match predicate"))-		Right _ -> AssertionFailed (Data.Text.pack ("throws: no exception thrown"))))+			then assertionPassed+			else assertionFailed ("throws: exception " ++ show exc ++ " did not match predicate")+		Right _ -> assertionFailed "throws: no exception thrown"  -- | Assert that some computation throws an exception equal to the given -- exception. This is better than just checking that the correct type was -- thrown, because the test can also verify the exception contains the correct -- information.-throwsEq :: (Eq err, Exception err, Show err) => err -> IO a -> Assertion-throwsEq expected io = Assertion (do+throwsEq :: (Eq err, Exception err, Show err) => err -> IO a -> IO Assertion+throwsEq expected io = do 	either_exc <- Control.Exception.try io-	return (case either_exc of+	return $ case either_exc of 		Left exc -> if exc == expected-			then AssertionPassed-			else AssertionFailed (Data.Text.pack ("throwsEq: exception " ++ show exc ++ " is not equal to " ++ show expected))-		Right _ -> AssertionFailed (Data.Text.pack ("throwsEq: no exception thrown"))))+			then assertionPassed+			else assertionFailed ("throwsEq: exception " ++ show exc ++ " is not equal to " ++ show expected)+		Right _ -> assertionFailed "throwsEq: no exception thrown"  -- | Assert a value is greater than another. greater :: (Ord a, Show a) => a -> a -> Assertion@@ -484,7 +510,7 @@ 	 	errorMsg diff = label ++ ": items differ\n" ++ diff --- | Class for types which can be treated as text.+-- | Class for types which can be treated as text; see 'equalLines'. class IsText a where 	toLines :: a -> [a] 	unpack :: a -> String@@ -515,8 +541,16 @@ -- to check line-by-line, so the error message will be easier to read on -- large inputs. equalLines :: (Ord a, IsText a) => a -> a -> Assertion-equalLines x y = checkDiff (toLines x) (toLines y) where-	checkDiff xs ys = case checkItems (Patience.diff xs ys) of+equalLines x y = checkLinesDiff "equalLines" (toLines x) (toLines y)++-- | Variant of 'equalLines' which allows a user-specified line-splitting+-- predicate.+equalLinesWith :: Ord a => (a -> [String]) -> a -> a -> Assertion+equalLinesWith toStringLines x y = checkLinesDiff "equalLinesWith" (toStringLines x) (toStringLines y)++checkLinesDiff :: (Ord a, IsText a) => String -> [a] -> [a] -> Assertion+checkLinesDiff label = go where+	go xs ys = case checkItems (Patience.diff xs ys) of 		(same, diff) -> pure same diff 	 	checkItems diffItems = case foldl' checkItem (True, []) diffItems of@@ -527,4 +561,4 @@ 		Patience.New t -> (False, ("\t+ " ++ unpack t) : acc) 		Patience.Both t _-> (same, ("\t  " ++ unpack t) : acc) 	-	errorMsg diff = "equalLines: lines differ\n" ++ diff+	errorMsg diff = label ++ ": lines differ\n" ++ diff
lib/Test/Chell/Main.hs view
@@ -1,4 +1,3 @@-{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE TemplateHaskell #-}  module Test.Chell.Main@@ -9,14 +8,10 @@ import           Control.Monad.Trans.Class (lift) import qualified Control.Monad.Trans.State as State import qualified Control.Monad.Trans.Writer as Writer-import qualified Data.ByteString import           Data.Char (ord)-import qualified Data.Text-import           Data.Text (Text, pack)-import qualified Data.Text.Encoding-import qualified Data.Text.IO+import           Data.List (isPrefixOf) import           System.Exit (exitSuccess, exitFailure)-import qualified System.IO as IO+import           System.IO (hPutStr, hPutStrLn, hIsTerminalDevice, stderr, stdout, withBinaryFile, IOMode(..)) import           System.Random (randomIO) import           Text.Printf (printf) @@ -35,11 +30,11 @@ 		, optionDescription = "Print more output" 		}) 	-	pathOption "optXmlReport" "xml-report" ""+	pathOption "optXmlReport" "xml-report" Path.empty 		"Write a parsable report to a given path, in XML."-	pathOption "optJsonReport" "json-report" ""+	pathOption "optJsonReport" "json-report" Path.empty 		"Write a parsable report to a given path, in JSON."-	pathOption "optTextReport" "text-report" ""+	pathOption "optTextReport" "text-report" Path.empty 		"Write a human-readable report to a given path." 	 	option "optSeed" (\o -> o@@ -65,10 +60,11 @@ 			] 		, optionDefault = "auto" 		, optionDescription = "Whether to enable color ('always', 'auto', or 'never')."-		}))+		})+	)  -- | A simple default main function, which runs a list of tests and logs--- statistics to stderr.+-- statistics to stdout. defaultMain :: [Suite] -> IO () defaultMain suites = runCommand $ \opts args -> do 	-- validate/sanitize test options@@ -79,7 +75,7 @@ 		Nothing -> return Nothing 		Just t -> if toInteger t * 1000 > toInteger (maxBound :: Int) 			then do-				IO.hPutStrLn IO.stderr "Test.Chell.defaultMain: Ignoring --timeout because it is too large."+				hPutStrLn stderr "Test.Chell.defaultMain: Ignoring --timeout because it is too large." 				return Nothing 			else return (Just t) 	let testOptions = defaultTestOptions@@ -98,7 +94,7 @@ 		ColorModeNever -> return (plainOutput (optVerbose opts)) 		ColorModeAlways -> return (colorOutput (optVerbose opts)) 		ColorModeAuto -> do-			isTerm <- IO.hIsTerminalDevice IO.stdout+			isTerm <- hIsTerminalDevice stdout 			return $ if isTerm 				then colorOutput (optVerbose opts) 				else plainOutput (optVerbose opts)@@ -113,28 +109,25 @@ 	-- generate reports 	let reports = getReports opts 	forM_ reports $ \(path, fmt, toText) ->-		IO.withBinaryFile path IO.WriteMode $ \h -> do-			let text = toText results-			let bytes = Data.Text.Encoding.encodeUtf8 text+		withBinaryFile path WriteMode $ \h -> do 			when (optVerbose opts) $ do 				putStrLn ("Writing " ++ fmt ++ " report to " ++ show path)-			Data.ByteString.hPut h bytes+			hPutStr h (toText results) 	 	let stats = resultStatistics results 	let (_, _, failed, aborted) = stats-	Data.Text.IO.putStrLn (formatResultStatistics stats)+	putStrLn (formatResultStatistics stats) 	 	if failed == 0 && aborted == 0 		then exitSuccess 		else exitFailure  matchesFilter :: [String] -> Test -> Bool-matchesFilter strFilters = check where-	filters = map Data.Text.pack strFilters+matchesFilter filters = check where 	check t = any (matchName (testName t)) filters-	matchName name f = f == name || Data.Text.isPrefixOf (Data.Text.append f ".") name+	matchName name f = f == name || isPrefixOf (f ++ ".") name -type Report = [(Test, TestResult)] -> Text+type Report = [(Test, TestResult)] -> String  getReports :: MainOptions -> [(String, String, Report)] getReports opts = concat [xml, json, text] where@@ -148,7 +141,7 @@ 		p | Path.null p -> [] 		p -> [(Path.encodeString p, "text", textReport)] -jsonReport :: [(Test, TestResult)] -> Text+jsonReport :: [(Test, TestResult)] -> String jsonReport results = Writer.execWriter writer where 	tell = Writer.tell 	@@ -172,18 +165,21 @@ 			tell "{\"test\": \"" 			tell (escapeJSON (testName t)) 			tell "\", \"result\": \"failed\", \"failures\": ["-			commas fs $ \(Failure loc msg) -> do+			commas fs $ \f -> do 				tell "{\"message\": \""-				tell (escapeJSON msg)+				tell (escapeJSON (failureMessage f)) 				tell "\""-				case loc of-					Just loc' -> do+				case failureLocation f of+					Just loc -> do 						tell ", \"location\": {\"module\": \""-						tell (escapeJSON (locationModule loc'))+						tell (escapeJSON (locationModule loc)) 						tell "\", \"file\": \""-						tell (escapeJSON (locationFile loc'))-						tell "\", \"line\": "-						tell (pack (show (locationLine loc')))+						tell (escapeJSON (locationFile loc))+						case locationLine loc of+							Just line -> do+								tell "\", \"line\": "+								tell (show line)+							Nothing -> tell "\"" 						tell "}" 					Nothing -> return () 				tell "}"@@ -198,12 +194,13 @@ 			tell "\"}" 			tellNotes notes 			tell "}"+		_ -> return () 	-	escapeJSON = Data.Text.concatMap (\c -> case c of+	escapeJSON = concatMap (\c -> case c of 		'"' -> "\\\"" 		'\\' -> "\\\\"-		_ | ord c <= 0x1F -> pack (printf "\\u%04X" (ord c))-		_ -> Data.Text.singleton c)+		_ | ord c <= 0x1F -> printf "\\u%04X" (ord c)+		_ -> [c]) 	 	tellNotes notes = do 		tell ", \"notes\": ["@@ -225,7 +222,7 @@ 		State.put True 		lift (block x) -xmlReport :: [(Test, TestResult)] -> Text+xmlReport :: [(Test, TestResult)] -> String xmlReport results = Writer.execWriter writer where 	tell = Writer.tell 	@@ -250,18 +247,21 @@ 			tell "\t<test-run test='" 			tell (escapeXML (testName t)) 			tell "' result='failed'>\n"-			forM_ fs $ \(Failure loc msg) -> do+			forM_ fs $ \f -> do 				tell "\t\t<failure message='"-				tell (escapeXML msg)-				case loc of-					Just loc' -> do+				tell (escapeXML (failureMessage f))+				case failureLocation f of+					Just loc -> do 						tell "'>\n" 						tell "\t\t\t<location module='"-						tell (escapeXML (locationModule loc'))+						tell (escapeXML (locationModule loc)) 						tell "' file='"-						tell (escapeXML (locationFile loc'))-						tell "' line='"-						tell (pack (show (locationLine loc')))+						tell (escapeXML (locationFile loc))+						case locationLine loc of+							Just line -> do+								tell "' line='"+								tell (show line)+							Nothing -> return () 						tell "'/>\n" 						tell "\t\t</failure>\n" 					Nothing -> tell "'/>\n"@@ -276,14 +276,15 @@ 			tell "'/>\n" 			tellNotes notes 			tell "\t</test-run>\n"+		_ -> return () 	-	escapeXML = Data.Text.concatMap (\c -> case c of+	escapeXML = concatMap (\c -> case c of 		'&' -> "&amp;" 		'<' -> "&lt;" 		'>' -> "&gt;" 		'"' -> "&quot;" 		'\'' -> "&apos;"-		_ -> Data.Text.singleton c)+		_ -> [c]) 	 	tellNotes notes = forM_ notes $ \(key, value) -> do 		tell "\t\t<note key=\""@@ -292,7 +293,7 @@ 		tell (escapeXML value) 		tell "\"/>\n" -textReport :: [(Test, TestResult)] -> Text+textReport :: [(Test, TestResult)] -> String textReport results = Writer.execWriter writer where 	tell = Writer.tell 	@@ -303,7 +304,7 @@ 	 	tellResult (t, result) = case result of 		TestPassed notes -> do-			tell (Data.Text.replicate 70 "=")+			tell (replicate 70 '=') 			tell "\n" 			tell "PASSED: " 			tell (testName t)@@ -311,41 +312,45 @@ 			tellNotes notes 			tell "\n\n" 		TestSkipped -> do-			tell (Data.Text.replicate 70 "=")+			tell (replicate 70 '=') 			tell "\n" 			tell "SKIPPED: " 			tell (testName t) 			tell "\n\n" 		TestFailed notes fs -> do-			tell (Data.Text.replicate 70 "=")+			tell (replicate 70 '=') 			tell "\n" 			tell "FAILED: " 			tell (testName t) 			tell "\n" 			tellNotes notes-			tell (Data.Text.replicate 70 "-")+			tell (replicate 70 '-') 			tell "\n"-			forM_ fs $ \(Failure loc msg) -> do-				case loc of-					Just loc' -> do-						tell (locationFile loc')-						tell ":"-						tell (pack (show (locationLine loc')))+			forM_ fs $ \f -> do+				case failureLocation f of+					Just loc -> do+						tell (locationFile loc)+						case locationLine loc of+							Just line -> do+								tell ":"+								tell (show line)+							Nothing -> return () 						tell "\n" 					Nothing -> return ()-				tell msg+				tell (failureMessage f) 				tell "\n\n" 		TestAborted notes msg -> do-			tell (Data.Text.replicate 70 "=")+			tell (replicate 70 '=') 			tell "\n" 			tell "ABORTED: " 			tell (testName t) 			tell "\n" 			tellNotes notes-			tell (Data.Text.replicate 70 "-")+			tell (replicate 70 '-') 			tell "\n" 			tell msg 			tell "\n\n"+		_ -> return () 	 	tellNotes notes = forM_ notes $ \(key, value) -> do 		tell key@@ -353,7 +358,7 @@ 		tell value 		tell "\n" -formatResultStatistics :: (Integer, Integer, Integer, Integer) -> Text+formatResultStatistics :: (Integer, Integer, Integer, Integer) -> String formatResultStatistics stats = Writer.execWriter writer where 	writer = do 		let (passed, skipped, failed, aborted) = stats@@ -361,8 +366,8 @@ 			then Writer.tell "PASS: " 			else Writer.tell "FAIL: " 		let putNum comma n what = Writer.tell $ if n == 1-			then pack (comma ++ "1 test " ++ what)-			else pack (comma ++ show n ++ " tests " ++ what)+			then comma ++ "1 test " ++ what+			else comma ++ show n ++ " tests " ++ what 		 		let total = sum [passed, skipped, failed, aborted] 		putNum "" total "run"@@ -378,3 +383,4 @@ 		TestSkipped{} -> State.modify (\(p, s, f, a) -> (p, s+1, f, a)) 		TestFailed{} ->  State.modify (\(p, s, f, a) -> (p, s, f+1, a)) 		TestAborted{} -> State.modify (\(p, s, f, a) -> (p, s, f, a+1))+		_ -> return ()
lib/Test/Chell/Output.hs view
@@ -1,14 +1,17 @@ {-# LANGUAGE CPP #-}  module Test.Chell.Output-	( Output(..)+	( Output+	, outputStart+	, outputResult+	+	, ColorMode(..)+	 	, plainOutput 	, colorOutput 	) where  import           Control.Monad (forM_, unless, when)-import           Data.Text (Text)-import qualified Data.Text.IO  #ifdef MIN_VERSION_ansi_terminal import qualified System.Console.ANSI as AnsiTerminal@@ -30,30 +33,37 @@ plainOutputStart :: Bool -> Test -> IO () plainOutputStart v t = when v $ do 	putStr "[ RUN   ] "-	Data.Text.IO.putStrLn (testName t)+	putStrLn (testName t)  plainOutputResult :: Bool -> Test -> TestResult -> IO () plainOutputResult v t (TestPassed _) = when v $ do 	putStr "[ PASS  ] "-	Data.Text.IO.putStrLn (testName t)+	putStrLn (testName t) 	putStrLn "" plainOutputResult v t TestSkipped = when v $ do 	putStr "[ SKIP  ] "-	Data.Text.IO.putStrLn (testName t)+	putStrLn (testName t) 	putStrLn "" plainOutputResult _ t (TestFailed notes fs) = do 	putStr "[ FAIL  ] "-	Data.Text.IO.putStrLn (testName t)+	putStrLn (testName t) 	printNotes notes 	printFailures fs plainOutputResult _ t (TestAborted notes msg) = do 	putStr "[ ABORT ] "-	Data.Text.IO.putStrLn (testName t)+	putStrLn (testName t) 	printNotes notes 	putStr "  "-	Data.Text.IO.putStr msg+	putStr msg 	putStrLn "\n"+plainOutputResult _ _ _ = return () +data ColorMode+	= ColorModeAuto+	| ColorModeAlways+	| ColorModeNever+	deriving (Enum)+ colorOutput :: Bool -> Output #ifndef MIN_VERSION_ansi_terminal colorOutput = plainOutput@@ -66,7 +76,7 @@ colorOutputStart :: Bool -> Test -> IO () colorOutputStart v t = when v $ do 	putStr "[ RUN   ] "-	Data.Text.IO.putStrLn (testName t)+	putStrLn (testName t)  colorOutputResult :: Bool -> Test -> TestResult -> IO () colorOutputResult v t (TestPassed _) = when v $ do@@ -79,7 +89,7 @@ 		[ AnsiTerminal.Reset 		] 	putStr "  ] "-	Data.Text.IO.putStrLn (testName t)+	putStrLn (testName t) 	putStrLn "" colorOutputResult v t TestSkipped = when v $ do 	putStr "[ "@@ -91,7 +101,7 @@ 		[ AnsiTerminal.Reset 		] 	putStr "  ] "-	Data.Text.IO.putStrLn (testName t)+	putStrLn (testName t) 	putStrLn "" colorOutputResult _ t (TestFailed notes fs) = do 	putStr "[ "@@ -103,7 +113,7 @@ 		[ AnsiTerminal.Reset 		] 	putStr "  ] "-	Data.Text.IO.putStrLn (testName t)+	putStrLn (testName t) 	printNotes notes 	printFailures fs colorOutputResult _ t (TestAborted notes msg) = do@@ -116,31 +126,34 @@ 		[ AnsiTerminal.Reset 		] 	putStr " ] "-	Data.Text.IO.putStrLn (testName t)+	putStrLn (testName t) 	printNotes notes 	putStr "  "-	Data.Text.IO.putStr msg+	putStr msg 	putStrLn "\n"+colorOutputResult _ _ _ = return () #endif -printNotes :: [(Text, Text)] -> IO ()+printNotes :: [(String, String)] -> IO () printNotes notes = unless (null notes) $ do 	forM_ notes $ \(key, value) -> do 		putStr "  note: "-		Data.Text.IO.putStr key+		putStr key 		putStr "="-		Data.Text.IO.putStrLn value+		putStrLn value 	putStrLn ""  printFailures :: [Failure] -> IO ()-printFailures fs = forM_ fs $ \(Failure loc msg) -> do+printFailures fs = forM_ fs $ \f -> do 	putStr "  "-	case loc of-		Just loc' -> do-			Data.Text.IO.putStr (locationFile loc')+	case failureLocation f of+		Just loc -> do+			putStr (locationFile loc) 			putStr ":"-			putStrLn (show (locationLine loc'))+			case locationLine loc of+				Just line -> putStrLn (show line)+				Nothing -> putStrLn "" 		Nothing -> return () 	putStr "  "-	Data.Text.IO.putStr msg+	putStr (failureMessage f) 	putStrLn "\n"
lib/Test/Chell/Types.hs view
@@ -1,18 +1,62 @@-{-# LANGUAGE OverloadedStrings #-}--module Test.Chell.Types where+module Test.Chell.Types+	( Test+	, test+	, testName+	+	, TestOptions+	, defaultTestOptions+	, testOptionSeed+	, testOptionTimeout+	+	, TestResult(TestPassed, TestSkipped, TestFailed, TestAborted)+	+	, Failure+	, failure+	, failureLocation+	, failureMessage+	+	, Location+	, location+	, locationFile+	, locationModule+	, locationLine+	+	, Suite+	, suite+	, suiteName+	, suiteTests+	+	, BuildSuite+	, SuiteOrTest+	, skipIf+	, skipWhen+	+	, runTest+	+	, handleJankyIO+	) where  import qualified Control.Exception import           Control.Exception (SomeException, Handler(..), catches, throwIO)-import qualified Data.Text-import           Data.Text (Text) import           System.Timeout (timeout) -data Test = Test Text (TestOptions -> IO TestResult)+-- | A 'Test' is, essentially, an IO action that returns a 'TestResult'. Tests+-- are aggregated into suites (see 'Suite').+data Test = Test String (TestOptions -> IO TestResult)  instance Show Test where-	show (Test name _) = "<Test " ++ show name ++ ">"+	showsPrec d (Test name _) = showParen (d > 10) (showString "Test " . shows name) +-- | Define a test, with the given name and implementation.+test :: String -> (TestOptions -> IO TestResult) -> Test+test = Test++-- | Get the name a test was given when it was defined; see 'test'.+testName :: Test -> String+testName (Test name _) = name++-- | Test options are passed to each test, and control details about how the+-- test should be run. data TestOptions = TestOptions 	{ 	@@ -22,6 +66,9 @@ 	-- 	-- When using 'defaultMain', users may specify a seed using the 	-- @--seed@ command-line option.+	--+	-- 'testOptionSeed' is a field accessor, and can be used to update+	-- a 'TestOptions' value. 	  testOptionSeed :: Int 	 	-- | An optional timeout, in millseconds. Tests which run longer than@@ -29,40 +76,250 @@ 	-- 	-- When using 'defaultMain', users may specify a timeout using the 	-- @--timeout@ command-line option.+	--+	-- 'testOptionTimeout' is a field accessor, and can be used to update+	-- a 'TestOptions' value. 	, testOptionTimeout :: Maybe Int 	} 	deriving (Show, Eq)  -- | Default test options. ----- @---'testOptionSeed' defaultTestOptions = 0---'testOptionTimeout' defaultTestOptions = Nothing--- @------ Since: 0.2.3+-- >$ ghci+-- >Prelude> import Test.Chell+-- >+-- >Test.Chell> testOptionSeed defaultTestOptions+-- >0+-- >+-- >Test.Chell> testOptionTimeout defaultTestOptions+-- >Nothing defaultTestOptions :: TestOptions defaultTestOptions = TestOptions 	{ testOptionSeed = 0 	, testOptionTimeout = Nothing 	} --- | @testName (Test name _) = name@-testName :: Test -> Text-testName (Test name _) = name+-- | The result of running a test.+--+-- To support future extensions to the testing API, any users of this module+-- who pattern-match against the 'TestResult' constructors should include a+-- default case. If no default case is provided, a warning will be issued.+data TestResult+	-- | The test passed, and generated the given notes.+	= TestPassed [(String, String)]+	+	-- | The test did not run, because it was skipped with 'skipIf'+	-- or 'skipWhen'.+	| TestSkipped+	+	-- | The test failed, generating the given notes and failures.+	| TestFailed [(String, String)] [Failure]+	+	-- | The test aborted with an error message, and generated the given+	-- notes.+	| TestAborted [(String, String)] String+	+	-- Not exported; used to generate GHC warnings for users who don't+	-- provide a default case.+	| TestResultCaseMustHaveDefault+	deriving (Show, Eq) +-- | Contains details about a test failure.+data Failure = Failure+	{+	-- | If given, the location of the failing assertion, expectation,+	-- etc.+	--+	-- 'failureLocation' is a field accessor, and can be used to update+	-- a 'Failure' value.+	  failureLocation :: Maybe Location+	+	-- | If given, a message which explains why the test failed.+	--+	-- 'failureMessage' is a field accessor, and can be used to update+	-- a 'Failure' value.+	, failureMessage :: String+	}+	deriving (Show, Eq)++-- | An empty 'Failure'; use the field accessors to populate this value.+failure :: Failure+failure = Failure Nothing ""++-- | Contains details about a location in the test source file.+data Location = Location+	{+	-- | A path to a source file, or empty if not provided.+	--+	-- 'locationFile' is a field accessor, and can be used to update+	-- a 'Location' value.+	  locationFile :: String+	+	-- | A Haskell module name, or empty if not provided.+	--+	-- 'locationModule' is a field accessor, and can be used to update+	-- a 'Location' value.+	, locationModule :: String+	+	-- | A line number, or Nothing if not provided.+	--+	-- 'locationLine' is a field accessor, and can be used to update+	-- a 'Location' value.+	, locationLine :: Maybe Integer+	}+	deriving (Show, Eq)++-- | An empty 'Location'; use the field accessors to populate this value.+location :: Location+location = Location "" "" Nothing++-- | A suite is a node in a hierarchy of tests, similar to a directory in the+-- filesystem. Each suite has a name and a list of children, which are either+-- suites or tests.+data Suite = Suite String [SOT]+	deriving (Show)++data SOT+	= SOT_Suite Suite+	| SOT_Test Test++instance Show SOT where+	showsPrec d (SOT_Test t) = showsPrec d t+	showsPrec d (SOT_Suite s) = showsPrec d s++-- | See 'suite'.+class BuildSuite a where+	addChildren :: Suite -> a++instance BuildSuite Suite where+	addChildren (Suite name cs) = Suite name (reverse cs)++-- | See 'suite'.+class SuiteOrTest a where+	toSOT :: a -> SOT+	skipIf_ :: Bool -> a -> a+	skipWhen_ :: IO Bool -> a -> a++instance SuiteOrTest Suite where+	toSOT = SOT_Suite+	skipIf_ skip s@(Suite name children) = if skip+		then Suite name (map skipSOT children)+		else s+	skipWhen_ p (Suite name children) = Suite name (map (skipWhenSOT p) children)++instance SuiteOrTest Test where+	toSOT = SOT_Test+	skipIf_ skip t@(Test name _) = if skip+		then Test name (\_ -> return TestSkipped)+		else t+	skipWhen_ p (Test name io) = Test name (\opts -> do+		skip <- p+		if skip then return TestSkipped else io opts)++skipSOT :: SOT -> SOT+skipSOT (SOT_Test (Test name _)) = SOT_Test (Test name (\_ -> return TestSkipped))+skipSOT (SOT_Suite (Suite name cs)) = SOT_Suite (Suite name (map skipSOT cs))++skipWhenSOT :: IO Bool -> SOT -> SOT+skipWhenSOT p (SOT_Suite s) = SOT_Suite (skipWhen_ p s)+skipWhenSOT p (SOT_Test t) = SOT_Test (skipWhen_ p t)++-- | Conditionally skip tests. Use this to avoid commenting out tests+-- which are currently broken, or do not work on the current platform.+--+-- @+--tests :: Suite+--tests = 'suite' \"tests\"+--    test_Foo+--    ('skipIf' builtOnUnix test_WindowsSpecific)+--    test_Bar+-- @+--+skipIf :: SuiteOrTest a => Bool -> a -> a+skipIf = skipIf_++-- | Conditionally skip tests, depending on the result of a runtime check. The+-- predicate is checked before each test is started.+--+-- @+--tests :: Suite+--tests = 'suite' \"tests\"+--    test_Foo+--    ('skipWhen' noNetwork test_PingGoogle)+--    test_Bar+-- @+skipWhen :: SuiteOrTest a => IO Bool -> a -> a+skipWhen = skipWhen_++instance (SuiteOrTest t, BuildSuite s) => BuildSuite (t -> s) where+	addChildren (Suite name cs) = \b -> addChildren (Suite name (toSOT b : cs))++-- | Define a new 'Suite', with the given name and children.+--+-- The type of this function allows any number of children to be added,+-- without requiring them to be homogenous types.+--+-- @+--test_Addition :: Test+--test_Subtraction :: Test+--test_Show :: Test+--+--tests_Math :: Suite+--tests_Math = 'suite' \"math\"+--    test_Addition+--    test_Subtraction+--+--tests_Prelude :: Suite+--tests_Prelude = 'suite' \"prelude\"+--    tests_Math+--    test_Show+-- @+suite :: BuildSuite a => String -> a+suite name = addChildren (Suite name [])++-- | Get a suite's name. Suite names may be any string, but are typically+-- plain ASCII so users can easily type them on the command line.+--+-- >$ ghci chell-example.hs+-- >Ok, modules loaded: Main.+-- >+-- >*Main> suiteName tests_Math+-- >"math"+suiteName :: Suite -> String+suiteName (Suite name _) = name++-- | Get the full list of tests contained within this 'Suite'. Each test is+-- given its full name within the test hierarchy, where names are separated+-- by periods.+--+-- >$ ghci chell-example.hs+-- >Ok, modules loaded: Main.+-- >+-- >*Main> suiteTests tests_Math+-- >[Test "math.addition",Test "math.subtraction"]+suiteTests :: Suite -> [Test]+suiteTests = go "" where+	prefixed prefix str = if null prefix+		then str+		else prefix ++ "." ++ str+	+	go prefix (Suite name children) = concatMap (step (prefixed prefix name)) children+	+	step prefix (SOT_Suite s) = go prefix s+	step prefix (SOT_Test (Test name io)) = [Test (prefixed prefix name) io]+ -- | Run a test, wrapped in error handlers. This will return 'TestAborted' if -- the test throws an exception or times out. runTest :: Test -> TestOptions -> IO TestResult runTest (Test _ io) options = handleJankyIO options (io options) (return []) -handleJankyIO :: TestOptions -> IO TestResult -> IO [(Text, Text)] -> IO TestResult+handleJankyIO :: TestOptions -> IO TestResult -> IO [(String, String)] -> IO TestResult handleJankyIO opts getResult getNotes = do 	let withTimeout = case testOptionTimeout opts of 		Just time -> timeout (time * 1000) 		Nothing -> fmap Just 	-	let hitTimeout = Data.Text.pack str where+	let hitTimeout = str where 		str = "Test timed out after " ++ show time ++ " milliseconds" 		Just time = testOptionTimeout opts 	@@ -74,7 +331,7 @@ 			return (TestAborted notes hitTimeout) 		Just (Left err) -> do 			notes <- getNotes-			return (TestAborted notes (Data.Text.pack err))+			return (TestAborted notes err)  try :: IO a -> IO (Either String a) try io = catches (fmap Right io) [Handler handleAsync, Handler handleExc] where@@ -83,57 +340,3 @@ 	 	handleExc :: SomeException -> IO (Either String a) 	handleExc exc = return (Left ("Test aborted due to exception: " ++ show exc))--data TestResult-	= TestPassed [(Text, Text)]-	| TestSkipped-	| TestFailed [(Text, Text)] [Failure]-	| TestAborted [(Text, Text)] Text-	deriving (Show, Eq)--data Failure = Failure (Maybe Location) Text-	deriving (Show, Eq)--data Location = Location-	{ locationFile :: Text-	, locationModule :: Text-	, locationLine :: Integer-	}-	deriving (Show, Eq)--data ColorMode-	= ColorModeAuto-	| ColorModeAlways-	| ColorModeNever-	deriving (Enum)---- | A tree of 'Test's; use the 'test' and 'suite' helper functions to build--- up a 'Suite'.-data Suite = Suite Text [Suite]-           | SuiteTest Test--instance Show Suite where-	show s = "<Suite " ++ show (suiteName s) ++ ">"--test :: Test -> Suite-test = SuiteTest--suite :: Text -> [Suite] -> Suite-suite = Suite--suiteName :: Suite -> Text-suiteName (Suite name _) = name-suiteName (SuiteTest t) = testName t---- | The full list of 'Test's contained within this 'Suite'. Each 'Test'--- is returned with its name modified to include the name of its parent--- 'Suite's.-suiteTests :: Suite -> [Test]-suiteTests = loop "" where-	loop prefix s = let-		name = if Data.Text.null prefix-			then suiteName s-			else Data.Text.concat [prefix, ".", suiteName s]-		in case s of-			Suite _ suites -> concatMap (loop name) suites-			SuiteTest (Test _ io) -> [Test name io]