tasty 0.3.1 → 0.4
raw patch · 12 files changed
+435/−263 lines, 12 files
Files
- Test/Tasty.hs +3/−5
- Test/Tasty/CmdLine.hs +34/−20
- Test/Tasty/Core.hs +2/−2
- Test/Tasty/CoreOptions.hs +11/−6
- Test/Tasty/Ingredients.hs +101/−0
- Test/Tasty/Ingredients/ConsoleReporter.hs +203/−0
- Test/Tasty/Ingredients/ListTests.hs +49/−0
- Test/Tasty/Options.hs +1/−2
- Test/Tasty/Run.hs +1/−21
- Test/Tasty/Runners.hs +25/−6
- Test/Tasty/UI.hs +0/−199
- tasty.cabal +5/−2
Test/Tasty.hs view
@@ -8,7 +8,7 @@ , testGroup -- * Running tests , defaultMain- , defaultMainWithRunner+ , defaultMainWithIngredients -- * Adjusting options -- | Normally options are specified on the command line. But you can -- also have different options for different subtrees in the same tree,@@ -20,13 +20,11 @@ import Test.Tasty.Core import Test.Tasty.Runners-import Test.Tasty.UI import Test.Tasty.Options --- | Parse the command line arguments and run the tests using the standard--- console runner+-- | Parse the command line arguments and run the tests defaultMain :: TestTree -> IO ()-defaultMain = defaultMainWithRunner runUI+defaultMain = defaultMainWithIngredients [listingTests, consoleTestReporter] -- | Locally adjust the option value for the given test subtree adjustOption :: IsOption v => (v -> v) -> TestTree -> TestTree
Test/Tasty/CmdLine.hs view
@@ -1,45 +1,59 @@ -- | Parsing options supplied on the command line {-# LANGUAGE ScopedTypeVariables #-} module Test.Tasty.CmdLine- ( treeOptionParser- , optionParser- , defaultMainWithRunner+ ( optionParser+ , suiteOptions+ , suiteOptionParser+ , defaultMainWithIngredients ) where import Options.Applicative import Data.Monoid import Data.Proxy-import Data.Tagged-import qualified Data.Map as Map-import Data.Typeable-import Control.Arrow import System.Exit + import Test.Tasty.Core import Test.Tasty.CoreOptions-import Test.Tasty.Run+import Test.Tasty.Ingredients import Test.Tasty.Options --- | Generate a command line parser for all the options relevant for this--- test tree. Also includes 'coreOptions'.-treeOptionParser :: TestTree -> Parser OptionSet-treeOptionParser = optionParser . (coreOptions ++) . getTreeOptions- -- | Generate a command line parser from a list of option descriptions optionParser :: [OptionDescription] -> Parser OptionSet optionParser = foldr addOption (pure mempty) where addOption :: OptionDescription -> Parser OptionSet -> Parser OptionSet- addOption (Option (px :: Proxy v)) p =+ addOption (Option (Proxy :: Proxy v)) p = setOption <$> (optionCLParser :: Parser v) <*> p +-- suiteOptions doesn't really belong here (since it's not CmdLine+-- specific), but I didn't want to create a new module just for it.++-- | All the options relevant for this test suite. This includes the+-- options for the test tree and ingredients, and the core options.+suiteOptions :: [Ingredient] -> TestTree -> [OptionDescription]+suiteOptions ins tree =+ coreOptions +++ ingredientsOptions ins +++ treeOptions tree++-- | The command line parser for the test suite+suiteOptionParser :: [Ingredient] -> TestTree -> Parser OptionSet+suiteOptionParser ins tree = optionParser $ suiteOptions ins tree+ -- | Parse the command line arguments and run the tests using the provided--- runner-defaultMainWithRunner :: Runner -> TestTree -> IO ()-defaultMainWithRunner runner testTree = do+-- ingredient list+defaultMainWithIngredients :: [Ingredient] -> TestTree -> IO ()+defaultMainWithIngredients ins testTree = do opts <- execParser $- info (helper <*> treeOptionParser testTree)+ info (helper <*> suiteOptionParser ins testTree) ( fullDesc <> header "Mmm... tasty test suite" )- ok <- execRunner runner opts testTree- if ok then exitSuccess else exitFailure++ case tryIngredients ins opts testTree of+ Nothing ->+ putStrLn+ "This doesn't taste right. Check your ingredients — did you forget a test reporter?"+ Just act -> do+ ok <- act+ if ok then exitSuccess else exitFailure
Test/Tasty/Core.hs view
@@ -127,8 +127,8 @@ AppMonoid f1 `mappend` AppMonoid f2 = AppMonoid $ f1 *> f2 -- | Get the list of options that are relevant for a given test tree-getTreeOptions :: TestTree -> [OptionDescription]-getTreeOptions =+treeOptions :: TestTree -> [OptionDescription]+treeOptions = Prelude.concat . Map.elems .
Test/Tasty/CoreOptions.hs view
@@ -1,4 +1,4 @@--- | Core options, i.e. the options used by Tasty itself+-- | Core options, i.e. the options used by tasty itself {-# LANGUAGE DeriveDataTypeable, GeneralizedNewtypeDeriving #-} module Test.Tasty.CoreOptions ( NumThreads(..)@@ -10,10 +10,15 @@ import Data.Proxy import Test.Tasty.Options-import Test.Tasty.Options import Test.Tasty.Patterns --- | Number of parallel threads to use for running tests+-- | Number of parallel threads to use for running tests.+--+-- Note that this is /not/ included in 'coreOptions'.+-- Instead, it's automatically included in the options for any+-- 'TestReporter' ingredient by 'ingredientOptions', because the way test+-- reporters are handled already involves parallelism. Other ingredients+-- may also choose to include this option. newtype NumThreads = NumThreads { getNumThreads :: Int } deriving (Eq, Ord, Num, Typeable) instance IsOption NumThreads where@@ -22,9 +27,9 @@ optionName = return "num-threads" optionHelp = return "Number of threads to use for tests execution" --- | The list of all core options+-- | The list of all core options, i.e. the options not specific to any+-- provider or ingredient, but to tasty itself. Currently only contains 'TestPattern'. coreOptions :: [OptionDescription] coreOptions =- [ Option (Proxy :: Proxy NumThreads)- , Option (Proxy :: Proxy TestPattern)+ [ Option (Proxy :: Proxy TestPattern) ]
+ Test/Tasty/Ingredients.hs view
@@ -0,0 +1,101 @@+module Test.Tasty.Ingredients+ ( Ingredient(..)+ , tryIngredients+ , ingredientOptions+ , ingredientsOptions+ ) 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.CoreOptions++-- | '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 '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. 'TestReporter', if it agrees to run,+-- automatically launches tests execution, and gets the 'StatusMap' which+-- it uses to report the progress and results to the user.+--+-- '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). 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'.+data Ingredient+ = TestReporter+ [OptionDescription]+ (OptionSet -> TestTree -> Maybe (StatusMap -> IO Bool))+ | TestManager+ [OptionDescription]+ (OptionSet -> TestTree -> Maybe (IO Bool))++-- | Try to run an 'Ingredient'.+--+-- If the ingredient refuses to run (usually based on the 'OptionSet'),+-- the function returns 'Nothing'.+--+-- For a 'TestReporter', this function automatically starts running the+-- tests in the background.+tryIngredient :: Ingredient -> OptionSet -> TestTree -> Maybe (IO Bool)+tryIngredient (TestReporter _ report) opts testTree = do -- Maybe monad+ reportFn <- report opts testTree+ return $ reportFn =<< launchTestTree opts testTree+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.+tryIngredients :: [Ingredient] -> OptionSet -> TestTree -> Maybe (IO Bool)+tryIngredients ins opts tree =+ msum $ map (\i -> tryIngredient i opts tree) ins++-- | 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+-- 'NumThreads'.+ingredientOptions :: Ingredient -> [OptionDescription]+ingredientOptions (TestReporter opts _) =+ Option (Proxy :: Proxy NumThreads) : opts+ingredientOptions (TestManager opts _) = opts++-- | Like 'ingredientOption', but folds over multiple ingredients.+ingredientsOptions :: [Ingredient] -> [OptionDescription]+ingredientsOptions = F.foldMap ingredientOptions
+ Test/Tasty/Ingredients/ConsoleReporter.hs view
@@ -0,0 +1,203 @@+{-# LANGUAGE TupleSections, CPP, ImplicitParams #-}+-- | Console reporter ingredient+module Test.Tasty.Ingredients.ConsoleReporter (consoleTestReporter) where++import Prelude hiding (fail)+import Control.Monad.State hiding (fail)+import Control.Concurrent.STM+import Control.Exception+import Test.Tasty.Core+import Test.Tasty.Run+import Test.Tasty.Ingredients+import Test.Tasty.Options+import Text.Printf+import qualified Data.IntMap as IntMap+import Data.Maybe+import Data.Monoid+import System.IO++#ifdef COLORS+import System.Console.ANSI+#endif++data RunnerState = RunnerState+ { ix :: !Int+ , nestedLevel :: !Int+ , failures :: !Int+ }++initialState :: RunnerState+initialState = RunnerState 0 0 0++type M = StateT RunnerState IO++indentSize :: Int+indentSize = 2++indent :: Int -> String+indent n = replicate (indentSize * n) ' '++-- handle multi-line result descriptions properly+formatDesc+ :: Int -- indent+ -> String+ -> String+formatDesc n desc =+ let+ -- remove all trailing linebreaks+ chomped = reverse . dropWhile (== '\n') . reverse $ desc++ multiline = '\n' `elem` chomped++ -- we add a leading linebreak to the description, to start it on a new+ -- line and add an indentation+ paddedDesc = flip concatMap chomped $ \c ->+ if c == '\n'+ then c : indent n+ else [c]+ in+ if multiline+ then paddedDesc+ else chomped++data Maximum a+ = Maximum a+ | MinusInfinity++instance Ord a => Monoid (Maximum a) where+ mempty = MinusInfinity++ Maximum a `mappend` Maximum b = Maximum (a `max` b)+ MinusInfinity `mappend` a = a+ a `mappend` MinusInfinity = a++-- | Compute the amount of space needed to align "OK"s and "FAIL"s+computeAlignment :: OptionSet -> TestTree -> Int+computeAlignment opts =+ fromMonoid .+ foldTestTree+ (\_ name _ level -> Maximum (length name + level))+ (\_ m -> m . (+ indentSize))+ opts+ where+ fromMonoid m =+ case m 0 of+ MinusInfinity -> 0+ Maximum x -> x++-- | A simple console UI+consoleTestReporter :: Ingredient+-- We fold the test tree using (AppMonoid m, Any) monoid.+--+-- The 'Any' part is needed to know whether a group is empty, in which case+-- we shouldn't display it.+consoleTestReporter = TestReporter [] $ \opts tree -> Just $ \smap -> do+ isTerm <- hIsTerminalDevice stdout++ let+ ?colors = isTerm++ let+ alignment = computeAlignment opts tree++ runSingleTest+ :: (IsTest t, ?colors :: Bool)+ => IntMap.IntMap (TVar Status)+ -> OptionSet -> TestName -> t -> (AppMonoid M, Any)+ runSingleTest smap _opts name _test = (, Any True) $ AppMonoid $ do+ st@RunnerState { ix = ix, nestedLevel = level } <- get+ let+ statusVar =+ fromMaybe (error "internal error: index out of bounds") $+ IntMap.lookup ix smap++ -- Print the test name before waiting for the test. This is useful+ -- for long-running tests.+ liftIO $ printf "%s%s: %s" (indent level) name+ (replicate (alignment - indentSize * level - length name) ' ')++ (rOk, rDesc) <-+ liftIO $ atomically $ do+ status <- readTVar statusVar+ case status of+ Done r -> return $ (resultSuccessful r, resultDescription r)+ Exception e -> return (False, "Exception: " ++ show e)+ _ -> retry++ liftIO $+ if rOk+ then ok "OK\n"+ else fail "FAIL\n"++ when (not $ null rDesc) $+ liftIO $ (if rOk then infoOk else infoFail) $+ printf "%s%s\n" (indent $ level + 1) (formatDesc (level+1) rDesc)+ let+ ix' = ix+1+ updateFailures = if rOk then id else (+1)+ put $! st { ix = ix', failures = updateFailures (failures st) }++ runGroup :: TestName -> (AppMonoid M, Any) -> (AppMonoid M, Any)+ runGroup _ (_, Any False) = mempty+ runGroup name (AppMonoid act, nonEmpty) = (, nonEmpty) $ AppMonoid $ do+ st@RunnerState { nestedLevel = level } <- get+ liftIO $ printf "%s%s\n" (indent level) name+ put $! st { nestedLevel = level + 1 }+ act+ modify $ \st -> st { nestedLevel = level }++ hSetBuffering stdout NoBuffering++ -- Do not retain the reference to the tree more than necessary+ _ <- evaluate alignment++ st <-+ flip execStateT initialState $ getApp $ fst $+ foldTestTree+ (runSingleTest smap)+ runGroup+ opts+ tree++ printf "\n"++ case failures st of+ 0 -> do+ ok $ printf "All %d tests passed\n" (ix st)+ return True++ fs -> do+ fail $ printf "%d out of %d tests failed\n" fs (ix st)+ return False++-- (Potentially) colorful output+ok, fail, infoOk, infoFail :: (?colors :: Bool) => String -> IO ()+#ifdef COLORS+fail = output BoldIntensity Vivid Red+ok = output NormalIntensity Dull Green+infoOk = output NormalIntensity Dull White+infoFail = output NormalIntensity Dull Black++output+ :: (?colors :: Bool)+ => ConsoleIntensity+ -> ColorIntensity+ -> Color+ -> String+ -> IO ()+output bold intensity color str+ | ?colors =+ (do+ setSGR+ [ SetColor Foreground intensity color+ , SetConsoleIntensity bold+ ]+ putStr str+ ) `finally` setSGR []+ | otherwise = putStr str+#else+ok = putStr+fail = putStr+infoOk = putStr+infoFail = putStr+#endif
+ Test/Tasty/Ingredients/ListTests.hs view
@@ -0,0 +1,49 @@+{-# LANGUAGE GeneralizedNewtypeDeriving, DeriveDataTypeable #-}+module Test.Tasty.Ingredients.ListTests+ ( ListTests(..)+ , testsNames+ , listingTests+ ) where++import Options.Applicative+import Data.Typeable+import Data.Proxy+import Data.Tagged++import Test.Tasty.Core+import Test.Tasty.Options+import Test.Tasty.Ingredients++-- | This option, when set to 'True', specifies that we should run in the+-- «list tests» mode+newtype ListTests = ListTests Bool+ deriving (Eq, Ord, Typeable)+instance IsOption ListTests where+ defaultValue = ListTests False+ parseValue = fmap ListTests . safeRead+ optionName = return "list-tests"+ optionHelp = return "Do not run the tests; just print their names"+ optionCLParser =+ fmap ListTests $+ switch+ ( short 'l'+ <> long (untag (optionName :: Tagged ListTests String))+ <> help (untag (optionHelp :: Tagged ListTests String))+ )++-- | Obtain the list of all tests in the suite+testsNames :: OptionSet -> TestTree -> [TestName]+testsNames {- opts -} {- tree -} =+ foldTestTree+ (\_opts name _test -> [name])+ (\groupName names -> map ((groupName ++ "/") ++) names)++-- | The ingredient that provides the test listing functionality+listingTests :: Ingredient+listingTests = TestManager [Option (Proxy :: Proxy ListTests)] $+ \opts tree ->+ case lookupOption opts of+ ListTests False -> Nothing+ ListTests True -> Just $ do+ mapM_ putStrLn $ testsNames opts tree+ return True
Test/Tasty/Options.hs view
@@ -3,8 +3,7 @@ OverlappingInstances, FlexibleInstances, UndecidableInstances, TypeOperators #-} -- | Extensible options. They are used for provider-specific settings,--- runner-specific settings and core settings (number of threads, test--- pattern).+-- ingredient-specific settings and core settings (such as the test name pattern). module Test.Tasty.Options ( -- * IsOption class
Test/Tasty/Run.hs view
@@ -2,14 +2,10 @@ module Test.Tasty.Run ( Status(..) , StatusMap- , Runner- , execRunner , launchTestTree ) where import qualified Data.IntMap as IntMap-import Data.Maybe-import Data.Typeable import Control.Monad.State import Control.Concurrent.STM import Control.Exception@@ -43,19 +39,10 @@ -- | Mapping from test numbers (starting from 0) to their status variables. ----- This is what a runner uses to analyse and display progress, and to+-- This is what an ingredient uses to analyse and display progress, and to -- detect when tests finish. type StatusMap = IntMap.IntMap (TVar Status) --- | A 'Runner' is responsible for user interaction during the test run.------ It is provided with the 'StatusMap', so the tests are already launched--- and all it needs to do is notifying the user about the progress and--- then displaying the overall results in the end.------ The function's result should indicate whether all the tests passed.-type Runner = OptionSet -> TestTree -> StatusMap -> IO Bool- -- | Start executing a test executeTest :: ((Progress -> IO ()) -> IO Result)@@ -126,10 +113,3 @@ let NumThreads numTheads = lookupOption opts launchTests numTheads tmap return $ fmap snd smap---- | Execute a 'Runner'.------ This is a shortcut which runs 'launchTestTree' behind the scenes.-execRunner :: Runner -> OptionSet -> TestTree -> IO Bool-execRunner runner opts testTree =- runner opts testTree =<< launchTestTree opts testTree
Test/Tasty/Runners.hs view
@@ -4,13 +4,30 @@ -- * Working with the test tree TestTree(..) , foldTestTree+ -- * Ingredients+ , Ingredient(..)+ , tryIngredients+ , ingredientOptions+ , ingredientsOptions+ -- * Standard console ingredients+ -- ** Console test reporter+ , consoleTestReporter+ -- ** Tests list+ , listingTests+ , ListTests(..)+ , testsNames -- * Command line handling- , module Test.Tasty.CmdLine+ , optionParser+ , suiteOptionParser+ , defaultMainWithIngredients -- * Running tests- , module Test.Tasty.Run- , module Test.Tasty.UI- -- * Core options- , module Test.Tasty.CoreOptions+ , Status(..)+ , StatusMap+ , launchTestTree+ , NumThreads(..)+ -- * Options+ , suiteOptions+ , coreOptions -- ** Patterns , module Test.Tasty.Patterns )@@ -18,7 +35,9 @@ import Test.Tasty.Core import Test.Tasty.Run+import Test.Tasty.Ingredients import Test.Tasty.CoreOptions import Test.Tasty.Patterns import Test.Tasty.CmdLine-import Test.Tasty.UI+import Test.Tasty.Ingredients.ConsoleReporter+import Test.Tasty.Ingredients.ListTests
− Test/Tasty/UI.hs
@@ -1,199 +0,0 @@-{-# LANGUAGE TupleSections, CPP, ImplicitParams #-}--- | Console runner-module Test.Tasty.UI (runUI) where--import Prelude hiding (fail)-import Control.Monad.State hiding (fail)-import Control.Concurrent.STM-import Control.Exception-import Test.Tasty.Core-import Test.Tasty.Run-import Test.Tasty.Options-import Text.Printf-import qualified Data.IntMap as IntMap-import Data.Maybe-import Data.Monoid-import System.IO--#ifdef COLORS-import System.Console.ANSI-#endif--data RunnerState = RunnerState- { ix :: !Int- , nestedLevel :: !Int- , failures :: !Int- }--initialState :: RunnerState-initialState = RunnerState 0 0 0--type M = StateT RunnerState IO--indentSize :: Int-indentSize = 2--indent :: Int -> String-indent n = replicate (indentSize * n) ' '---- handle multi-line result descriptions properly-formatDesc- :: Int -- indent- -> String- -> String-formatDesc n desc =- let- -- remove all trailing linebreaks- chomped = reverse . dropWhile (== '\n') . reverse $ desc-- multiline = '\n' `elem` chomped-- -- we add a leading linebreak to the description, to start it on a new- -- line and add an indentation- paddedDesc = flip concatMap chomped $ \c ->- if c == '\n'- then c : indent n- else [c]- in- if multiline- then paddedDesc- else chomped--data Maximum a- = Maximum a- | MinusInfinity--instance Ord a => Monoid (Maximum a) where- mempty = MinusInfinity-- Maximum a `mappend` Maximum b = Maximum (a `max` b)- MinusInfinity `mappend` a = a- a `mappend` MinusInfinity = a---- | Compute the amount of space needed to align "OK"s and "FAIL"s-computeAlignment :: OptionSet -> TestTree -> Int-computeAlignment opts =- fromMonoid .- foldTestTree- (\_ name _ level -> Maximum (length name + level))- (\_ m -> m . (+ indentSize))- opts- where- fromMonoid m =- case m 0 of- MinusInfinity -> 0- Maximum x -> x---- | A simple console UI-runUI :: Runner--- We fold the test tree using (AppMonoid m, Any) monoid.------ The 'Any' part is needed to know whether a group is empty, in which case--- we shouldn't display it.-runUI opts tree smap = do- isTerm <- hIsTerminalDevice stdout-- let- ?colors = isTerm-- hSetBuffering stdout NoBuffering-- -- Do not retain the reference to the tree more than necessary- _ <- evaluate alignment-- st <-- flip execStateT initialState $ getApp $ fst $- foldTestTree- (runSingleTest smap)- runGroup- opts- tree-- printf "\n"-- case failures st of- 0 -> do- ok $ printf "All %d tests passed\n" (ix st)- return True-- fs -> do- fail $ printf "%d out of %d tests failed\n" fs (ix st)- return False-- where- alignment = computeAlignment opts tree-- runSingleTest- :: (IsTest t, ?colors :: Bool)- => IntMap.IntMap (TVar Status)- -> OptionSet -> TestName -> t -> (AppMonoid M, Any)- runSingleTest smap _opts name _test = (, Any True) $ AppMonoid $ do- st@RunnerState { ix = ix, nestedLevel = level } <- get- let- statusVar =- fromMaybe (error "internal error: index out of bounds") $- IntMap.lookup ix smap-- (rOk, rDesc) <-- liftIO $ atomically $ do- status <- readTVar statusVar- case status of- Done r -> return $ (resultSuccessful r, resultDescription r)- Exception e -> return (False, "Exception: " ++ show e)- _ -> retry-- liftIO $ printf "%s%s: %s" (indent level) name- (replicate (alignment - indentSize * level - length name) ' ')- liftIO $- if rOk- then ok "OK\n"- else fail "FAIL\n"-- when (not $ null rDesc) $- liftIO $ (if rOk then infoOk else infoFail) $- printf "%s%s\n" (indent $ level + 1) (formatDesc (level+1) rDesc)- let- ix' = ix+1- updateFailures = if rOk then id else (+1)- put $! st { ix = ix', failures = updateFailures (failures st) }-- runGroup :: TestName -> (AppMonoid M, Any) -> (AppMonoid M, Any)- runGroup _ (_, Any False) = mempty- runGroup name (AppMonoid act, nonEmpty) = (, nonEmpty) $ AppMonoid $ do- st@RunnerState { nestedLevel = level } <- get- liftIO $ printf "%s%s\n" (indent level) name- put $! st { nestedLevel = level + 1 }- act- modify $ \st -> st { nestedLevel = level }---- (Potentially) colorful output-ok, fail, infoOk, infoFail :: (?colors :: Bool) => String -> IO ()-#ifdef COLORS-fail = output BoldIntensity Vivid Red-ok = output NormalIntensity Dull Green-infoOk = output NormalIntensity Dull White-infoFail = output NormalIntensity Dull Black--output- :: (?colors :: Bool)- => ConsoleIntensity- -> ColorIntensity- -> Color- -> String- -> IO ()-output bold intensity color str- | ?colors =- (do- setSGR- [ SetColor Foreground intensity color- , SetConsoleIntensity bold- ]- putStr str- ) `finally` setSGR []- | otherwise = putStr str-#else-ok = putStr-fail = putStr-infoOk = putStr-infoFail = putStr-#endif
tasty.cabal view
@@ -2,7 +2,7 @@ -- see http://haskell.org/cabal/users-guide/ name: tasty-version: 0.3.1+version: 0.4 synopsis: Modern and extensible testing framework description: See <http://documentup.com/feuerbach/tasty> license: MIT@@ -35,8 +35,10 @@ Test.Tasty.CoreOptions, Test.Tasty.Patterns, Test.Tasty.Run,+ Test.Tasty.Ingredients, Test.Tasty.CmdLine,- Test.Tasty.UI+ Test.Tasty.Ingredients.ConsoleReporter+ Test.Tasty.Ingredients.ListTests build-depends: base ==4.*, stm >= 2.3,@@ -51,3 +53,4 @@ cpp-options: -DCOLORS -- hs-source-dirs: default-language: Haskell2010+ ghc-options: -Wall -fno-warn-unused-do-bind -fno-warn-name-shadowing