hspec-meta 2.7.8 → 2.9.0
raw patch · 41 files changed
+2175/−1196 lines, 41 filesPVP ok
version bump matches the API change (PVP)
API changes (from Hackage documentation)
+ Test.Hspec.Meta: aroundAll :: (ActionWith a -> IO ()) -> SpecWith a -> Spec
+ Test.Hspec.Meta: aroundAllWith :: forall a b. (ActionWith a -> ActionWith b) -> SpecWith a -> SpecWith b
+ Test.Hspec.Meta: aroundAll_ :: (IO () -> IO ()) -> SpecWith a -> SpecWith a
+ Test.Hspec.Meta: ignoreSubject :: SpecWith () -> SpecWith a
+ Test.Hspec.Meta: mapSubject :: (b -> a) -> SpecWith a -> SpecWith b
- Test.Hspec.Meta: afterAll :: ActionWith a -> SpecWith a -> SpecWith a
+ Test.Hspec.Meta: afterAll :: HasCallStack => ActionWith a -> SpecWith a -> SpecWith a
- Test.Hspec.Meta: afterAll_ :: IO () -> SpecWith a -> SpecWith a
+ Test.Hspec.Meta: afterAll_ :: HasCallStack => IO () -> SpecWith a -> SpecWith a
Files
- hspec-core/src/GetOpt/Declarative.hs +5/−0
- hspec-core/src/GetOpt/Declarative/Environment.hs +51/−0
- hspec-core/src/GetOpt/Declarative/Interpret.hs +97/−0
- hspec-core/src/GetOpt/Declarative/Types.hs +18/−0
- hspec-core/src/GetOpt/Declarative/Util.hs +47/−0
- hspec-core/src/Test/Hspec/Core/Clock.hs +8/−1
- hspec-core/src/Test/Hspec/Core/Compat.hs +50/−33
- hspec-core/src/Test/Hspec/Core/Config.hs +16/−7
- hspec-core/src/Test/Hspec/Core/Config/Definition.hs +290/−0
- hspec-core/src/Test/Hspec/Core/Config/Options.hs +56/−294
- hspec-core/src/Test/Hspec/Core/Config/Util.hs +0/−37
- hspec-core/src/Test/Hspec/Core/Example.hs +3/−1
- hspec-core/src/Test/Hspec/Core/Example/Location.hs +4/−1
- hspec-core/src/Test/Hspec/Core/FailureReport.hs +2/−2
- hspec-core/src/Test/Hspec/Core/Format.hs +86/−11
- hspec-core/src/Test/Hspec/Core/Formatters.hs +2/−278
- hspec-core/src/Test/Hspec/Core/Formatters/Diff.hs +0/−1
- hspec-core/src/Test/Hspec/Core/Formatters/Free.hs +0/−22
- hspec-core/src/Test/Hspec/Core/Formatters/Internal.hs +119/−91
- hspec-core/src/Test/Hspec/Core/Formatters/Monad.hs +0/−246
- hspec-core/src/Test/Hspec/Core/Formatters/V1.hs +349/−0
- hspec-core/src/Test/Hspec/Core/Formatters/V1/Free.hs +22/−0
- hspec-core/src/Test/Hspec/Core/Formatters/V1/Monad.hs +258/−0
- hspec-core/src/Test/Hspec/Core/Formatters/V2.hs +333/−0
- hspec-core/src/Test/Hspec/Core/Hooks.hs +21/−3
- hspec-core/src/Test/Hspec/Core/QuickCheck.hs +3/−0
- hspec-core/src/Test/Hspec/Core/QuickCheckUtil.hs +0/−1
- hspec-core/src/Test/Hspec/Core/Runner.hs +70/−46
- hspec-core/src/Test/Hspec/Core/Runner/Eval.hs +71/−59
- hspec-core/src/Test/Hspec/Core/Runner/PrintSlowSpecItems.hs +41/−0
- hspec-core/src/Test/Hspec/Core/Shuffle.hs +1/−1
- hspec-core/src/Test/Hspec/Core/Tree.hs +4/−4
- hspec-core/src/Test/Hspec/Core/Util.hs +3/−3
- hspec-core/vendor/Control/Concurrent/Async.hs +1/−1
- hspec-core/vendor/Data/Algorithm/Diff.hs +1/−0
- hspec-discover/src/Test/Hspec/Discover/Run.hs +118/−42
- hspec-discover/src/Test/Hspec/Discover/Sort.hs +6/−3
- hspec-meta.cabal +13/−5
- src/Test/Hspec.hs +5/−0
- src/Test/Hspec/Discover.hs +0/−2
- version.yaml +1/−1
+ hspec-core/src/GetOpt/Declarative.hs view
@@ -0,0 +1,5 @@+module GetOpt.Declarative (module GetOpt.Declarative) where+import Prelude ()+import GetOpt.Declarative.Types as GetOpt.Declarative+import GetOpt.Declarative.Interpret as GetOpt.Declarative+import GetOpt.Declarative.Environment as GetOpt.Declarative
+ hspec-core/src/GetOpt/Declarative/Environment.hs view
@@ -0,0 +1,51 @@+module GetOpt.Declarative.Environment (+ InvalidValue(..)+, parseEnvironmentOptions+, parseEnvironmentOption+) where++import Prelude ()+import Test.Hspec.Core.Compat+import Data.Char++import GetOpt.Declarative.Types++data InvalidValue = InvalidValue String String+ deriving (Eq, Show)++parseEnvironmentOptions :: String -> [(String, String)] -> config -> [Option config] -> ([InvalidValue], config)+parseEnvironmentOptions prefix env = foldr f . (,) []+ where+ f :: Option config -> ([InvalidValue], config) -> ([InvalidValue], config)+ f option (errs, config) = case parseEnvironmentOption prefix env config option of+ Left err -> (err : errs, config)+ Right c -> (errs, c)++parseEnvironmentOption :: String -> [(String, String)] -> config -> Option config -> Either InvalidValue config+parseEnvironmentOption prefix env config option = case lookup name env of+ Nothing -> Right config+ Just value -> case optionSetter option of+ NoArg setter -> case value of+ "yes" -> Right $ setter config+ _ -> invalidValue+ Flag setter -> case value of+ "yes" -> Right $ setter True config+ "no" -> Right $ setter False config+ _ -> invalidValue+ OptArg _ setter -> case setter (Just value) config of+ Just c -> Right c+ Nothing -> invalidValue+ Arg _ setter -> case setter value config of+ Just c -> Right c+ Nothing -> invalidValue+ where+ invalidValue = Left (InvalidValue name value)+ where+ name = envVarName prefix option++envVarName :: String -> Option config -> String+envVarName prefix option = prefix ++ '_' : map f (optionName option)+ where+ f c = case c of+ '-' -> '_'+ _ -> toUpper c
+ hspec-core/src/GetOpt/Declarative/Interpret.hs view
@@ -0,0 +1,97 @@+module GetOpt.Declarative.Interpret (+ ParseResult(..)+, parseCommandLineOptions+, parse+, interpretOptions+) where++import Prelude ()+import Test.Hspec.Core.Compat+import Data.Maybe++import System.Console.GetOpt (OptDescr, ArgOrder(..), getOpt)+import qualified System.Console.GetOpt as GetOpt++import GetOpt.Declarative.Types+import GetOpt.Declarative.Util (mkUsageInfo, mapOptDescr)++data InvalidArgument = InvalidArgument String String++data ParseResult config = Help String | Failure String | Success config++parseCommandLineOptions :: [(String, [Option config])] -> String -> [String] -> config -> ParseResult config+parseCommandLineOptions opts prog args config = case parseWithHelp (concatMap snd options) config args of+ Nothing -> Help usage+ Just (Right c) -> Success c+ Just (Left err) -> Failure $ prog ++ ": " ++ err ++ "\nTry `" ++ prog ++ " --help' for more information.\n"+ where+ options = addHelpFlag $ map (fmap interpretOptions) opts++ documentedOptions = addHelpFlag $ map (fmap $ interpretOptions . filter optionDocumented) opts++ usage :: String+ usage = "Usage: " ++ prog ++ " [OPTION]...\n\n"+ ++ (intercalate "\n" $ map (uncurry mkUsageInfo) documentedOptions)++addHelpFlag :: [(a, [OptDescr a1])] -> [(a, [OptDescr (Maybe a1)])]+addHelpFlag opts = case opts of+ (section, xs) : ys -> (section, GetOpt.Option [] ["help"] (GetOpt.NoArg help) "display this help and exit" : noHelp xs) : map (fmap noHelp) ys+ [] -> []+ where+ help = Nothing++ noHelp :: [OptDescr a] -> [OptDescr (Maybe a)]+ noHelp = map (mapOptDescr Just)++parseWithHelp :: [OptDescr (Maybe (config -> Either InvalidArgument config))] -> config -> [String] -> Maybe (Either String config)+parseWithHelp options config args = case getOpt Permute options args of+ (opts, [], []) | _ : _ <- [() | Nothing <- opts] -> Nothing+ (opts, xs, ys) -> Just $ interpretResult config (catMaybes opts, xs, ys)++parse :: [OptDescr (config -> Either InvalidArgument config)] -> config -> [String] -> Either String config+parse options config = interpretResult config . getOpt Permute options++interpretResult :: config -> ([config -> Either InvalidArgument config], [String], [String]) -> Either String config+interpretResult config = interpretGetOptResult >=> foldResult config++foldResult :: config -> [config -> Either InvalidArgument config] -> Either String config+foldResult config opts = either (Left . renderInvalidArgument) return $ foldlM (flip id) config opts++renderInvalidArgument :: InvalidArgument -> String+renderInvalidArgument (InvalidArgument name value) = "invalid argument `" ++ value ++ "' for `--" ++ name ++ "'"++interpretGetOptResult :: ([a], [String], [String]) -> Either String [a]+interpretGetOptResult result = case result of+ (opts, [], []) -> Right opts+ (_, _, err:_) -> Left (init err)+ (_, arg:_, _) -> Left ("unexpected argument `" ++ arg ++ "'")++interpretOptions :: [Option config] -> [OptDescr (config -> Either InvalidArgument config)]+interpretOptions = concatMap interpretOption++interpretOption :: Option config -> [OptDescr (config -> Either InvalidArgument config)]+interpretOption (Option name shortcut argDesc help _) = case argDesc of+ NoArg setter -> [option $ GetOpt.NoArg (Right . setter)]++ Flag setter -> [+ option (arg True)+ , GetOpt.Option [] ["no-" ++ name] (arg False) ("do not " ++ help)+ ]+ where+ arg v = GetOpt.NoArg (Right . setter v)++ OptArg argName setter -> [option $ GetOpt.OptArg arg argName]+ where+ arg mInput c = case setter mInput c of+ Just c_ -> Right c_+ Nothing -> case mInput of+ Just input -> invalid input+ Nothing -> Right c++ Arg argName setter -> [option (GetOpt.ReqArg arg argName)]+ where+ arg input = maybe (invalid input) Right . setter input++ where+ invalid = Left . InvalidArgument name+ option arg = GetOpt.Option (maybeToList shortcut) [name] arg help
+ hspec-core/src/GetOpt/Declarative/Types.hs view
@@ -0,0 +1,18 @@+module GetOpt.Declarative.Types where++import Prelude ()+import Test.Hspec.Core.Compat++data Option config = Option {+ optionName :: String+, optionShortcut :: Maybe Char+, optionSetter :: OptionSetter config+, optionHelp :: String+, optionDocumented :: Bool+}++data OptionSetter config =+ NoArg (config -> config)+ | Flag (Bool -> config -> config)+ | OptArg String (Maybe String -> config -> Maybe config)+ | Arg String (String -> config -> Maybe config)
+ hspec-core/src/GetOpt/Declarative/Util.hs view
@@ -0,0 +1,47 @@+{-# LANGUAGE CPP #-}+module GetOpt.Declarative.Util (mkUsageInfo, mapOptDescr) where++import Prelude ()+import Test.Hspec.Core.Compat++import System.Console.GetOpt++import Test.Hspec.Core.Util++modifyHelp :: (String -> String) -> OptDescr a -> OptDescr a+modifyHelp modify (Option s n a help) = Option s n a (modify help)++mkUsageInfo :: String -> [OptDescr a] -> String+mkUsageInfo title = usageInfo title . addLineBreaksForHelp . condenseNoOptions++addLineBreaksForHelp :: [OptDescr a] -> [OptDescr a]+addLineBreaksForHelp options = map (modifyHelp addLineBreaks) options+ where+ withoutHelpWidth = maxLength . usageInfo "" . map removeHelp+ helpWidth = 80 - withoutHelpWidth options++ addLineBreaks = unlines . lineBreaksAt helpWidth++ maxLength = maximum . map length . lines+ removeHelp = modifyHelp (const "")++condenseNoOptions :: [OptDescr a] -> [OptDescr a]+condenseNoOptions options = case options of+ Option "" [optionA] arg help : Option "" [optionB] _ _ : ys | optionB == ("no-" ++ optionA) ->+ Option "" ["[no-]" ++ optionA] arg help : condenseNoOptions ys+ x : xs -> x : condenseNoOptions xs+ [] -> []++mapOptDescr :: (a -> b) -> OptDescr a -> OptDescr b+#if MIN_VERSION_base(4,7,0)+mapOptDescr = fmap+#else+mapOptDescr f opt = case opt of+ Option short long arg help -> Option short long (mapArgDescr f arg) help++mapArgDescr :: (a -> b) -> ArgDescr a -> ArgDescr b+mapArgDescr f arg = case arg of+ NoArg a -> NoArg (f a)+ ReqArg parse name -> ReqArg (fmap f parse) name+ OptArg parse name -> OptArg (fmap f parse) name+#endif
hspec-core/src/Test/Hspec/Core/Clock.hs view
@@ -1,6 +1,7 @@ {-# LANGUAGE GeneralizedNewtypeDeriving #-} module Test.Hspec.Core.Clock ( Seconds(..)+, toMilliseconds , toMicroseconds , getMonotonicTime , measure@@ -8,13 +9,19 @@ , timeout ) where +import Prelude ()+import Test.Hspec.Core.Compat+ import Text.Printf import System.Clock import Control.Concurrent import qualified System.Timeout as System newtype Seconds = Seconds Double- deriving (Eq, Show, Num, Fractional, PrintfArg)+ deriving (Eq, Show, Ord, Num, Fractional, PrintfArg)++toMilliseconds :: Seconds -> Int+toMilliseconds (Seconds s) = floor (s * 1000) toMicroseconds :: Seconds -> Int toMicroseconds (Seconds s) = floor (s * 1000000)
hspec-core/src/Test/Hspec/Core/Compat.hs view
@@ -1,31 +1,11 @@ {-# LANGUAGE CPP #-} module Test.Hspec.Core.Compat (- getDefaultConcurrentJobs-, showType-, showFullType-, readMaybe-, lookupEnv-, module Data.IORef--, module Prelude-, module Control.Applicative-, module Control.Monad-, module Data.Foldable-, module Data.Traversable-, module Data.Monoid-, module Data.List--#if !MIN_VERSION_base(4,6,0)-, modifyIORef'-, atomicWriteIORef-#endif-, interruptible--, guarded+ module Imports+, module Test.Hspec.Core.Compat ) where -import Control.Applicative-import Control.Monad hiding (+import Control.Applicative as Imports+import Control.Monad as Imports hiding ( mapM , mapM_ , forM@@ -34,12 +14,28 @@ , sequence , sequence_ )-import Data.Foldable-import Data.Traversable-import Data.Monoid-import Data.List (intercalate)+import Data.Foldable as Imports -import Prelude hiding (+#if MIN_VERSION_base(4,11,0)+import Data.Functor as Imports+#endif++import Data.Traversable as Imports+import Data.Monoid as Imports+import Data.List as Imports (+ stripPrefix+ , isPrefixOf+ , isInfixOf+ , intercalate+ , inits+ , tails+ , sortBy+#if MIN_VERSION_base(4,8,0)+ , sortOn+#endif+ )++import Prelude as Imports hiding ( all , and , any@@ -66,22 +62,31 @@ ) import Data.Typeable (Typeable, typeOf, typeRepTyCon)+import Data.IORef as Imports++#if MIN_VERSION_base(4,6,0)+import Text.Read as Imports (readMaybe)+import System.Environment as Imports (lookupEnv)+#else import Text.Read-import Data.IORef import System.Environment+import qualified Text.ParserCombinators.ReadP as P+#endif +#if !MIN_VERSION_base(4,8,0)+import Data.Ord (comparing)+#endif+ import Data.Typeable (tyConModule, tyConName) import Control.Concurrent #if MIN_VERSION_base(4,9,0)-import Control.Exception (interruptible)+import Control.Exception as Imports (interruptible) #else import GHC.IO #endif #if !MIN_VERSION_base(4,6,0)-import qualified Text.ParserCombinators.ReadP as P- -- |Strict version of 'modifyIORef' modifyIORef' :: IORef a -> (a -> a) -> IO () modifyIORef' ref f = do@@ -147,3 +152,15 @@ guarded :: Alternative m => (a -> Bool) -> a -> m a guarded p a = if p a then pure a else empty++#if !MIN_VERSION_base(4,8,0)+sortOn :: Ord b => (a -> b) -> [a] -> [a]+sortOn f =+ map snd . sortBy (comparing fst) . map (\x -> let y = f x in y `seq` (y, x))+#endif++#if !MIN_VERSION_base(4,11,0)+infixl 1 <&>+(<&>) :: Functor f => f a -> (a -> b) -> f b+(<&>) = flip (<$>)+#endif
hspec-core/src/Test/Hspec/Core/Config.hs view
@@ -2,6 +2,7 @@ module Test.Hspec.Core.Config ( Config (..) , ColorMode(..)+, UnicodeMode(..) , defaultConfig , readConfig , configAddFilter@@ -24,11 +25,12 @@ import System.Exit import System.FilePath import System.Directory-import System.Environment (getProgName)+import System.Environment (getProgName, getEnvironment) import qualified Test.QuickCheck as QC import Test.Hspec.Core.Util import Test.Hspec.Core.Config.Options+import Test.Hspec.Core.Config.Definition (Config(..), ColorMode(..), UnicodeMode(..), defaultConfig, filterOr) import Test.Hspec.Core.FailureReport import Test.Hspec.Core.QuickCheckUtil (mkGen) import Test.Hspec.Core.Example (Params(..), defaultParams)@@ -67,18 +69,22 @@ where qcArgs = ( maybe id setSeed (configQuickCheckSeed c)- . maybe id setMaxDiscardRatio (configQuickCheckMaxDiscardRatio c)+ . maybe id setMaxShrinks (configQuickCheckMaxShrinks c) . maybe id setMaxSize (configQuickCheckMaxSize c)+ . maybe id setMaxDiscardRatio (configQuickCheckMaxDiscardRatio c) . maybe id setMaxSuccess (configQuickCheckMaxSuccess c)) (paramsQuickCheckArgs defaultParams) setMaxSuccess :: Int -> QC.Args -> QC.Args setMaxSuccess n args = args {QC.maxSuccess = n} + setMaxDiscardRatio :: Int -> QC.Args -> QC.Args+ setMaxDiscardRatio n args = args {QC.maxDiscardRatio = n}+ setMaxSize :: Int -> QC.Args -> QC.Args setMaxSize n args = args {QC.maxSize = n} - setMaxDiscardRatio :: Int -> QC.Args -> QC.Args- setMaxDiscardRatio n args = args {QC.maxDiscardRatio = n}+ setMaxShrinks :: Int -> QC.Args -> QC.Args+ setMaxShrinks n args = args {QC.maxShrinks = n} setSeed :: Integer -> QC.Args -> QC.Args setSeed n args = args {QC.replay = Just (mkGen (fromIntegral n), 0)}@@ -113,10 +119,13 @@ case ignore of True -> return [] False -> readConfigFiles- envVar <- fmap words <$> lookupEnv envVarName- case parseOptions opts_ prog configFiles envVar args of+ env <- getEnvironment+ let envVar = words <$> lookup envVarName env+ case parseOptions opts_ prog configFiles envVar env args of Left (err, msg) -> exitWithMessage err msg- Right opts -> return opts+ Right (warnings, opts) -> do+ mapM_ (hPutStrLn stderr) warnings+ return opts readFailureReportOnRerun :: Config -> IO (Maybe FailureReport) readFailureReportOnRerun config
+ hspec-core/src/Test/Hspec/Core/Config/Definition.hs view
@@ -0,0 +1,290 @@+{-# LANGUAGE CPP #-}+module Test.Hspec.Core.Config.Definition (+ Config(..)+, ColorMode(..)+, UnicodeMode(..)+, filterOr+, defaultConfig++, commandLineOnlyOptions+, formatterOptions+, smallCheckOptions+, quickCheckOptions+, runnerOptions++#ifdef TEST+, formatOrList+#endif+) where++import Prelude ()+import Test.Hspec.Core.Compat++import Test.Hspec.Core.Example (Params(..), defaultParams)+import Test.Hspec.Core.Format (Format, FormatConfig)+import qualified Test.Hspec.Core.Formatters.V1 as V1+import qualified Test.Hspec.Core.Formatters.V2 as V2+import Test.Hspec.Core.Util++import GetOpt.Declarative+++data ColorMode = ColorAuto | ColorNever | ColorAlways+ deriving (Eq, Show)++data UnicodeMode = UnicodeAuto | UnicodeNever | UnicodeAlways+ deriving (Eq, Show)++data Config = Config {+ configIgnoreConfigFile :: Bool+, configDryRun :: Bool+, configFocusedOnly :: Bool+, configFailOnFocused :: Bool+, configPrintSlowItems :: Maybe Int+, configPrintCpuTime :: Bool+, configFailFast :: Bool+, configRandomize :: Bool+, configFailureReport :: Maybe FilePath+, configRerun :: Bool+, configRerunAllOnSuccess :: Bool++-- |+-- A predicate that is used to filter the spec before it is run. Only examples+-- that satisfy the predicate are run.+, configFilterPredicate :: Maybe (Path -> Bool)+, configSkipPredicate :: Maybe (Path -> Bool)+, configQuickCheckSeed :: Maybe Integer+, configQuickCheckMaxSuccess :: Maybe Int+, configQuickCheckMaxDiscardRatio :: Maybe Int+, configQuickCheckMaxSize :: Maybe Int+, configQuickCheckMaxShrinks :: Maybe Int+, configSmallCheckDepth :: Int+, configColorMode :: ColorMode+, configUnicodeMode :: UnicodeMode+, configDiff :: Bool+, configTimes :: Bool+, configAvailableFormatters :: [(String, FormatConfig -> IO Format)]+, configFormat :: Maybe (FormatConfig -> IO Format)+, configFormatter :: Maybe V1.Formatter -- ^ deprecated, use `configFormat` instead+, configHtmlOutput :: Bool+, configConcurrentJobs :: Maybe Int+}++defaultConfig :: Config+defaultConfig = Config {+ configIgnoreConfigFile = False+, configDryRun = False+, configFocusedOnly = False+, configFailOnFocused = False+, configPrintSlowItems = Nothing+, configPrintCpuTime = False+, configFailFast = False+, configRandomize = False+, configFailureReport = Nothing+, configRerun = False+, configRerunAllOnSuccess = False+, configFilterPredicate = Nothing+, configSkipPredicate = Nothing+, configQuickCheckSeed = Nothing+, configQuickCheckMaxSuccess = Nothing+, configQuickCheckMaxDiscardRatio = Nothing+, configQuickCheckMaxSize = Nothing+, configQuickCheckMaxShrinks = Nothing+, configSmallCheckDepth = paramsSmallCheckDepth defaultParams+, configColorMode = ColorAuto+, configUnicodeMode = UnicodeAuto+, configDiff = True+, configTimes = False+, configAvailableFormatters = map (fmap V2.formatterToFormat) [+ ("checks", V2.checks)+ , ("specdoc", V2.specdoc)+ , ("progress", V2.progress)+ , ("failed-examples", V2.failed_examples)+ , ("silent", V2.silent)+ ]+, configFormat = Nothing+, configFormatter = Nothing+, configHtmlOutput = False+, configConcurrentJobs = Nothing+}++option :: String -> OptionSetter config -> String -> Option config+option name arg help = Option name Nothing arg help True++mkFlag :: String -> (Bool -> Config -> Config) -> String -> Option Config+mkFlag name setter = option name (Flag setter)++mkOptionNoArg :: String -> Maybe Char -> (Config -> Config) -> String -> Option Config+mkOptionNoArg name shortcut setter help = Option name shortcut (NoArg setter) help True++mkOption :: String -> Maybe Char -> OptionSetter Config -> String -> Option Config+mkOption name shortcut arg help = Option name shortcut arg help True++undocumented :: Option config -> Option config+undocumented opt = opt {optionDocumented = False}++argument :: String -> (String -> Maybe a) -> (a -> Config -> Config) -> OptionSetter Config+argument name parser setter = Arg name $ \ input c -> flip setter c <$> parser input++formatterOptions :: [(String, FormatConfig -> IO Format)] -> [Option Config]+formatterOptions formatters = [+ mkOption "format" (Just 'f') (argument "FORMATTER" readFormatter setFormatter) helpForFormat+ , mkFlag "color" setColor "colorize the output"+ , mkFlag "unicode" setUnicode "output unicode"+ , mkFlag "diff" setDiff "show colorized diffs"+ , mkFlag "times" setTimes "report times for individual spec items"+ , mkOptionNoArg "print-cpu-time" Nothing setPrintCpuTime "include used CPU time in summary"+ , printSlowItemsOption++ -- undocumented for now, as we probably want to change this to produce a+ -- standalone HTML report in the future+ , undocumented $ mkOptionNoArg "html" Nothing setHtml "produce HTML output"+ ]+ where+ setHtml config = config {configHtmlOutput = True}++ helpForFormat :: String+ helpForFormat = "use a custom formatter; this can be one of " ++ (formatOrList $ map fst formatters)++ readFormatter :: String -> Maybe (FormatConfig -> IO Format)+ readFormatter = (`lookup` formatters)++ setFormatter :: (FormatConfig -> IO Format) -> Config -> Config+ setFormatter f c = c {configFormat = Just f}++ setColor :: Bool -> Config -> Config+ setColor v config = config {configColorMode = if v then ColorAlways else ColorNever}++ setUnicode :: Bool -> Config -> Config+ setUnicode v config = config {configUnicodeMode = if v then UnicodeAlways else UnicodeNever}++ setDiff :: Bool -> Config -> Config+ setDiff v config = config {configDiff = v}++ setTimes :: Bool -> Config -> Config+ setTimes v config = config {configTimes = v}++ setPrintCpuTime config = config {configPrintCpuTime = True}++printSlowItemsOption :: Option Config+printSlowItemsOption = Option name (Just 'p') (OptArg "N" arg) "print the N slowest spec items (default: 10)" True+ where+ name = "print-slow-items"++ setter :: Maybe Int -> Config -> Config+ setter v c = c {configPrintSlowItems = v}++ arg :: Maybe String -> Config -> Maybe Config+ arg = maybe (Just . (setter $ Just 10)) parseArg++ parseArg :: String -> Config -> Maybe Config+ parseArg input c = case readMaybe input of+ Just 0 -> Just (setter Nothing c)+ Just n -> Just (setter (Just n) c)+ Nothing -> Nothing++smallCheckOptions :: [Option Config]+smallCheckOptions = [+ option "depth" (argument "N" readMaybe setDepth) "maximum depth of generated test values for SmallCheck properties"+ ]++setDepth :: Int -> Config -> Config+setDepth n c = c {configSmallCheckDepth = n}++quickCheckOptions :: [Option Config]+quickCheckOptions = [+ Option "qc-max-success" (Just 'a') (argument "N" readMaybe setMaxSuccess) "maximum number of successful tests before a QuickCheck property succeeds" True+ , option "qc-max-discard" (argument "N" readMaybe setMaxDiscardRatio) "maximum number of discarded tests per successful test before giving up"+ , option "qc-max-size" (argument "N" readMaybe setMaxSize) "size to use for the biggest test cases"+ , option "qc-max-shrinks" (argument "N" readMaybe setMaxShrinks) "maximum number of shrinks to perform before giving up (a value of 0 turns shrinking off)"+ , option "seed" (argument "N" readMaybe setSeed) "used seed for QuickCheck properties"++ -- for compatibility with test-framework+ , undocumented $ option "maximum-generated-tests" (argument "NUMBER" readMaybe setMaxSuccess) "how many automated tests something like QuickCheck should try, by default"+ ]++setMaxSuccess :: Int -> Config -> Config+setMaxSuccess n c = c {configQuickCheckMaxSuccess = Just n}++setMaxDiscardRatio :: Int -> Config -> Config+setMaxDiscardRatio n c = c {configQuickCheckMaxDiscardRatio = Just n}++setMaxSize :: Int -> Config -> Config+setMaxSize n c = c {configQuickCheckMaxSize = Just n}++setMaxShrinks :: Int -> Config -> Config+setMaxShrinks n c = c {configQuickCheckMaxShrinks = Just n}++setSeed :: Integer -> Config -> Config+setSeed n c = c {configQuickCheckSeed = Just n}++runnerOptions :: [Option Config]+runnerOptions = [+ mkFlag "dry-run" setDryRun "pretend that everything passed; don't verify anything"+ , mkFlag "focused-only" setFocusedOnly "do not run anything, unless there are focused spec items"+ , mkFlag "fail-on-focused" setFailOnFocused "fail on focused spec items"+ , mkFlag "fail-fast" setFailFast "abort on first failure"+ , mkFlag "randomize" setRandomize "randomize execution order"+ , mkOptionNoArg "rerun" (Just 'r') setRerun "rerun all examples that failed in the previous test run (only works in combination with --failure-report or in GHCi)"+ , option "failure-report" (argument "FILE" return setFailureReport) "read/write a failure report for use with --rerun"+ , mkOptionNoArg "rerun-all-on-success" Nothing setRerunAllOnSuccess "run the whole test suite after a previously failing rerun succeeds for the first time (only works in combination with --rerun)"+ , mkOption "jobs" (Just 'j') (argument "N" readMaxJobs setMaxJobs) "run at most N parallelizable tests simultaneously (default: number of available processors)"+ ]+ where+ readMaxJobs :: String -> Maybe Int+ readMaxJobs s = do+ n <- readMaybe s+ guard $ n > 0+ return n++ setFailureReport :: String -> Config -> Config+ setFailureReport file c = c {configFailureReport = Just file}++ setMaxJobs :: Int -> Config -> Config+ setMaxJobs n c = c {configConcurrentJobs = Just n}++ setDryRun :: Bool -> Config -> Config+ setDryRun value config = config {configDryRun = value}++ setFocusedOnly :: Bool -> Config -> Config+ setFocusedOnly value config = config {configFocusedOnly = value}++ setFailOnFocused :: Bool -> Config -> Config+ setFailOnFocused value config = config {configFailOnFocused = value}++ setFailFast :: Bool -> Config -> Config+ setFailFast value config = config {configFailFast = value}++ setRandomize :: Bool -> Config -> Config+ setRandomize value config = config {configRandomize = value}++ setRerun config = config {configRerun = True}+ setRerunAllOnSuccess config = config {configRerunAllOnSuccess = True}++commandLineOnlyOptions :: [Option Config]+commandLineOnlyOptions = [+ mkOptionNoArg "ignore-dot-hspec" Nothing setIgnoreConfigFile "do not read options from ~/.hspec and .hspec"+ , mkOption "match" (Just 'm') (argument "PATTERN" return addMatch) "only run examples that match given PATTERN"+ , option "skip" (argument "PATTERN" return addSkip) "skip examples that match given PATTERN"+ ]+ where+ setIgnoreConfigFile config = config {configIgnoreConfigFile = True}++addMatch :: String -> Config -> Config+addMatch s c = c {configFilterPredicate = Just (filterPredicate s) `filterOr` configFilterPredicate c}++addSkip :: String -> Config -> Config+addSkip s c = c {configSkipPredicate = Just (filterPredicate s) `filterOr` configSkipPredicate c}++filterOr :: Maybe (Path -> Bool) -> Maybe (Path -> Bool) -> Maybe (Path -> Bool)+filterOr p1_ p2_ = case (p1_, p2_) of+ (Just p1, Just p2) -> Just $ \path -> p1 path || p2 path+ _ -> p1_ <|> p2_++formatOrList :: [String] -> String+formatOrList xs = case xs of+ [] -> ""+ x : ys -> (case ys of+ [] -> x+ _ : [] -> x ++ " or "+ _ : _ : _ -> x ++ ", ") ++ formatOrList ys
hspec-core/src/Test/Hspec/Core/Config/Options.hs view
@@ -1,336 +1,98 @@+{-# LANGUAGE CPP #-} module Test.Hspec.Core.Config.Options (- Config(..)-, ColorMode (..)-, defaultConfig-, filterOr-, parseOptions-, ConfigFile-, ignoreConfigFile+ ConfigFile , envVarName+, ignoreConfigFile+, parseOptions ) where import Prelude () import Test.Hspec.Core.Compat -import System.IO import System.Exit-import System.Console.GetOpt -import Test.Hspec.Core.Formatters-import Test.Hspec.Core.Config.Util-import Test.Hspec.Core.Util-import Test.Hspec.Core.Example (Params(..), defaultParams)-import Data.Functor.Identity-import Data.Maybe+import Test.Hspec.Core.Format (Format, FormatConfig)+import Test.Hspec.Core.Config.Definition+import qualified GetOpt.Declarative as Declarative+import GetOpt.Declarative.Interpret (parse, interpretOptions, ParseResult(..)) type ConfigFile = (FilePath, [String])- type EnvVar = [String] envVarName :: String envVarName = "HSPEC_OPTIONS" -data Config = Config {- configIgnoreConfigFile :: Bool-, configDryRun :: Bool-, configFocusedOnly :: Bool-, configFailOnFocused :: Bool-, configPrintCpuTime :: Bool-, configFastFail :: Bool-, configRandomize :: Bool-, configFailureReport :: Maybe FilePath-, configRerun :: Bool-, configRerunAllOnSuccess :: Bool---- |--- A predicate that is used to filter the spec before it is run. Only examples--- that satisfy the predicate are run.-, configFilterPredicate :: Maybe (Path -> Bool)-, configSkipPredicate :: Maybe (Path -> Bool)-, configQuickCheckSeed :: Maybe Integer-, configQuickCheckMaxSuccess :: Maybe Int-, configQuickCheckMaxDiscardRatio :: Maybe Int-, configQuickCheckMaxSize :: Maybe Int-, configSmallCheckDepth :: Int-, configColorMode :: ColorMode-, configDiff :: Bool-, configFormatter :: Maybe Formatter-, configHtmlOutput :: Bool-, configOutputFile :: Either Handle FilePath-, configConcurrentJobs :: Maybe Int-}--defaultConfig :: Config-defaultConfig = Config {- configIgnoreConfigFile = False-, configDryRun = False-, configFocusedOnly = False-, configFailOnFocused = False-, configPrintCpuTime = False-, configFastFail = False-, configRandomize = False-, configFailureReport = Nothing-, configRerun = False-, configRerunAllOnSuccess = False-, configFilterPredicate = Nothing-, configSkipPredicate = Nothing-, configQuickCheckSeed = Nothing-, configQuickCheckMaxSuccess = Nothing-, configQuickCheckMaxDiscardRatio = Nothing-, configQuickCheckMaxSize = Nothing-, configSmallCheckDepth = paramsSmallCheckDepth defaultParams-, configColorMode = ColorAuto-, configDiff = True-, configFormatter = Nothing-, configHtmlOutput = False-, configOutputFile = Left stdout-, configConcurrentJobs = Nothing-}--filterOr :: Maybe (Path -> Bool) -> Maybe (Path -> Bool) -> Maybe (Path -> Bool)-filterOr p1_ p2_ = case (p1_, p2_) of- (Just p1, Just p2) -> Just $ \path -> p1 path || p2 path- _ -> p1_ <|> p2_--addMatch :: String -> Config -> Config-addMatch s c = c {configFilterPredicate = Just (filterPredicate s) `filterOr` configFilterPredicate c}--addSkip :: String -> Config -> Config-addSkip s c = c {configSkipPredicate = Just (filterPredicate s) `filterOr` configSkipPredicate c}--setDepth :: Int -> Config -> Config-setDepth n c = c {configSmallCheckDepth = n}--setMaxSuccess :: Int -> Config -> Config-setMaxSuccess n c = c {configQuickCheckMaxSuccess = Just n}--setMaxSize :: Int -> Config -> Config-setMaxSize n c = c {configQuickCheckMaxSize = Just n}--setMaxDiscardRatio :: Int -> Config -> Config-setMaxDiscardRatio n c = c {configQuickCheckMaxDiscardRatio = Just n}--setSeed :: Integer -> Config -> Config-setSeed n c = c {configQuickCheckSeed = Just n}--data ColorMode = ColorAuto | ColorNever | ColorAlways- deriving (Eq, Show)--type Result m = Either InvalidArgument (m Config)--data InvalidArgument = InvalidArgument String String--data Arg a = Arg {- _argumentName :: String-, _argumentParser :: String -> Maybe a-, _argumentSetter :: a -> Config -> Config-}--mkOption :: Monad m => [Char] -> String -> Arg a -> String -> OptDescr (Result m -> Result m)-mkOption shortcut name (Arg argName parser setter) help = Option shortcut [name] (ReqArg arg argName) help- where- arg input x = x >>= \c -> case parser input of- Just n -> Right (setter n `liftM` c)- Nothing -> Left (InvalidArgument name input)--mkFlag :: Monad m => String -> (Bool -> Config -> Config) -> String -> [OptDescr (Result m -> Result m)]-mkFlag name setter help = [- Option [] [name] (NoArg $ set $ setter True) help- , Option [] ["no-" ++ name] (NoArg $ set $ setter False) ("do not " ++ help)- ]--commandLineOptions :: [OptDescr (Result Maybe -> Result Maybe)]-commandLineOptions = [- Option [] ["help"] (NoArg (const $ Right Nothing)) "display this help and exit"- , Option [] ["ignore-dot-hspec"] (NoArg setIgnoreConfigFile) "do not read options from ~/.hspec and .hspec"- , mkOption "m" "match" (Arg "PATTERN" return addMatch) "only run examples that match given PATTERN"- , mkOption [] "skip" (Arg "PATTERN" return addSkip) "skip examples that match given PATTERN"- ]- where- setIgnoreConfigFile = set $ \config -> config {configIgnoreConfigFile = True}--formatterOptions :: Monad m => [OptDescr (Result m -> Result m)]-formatterOptions = concat [- [mkOption "f" "format" (Arg "FORMATTER" readFormatter setFormatter) helpForFormat]- , mkFlag "color" setColor "colorize the output"- , mkFlag "diff" setDiff "show colorized diffs"- , [Option [] ["print-cpu-time"] (NoArg setPrintCpuTime) "include used CPU time in summary"]- ]- where- formatters :: [(String, Formatter)]- formatters = [- ("specdoc", specdoc)- , ("progress", progress)- , ("failed-examples", failed_examples)- , ("silent", silent)- ]-- helpForFormat :: String- helpForFormat = "use a custom formatter; this can be one of " ++ (formatOrList $ map fst formatters)-- readFormatter :: String -> Maybe Formatter- readFormatter = (`lookup` formatters)-- setFormatter :: Formatter -> Config -> Config- setFormatter f c = c {configFormatter = Just f}-- setColor :: Bool -> Config -> Config- setColor v config = config {configColorMode = if v then ColorAlways else ColorNever}-- setDiff :: Bool -> Config -> Config- setDiff v config = config {configDiff = v}-- setPrintCpuTime = set $ \config -> config {configPrintCpuTime = True}--smallCheckOptions :: Monad m => [OptDescr (Result m -> Result m)]-smallCheckOptions = [- mkOption [] "depth" (Arg "N" readMaybe setDepth) "maximum depth of generated test values for SmallCheck properties"- ]--quickCheckOptions :: Monad m => [OptDescr (Result m -> Result m)]-quickCheckOptions = [- mkOption "a" "qc-max-success" (Arg "N" readMaybe setMaxSuccess) "maximum number of successful tests before a QuickCheck property succeeds"- , mkOption "" "qc-max-size" (Arg "N" readMaybe setMaxSize) "size to use for the biggest test cases"- , mkOption "" "qc-max-discard" (Arg "N" readMaybe setMaxDiscardRatio) "maximum number of discarded tests per successful test before giving up"- , mkOption [] "seed" (Arg "N" readMaybe setSeed) "used seed for QuickCheck properties"- ]--runnerOptions :: Monad m => [OptDescr (Result m -> Result m)]-runnerOptions = concat [- mkFlag "dry-run" setDryRun "pretend that everything passed; don't verify anything"- , mkFlag "focused-only" setFocusedOnly "do not run anything, unless there are focused spec items"- , mkFlag "fail-on-focused" setFailOnFocused "fail on focused spec items"- , mkFlag "fail-fast" setFastFail "abort on first failure"- , mkFlag "randomize" setRandomize "randomize execution order"- ] ++ [- Option "r" ["rerun"] (NoArg setRerun) "rerun all examples that failed in the previous test run (only works in combination with --failure-report or in GHCi)"- , mkOption [] "failure-report" (Arg "FILE" return setFailureReport) "read/write a failure report for use with --rerun"- , Option [] ["rerun-all-on-success"] (NoArg setRerunAllOnSuccess) "run the whole test suite after a previously failing rerun succeeds for the first time (only works in combination with --rerun)"--- , mkOption "j" "jobs" (Arg "N" readMaxJobs setMaxJobs) "run at most N parallelizable tests simultaneously (default: number of available processors)"- ]- where- readMaxJobs :: String -> Maybe Int- readMaxJobs s = do- n <- readMaybe s- guard $ n > 0- return n-- setFailureReport :: String -> Config -> Config- setFailureReport file c = c {configFailureReport = Just file}-- setMaxJobs :: Int -> Config -> Config- setMaxJobs n c = c {configConcurrentJobs = Just n}-- setDryRun :: Bool -> Config -> Config- setDryRun value config = config {configDryRun = value}-- setFocusedOnly :: Bool -> Config -> Config- setFocusedOnly value config = config {configFocusedOnly = value}-- setFailOnFocused :: Bool -> Config -> Config- setFailOnFocused value config = config {configFailOnFocused = value}-- setFastFail :: Bool -> Config -> Config- setFastFail value config = config {configFastFail = value}-- setRandomize :: Bool -> Config -> Config- setRandomize value config = config {configRandomize = value}-- setRerun = set $ \config -> config {configRerun = True}- setRerunAllOnSuccess = set $ \config -> config {configRerunAllOnSuccess = True}+commandLineOptions :: [(String, FormatConfig -> IO Format)] -> [(String, [Declarative.Option Config])]+commandLineOptions formatters =+ ("OPTIONS", commandLineOnlyOptions)+ : otherOptions formatters -documentedConfigFileOptions :: Monad m => [(String, [OptDescr (Result m -> Result m)])]-documentedConfigFileOptions = [+otherOptions :: [(String, FormatConfig -> IO Format)] -> [(String, [Declarative.Option Config])]+otherOptions formatters = [ ("RUNNER OPTIONS", runnerOptions)- , ("FORMATTER OPTIONS", formatterOptions)+ , ("FORMATTER OPTIONS", formatterOptions formatters) , ("OPTIONS FOR QUICKCHECK", quickCheckOptions) , ("OPTIONS FOR SMALLCHECK", smallCheckOptions) ] -documentedOptions :: [(String, [OptDescr (Result Maybe -> Result Maybe)])]-documentedOptions = ("OPTIONS", commandLineOptions) : documentedConfigFileOptions--configFileOptions :: Monad m => [OptDescr (Result m -> Result m)]-configFileOptions = (concat . map snd) documentedConfigFileOptions--set :: Monad m => (Config -> Config) -> Either a (m Config) -> Either a (m Config)-set = liftM . liftM--undocumentedOptions :: Monad m => [OptDescr (Result m -> Result m)]-undocumentedOptions = [- -- for compatibility with test-framework- mkOption [] "maximum-generated-tests" (Arg "NUMBER" readMaybe setMaxSuccess) "how many automated tests something like QuickCheck should try, by default"-- -- undocumented for now, as we probably want to change this to produce a- -- standalone HTML report in the future- , Option [] ["html"] (NoArg setHtml) "produce HTML output"-- , mkOption "o" "out" (Arg "FILE" return setOutputFile) "write output to a file instead of STDOUT"-- -- now a noop- , Option "v" ["verbose"] (NoArg id) "do not suppress output to stdout when evaluating examples"- ]- where- setHtml = set $ \config -> config {configHtmlOutput = True}-- setOutputFile :: String -> Config -> Config- setOutputFile file c = c {configOutputFile = Right file}--recognizedOptions :: [OptDescr (Result Maybe -> Result Maybe)]-recognizedOptions = commandLineOptions ++ configFileOptions ++ undocumentedOptions+ignoreConfigFile :: Config -> [String] -> IO Bool+ignoreConfigFile config args = do+ ignore <- lookupEnv "IGNORE_DOT_HSPEC"+ case ignore of+ Just _ -> return True+ Nothing -> case parseCommandLineOptions "" args config of+ Right c -> return (configIgnoreConfigFile c)+ _ -> return False -parseOptions :: Config -> String -> [ConfigFile] -> Maybe EnvVar -> [String] -> Either (ExitCode, String) Config-parseOptions config prog configFiles envVar args = do+parseOptions :: Config -> String -> [ConfigFile] -> Maybe EnvVar -> [(String, String)] -> [String] -> Either (ExitCode, String) ([String], Config)+parseOptions config prog configFiles envVar env args = do foldM (parseFileOptions prog) config configFiles- >>= parseEnvVarOptions prog envVar- >>= parseCommandLineOptions prog args+ >>= maybe return (parseEnvVarOptions prog) envVar+ >>= parseEnvironmentOptions env+ >>= traverseTuple (parseCommandLineOptions prog args) +traverseTuple :: Applicative f => (a -> f b) -> (c, a) -> f (c, b)+#if MIN_VERSION_base(4,7,0)+traverseTuple = traverse+#else+traverseTuple f (c, a) = (,) c <$> f a+#endif+ parseCommandLineOptions :: String -> [String] -> Config -> Either (ExitCode, String) Config-parseCommandLineOptions prog args config = case parse recognizedOptions config args of- Right Nothing -> Left (ExitSuccess, usage)- Right (Just c) -> Right c- Left err -> failure err+parseCommandLineOptions prog args config = case Declarative.parseCommandLineOptions (commandLineOptions formatters) prog args config of+ Success c -> Right c+ Help message -> Left (ExitSuccess, message)+ Failure message -> Left (ExitFailure 1, message) where- failure err = Left (ExitFailure 1, prog ++ ": " ++ err ++ "\nTry `" ++ prog ++ " --help' for more information.\n")+ formatters = configAvailableFormatters config - usage :: String- usage = "Usage: " ++ prog ++ " [OPTION]...\n\n"- ++ (intercalate "\n" $ map (uncurry mkUsageInfo) documentedOptions)+parseEnvironmentOptions :: [(String, String)] -> Config -> Either (ExitCode, String) ([String], Config)+parseEnvironmentOptions env config = case Declarative.parseEnvironmentOptions "HSPEC" env config (concatMap snd $ commandLineOptions formatters) of+ (warnings, c) -> Right (map formatWarning warnings, c)+ where+ formatters = configAvailableFormatters config+ formatWarning (Declarative.InvalidValue name value) = "invalid value `" ++ value ++ "' for environment variable " ++ name parseFileOptions :: String -> Config -> ConfigFile -> Either (ExitCode, String) Config parseFileOptions prog config (name, args) = parseOtherOptions prog ("in config file " ++ name) args config -parseEnvVarOptions :: String -> (Maybe EnvVar) -> Config -> Either (ExitCode, String) Config-parseEnvVarOptions prog args =- parseOtherOptions prog ("from environment variable " ++ envVarName) (fromMaybe [] args)+parseEnvVarOptions :: String -> EnvVar -> Config -> Either (ExitCode, String) Config+parseEnvVarOptions prog =+ parseOtherOptions prog ("from environment variable " ++ envVarName) parseOtherOptions :: String -> String -> [String] -> Config -> Either (ExitCode, String) Config-parseOtherOptions prog source args config = case parse configFileOptions config args of- Right (Identity c) -> Right c+parseOtherOptions prog source args config = case parse (interpretOptions options) config args of+ Right c -> Right c Left err -> failure err where+ options :: [Declarative.Option Config]+ options = filter Declarative.optionDocumented $ concatMap snd (otherOptions formatters)++ formatters = configAvailableFormatters config+ failure err = Left (ExitFailure 1, prog ++ ": " ++ message) where message = unlines $ case lines err of [x] -> [x ++ " " ++ source] xs -> xs ++ [source]--parse :: Monad m => [OptDescr (Result m -> Result m)] -> Config -> [String] -> Either String (m Config)-parse options config args = case getOpt Permute options args of- (opts, [], []) -> case foldl' (flip id) (Right $ return config) opts of- Left (InvalidArgument name value) -> Left ("invalid argument `" ++ value ++ "' for `--" ++ name ++ "'")- Right x -> Right x- (_, _, err:_) -> Left (init err)- (_, arg:_, _) -> Left ("unexpected argument `" ++ arg ++ "'")--ignoreConfigFile :: Config -> [String] -> IO Bool-ignoreConfigFile config args = do- ignore <- lookupEnv "IGNORE_DOT_HSPEC"- case ignore of- Just _ -> return True- Nothing -> case parse recognizedOptions config args of- Right (Just c) -> return (configIgnoreConfigFile c)- _ -> return False
− hspec-core/src/Test/Hspec/Core/Config/Util.hs
@@ -1,37 +0,0 @@-module Test.Hspec.Core.Config.Util where--import System.Console.GetOpt--import Test.Hspec.Core.Util--modifyHelp :: (String -> String) -> OptDescr a -> OptDescr a-modifyHelp modify (Option s n a help) = Option s n a (modify help)--mkUsageInfo :: String -> [OptDescr a] -> String-mkUsageInfo title = usageInfo title . addLineBreaksForHelp . condenseNoOptions--addLineBreaksForHelp :: [OptDescr a] -> [OptDescr a]-addLineBreaksForHelp options = map (modifyHelp addLineBreaks) options- where- withoutHelpWidth = maxLength . usageInfo "" . map removeHelp- helpWidth = 80 - withoutHelpWidth options-- addLineBreaks = unlines . lineBreaksAt helpWidth-- maxLength = maximum . map length . lines- removeHelp = modifyHelp (const "")--condenseNoOptions :: [OptDescr a] -> [OptDescr a]-condenseNoOptions options = case options of- Option "" [optionA] arg help : Option "" [optionB] _ _ : ys | optionB == ("no-" ++ optionA) ->- Option "" ["[no-]" ++ optionA] arg help : condenseNoOptions ys- x : xs -> x : condenseNoOptions xs- [] -> []--formatOrList :: [String] -> String-formatOrList xs = case xs of- [] -> ""- x : ys -> (case ys of- [] -> x- _ : [] -> x ++ " or "- _ : _ : _ -> x ++ ", ") ++ formatOrList ys
hspec-core/src/Test/Hspec/Core/Example.hs view
@@ -21,6 +21,9 @@ , safeEvaluateExample ) where +import Prelude ()+import Test.Hspec.Core.Compat+ import qualified Test.HUnit.Lang as HUnit import Data.CallStack@@ -36,7 +39,6 @@ import Test.Hspec.Core.QuickCheckUtil import Test.Hspec.Core.Util-import Test.Hspec.Core.Compat import Test.Hspec.Core.Example.Location -- | A type class for examples
hspec-core/src/Test/Hspec/Core/Example/Location.hs view
@@ -14,7 +14,6 @@ import Test.Hspec.Core.Compat import Control.Exception-import Data.List import Data.Char import Data.Maybe import GHC.IO.Exception@@ -77,7 +76,11 @@ fromPatternMatchFailureInDoExpression :: String -> Maybe Location fromPatternMatchFailureInDoExpression input =+#if MIN_VERSION_base(4,16,0)+ stripPrefix "Pattern match failure in 'do' block at " input >>= parseSourceSpan+#else stripPrefix "Pattern match failure in do expression at " input >>= parseSourceSpan+#endif parseCallStack :: String -> Maybe Location parseCallStack input = case reverse (lines input) of
hspec-core/src/Test/Hspec/Core/FailureReport.hs view
@@ -9,13 +9,13 @@ import Test.Hspec.Core.Compat #ifndef __GHCJS__-import System.SetEnv+import System.SetEnv (setEnv) import Test.Hspec.Core.Util (safeTry) #endif import System.IO import System.Directory import Test.Hspec.Core.Util (Path)-import Test.Hspec.Core.Config.Options (Config(..))+import Test.Hspec.Core.Config.Definition (Config(..)) data FailureReport = FailureReport { failureReportSeed :: Integer
hspec-core/src/Test/Hspec/Core/Format.hs view
@@ -1,6 +1,11 @@ {-# LANGUAGE RankNTypes #-}+{-# LANGUAGE ExistentialQuantification #-}+-- |+-- Stability: experimental module Test.Hspec.Core.Format (- Format(..)+ Format+, FormatConfig(..)+, Event(..) , Progress , Path , Location(..)@@ -8,13 +13,25 @@ , Item(..) , Result(..) , FailureReason(..)+, monadic ) where +import Prelude ()+import Test.Hspec.Core.Compat++import Control.Exception+import Control.Concurrent+import Control.Concurrent.Async (async)+import qualified Control.Concurrent.Async as Async+import Control.Monad.IO.Class+ import Test.Hspec.Core.Spec (Progress, Location(..)) import Test.Hspec.Core.Example (FailureReason(..)) import Test.Hspec.Core.Util (Path)-import Test.Hspec.Core.Clock+import Test.Hspec.Core.Clock (Seconds(..)) +type Format = Event -> IO ()+ data Item = Item { itemLocation :: Maybe Location , itemDuration :: Seconds@@ -24,14 +41,72 @@ data Result = Success- | Pending (Maybe String)- | Failure FailureReason+ | Pending (Maybe Location) (Maybe String)+ | Failure (Maybe Location) FailureReason deriving Show -data Format m = Format {- formatRun :: forall a. m a -> IO a-, formatGroupStarted :: Path -> m ()-, formatGroupDone :: Path -> m ()-, formatProgress :: Path -> Progress -> m ()-, formatItem :: Path -> Item -> m ()-}+data Event =+ Started+ | GroupStarted Path+ | GroupDone Path+ | Progress Path Progress+ | ItemStarted Path+ | ItemDone Path Item+ | Done [(Path, Item)]+ deriving Show++data FormatConfig = FormatConfig {+ formatConfigUseColor :: Bool+, formatConfigOutputUnicode :: Bool+, formatConfigUseDiff :: Bool+, formatConfigPrintTimes :: Bool+, formatConfigHtmlOutput :: Bool+, formatConfigPrintCpuTime :: Bool+, formatConfigUsedSeed :: Integer+, formatConfigExpectedTotalCount :: Int+} deriving (Eq, Show)++data Signal = Ok | NotOk SomeException++monadic :: MonadIO m => (m () -> IO ()) -> (Event -> m ()) -> IO Format+monadic run format = do+ mvar <- newEmptyMVar+ done <- newEmptyMVar++ let+ putEvent :: Event -> IO ()+ putEvent = putMVar mvar++ takeEvent :: MonadIO m => m Event+ takeEvent = liftIO $ takeMVar mvar++ signal :: MonadIO m => Signal -> m ()+ signal = liftIO . putMVar done++ wait :: IO Signal+ wait = takeMVar done++ go = do+ event <- takeEvent+ format event+ case event of+ Done {} -> return ()+ _ -> do+ signal Ok+ go++ t <- async $ do+ (run go >> signal Ok) `catch` (signal . NotOk)++ return $ \ event -> do+ running <- Async.poll t+ case running of+ Just _ -> return ()+ Nothing -> do+ putEvent event+ r <- wait+ case r of+ Ok -> return ()+ NotOk err -> do+ Async.wait t+ throwIO err
hspec-core/src/Test/Hspec/Core/Formatters.hs view
@@ -1,278 +1,2 @@-{-# LANGUAGE CPP #-}--- |--- Stability: experimental------ This module contains formatters that can be used with--- `Test.Hspec.Core.Runner.hspecWith`.-module Test.Hspec.Core.Formatters (---- * Formatters- silent-, specdoc-, progress-, failed_examples---- * Implementing a custom Formatter--- |--- A formatter is a set of actions. Each action is evaluated when a certain--- situation is encountered during a test run.------ Actions live in the `FormatM` monad. It provides access to the runner state--- and primitives for appending to the generated report.-, Formatter (..)-, FailureReason (..)-, FormatM---- ** Accessing the runner state-, getSuccessCount-, getPendingCount-, getFailCount-, getTotalCount--, FailureRecord (..)-, getFailMessages-, usedSeed--, Seconds(..)-, getCPUTime-, getRealTime---- ** Appending to the generated report-, write-, writeLine-, writeTransient---- ** Dealing with colors-, withInfoColor-, withSuccessColor-, withPendingColor-, withFailColor--, useDiff-, extraChunk-, missingChunk---- ** Helpers-, formatException-) where--import Prelude ()-import Test.Hspec.Core.Compat hiding (First)--import Data.Maybe-import Test.Hspec.Core.Util-import Test.Hspec.Core.Clock-import Test.Hspec.Core.Spec (Location(..))-import Text.Printf-import Control.Monad.IO.Class-import Control.Exception---- We use an explicit import list for "Test.Hspec.Formatters.Internal", to make--- sure, that we only use the public API to implement formatters.------ Everything imported here has to be re-exported, so that users can implement--- their own formatters.-import Test.Hspec.Core.Formatters.Monad (- Formatter (..)- , FailureReason (..)- , FormatM-- , getSuccessCount- , getPendingCount- , getFailCount- , getTotalCount-- , FailureRecord (..)- , getFailMessages- , usedSeed-- , getCPUTime- , getRealTime-- , write- , writeLine- , writeTransient-- , withInfoColor- , withSuccessColor- , withPendingColor- , withFailColor-- , useDiff- , extraChunk- , missingChunk- )--import Test.Hspec.Core.Formatters.Diff--silent :: Formatter-silent = Formatter {- headerFormatter = return ()-, exampleGroupStarted = \_ _ -> return ()-, exampleGroupDone = return ()-, exampleProgress = \_ _ -> return ()-, exampleSucceeded = \ _ _ -> return ()-, exampleFailed = \_ _ _ -> return ()-, examplePending = \_ _ _ -> return ()-, failedFormatter = return ()-, footerFormatter = return ()-}--specdoc :: Formatter-specdoc = silent {-- headerFormatter = do- writeLine ""--, exampleGroupStarted = \nesting name -> do- writeLine (indentationFor nesting ++ name)--, exampleProgress = \_ p -> do- writeTransient (formatProgress p)--, exampleSucceeded = \(nesting, requirement) info -> withSuccessColor $ do- writeLine $ indentationFor nesting ++ requirement- forM_ (lines info) $ \ s ->- writeLine $ indentationFor ("" : nesting) ++ s--, exampleFailed = \(nesting, requirement) info _ -> withFailColor $ do- n <- getFailCount- writeLine $ indentationFor nesting ++ requirement ++ " FAILED [" ++ show n ++ "]"- forM_ (lines info) $ \ s ->- writeLine $ indentationFor ("" : nesting) ++ s--, examplePending = \(nesting, requirement) info reason -> withPendingColor $ do- writeLine $ indentationFor nesting ++ requirement- forM_ (lines info) $ \ s ->- writeLine $ indentationFor ("" : nesting) ++ s- writeLine $ indentationFor ("" : nesting) ++ "# PENDING: " ++ fromMaybe "No reason given" reason--, failedFormatter = defaultFailedFormatter--, footerFormatter = defaultFooter-} where- indentationFor nesting = replicate (length nesting * 2) ' '- formatProgress (current, total)- | total == 0 = show current- | otherwise = show current ++ "/" ++ show total---progress :: Formatter-progress = silent {- exampleSucceeded = \_ _ -> withSuccessColor $ write "."-, exampleFailed = \_ _ _ -> withFailColor $ write "F"-, examplePending = \_ _ _ -> withPendingColor $ write "."-, failedFormatter = defaultFailedFormatter-, footerFormatter = defaultFooter-}---failed_examples :: Formatter-failed_examples = silent {- failedFormatter = defaultFailedFormatter-, footerFormatter = defaultFooter-}--defaultFailedFormatter :: FormatM ()-defaultFailedFormatter = do- writeLine ""-- failures <- getFailMessages-- unless (null failures) $ do- writeLine "Failures:"- writeLine ""-- forM_ (zip [1..] failures) $ \x -> do- formatFailure x- writeLine ""--#if __GLASGOW_HASKELL__ == 800- withFailColor $ do- writeLine "WARNING:"- writeLine " Your version of GHC is affected by https://ghc.haskell.org/trac/ghc/ticket/13285."- writeLine " Source locations may not work as expected."- writeLine ""- writeLine " Please consider upgrading GHC!"- writeLine ""-#endif-- write "Randomized with seed " >> usedSeed >>= writeLine . show- writeLine ""- where- formatFailure :: (Int, FailureRecord) -> FormatM ()- formatFailure (n, FailureRecord mLoc path reason) = do- forM_ mLoc $ \loc -> do- withInfoColor $ writeLine (formatLoc loc)- write (" " ++ show n ++ ") ")- writeLine (formatRequirement path)- case reason of- NoReason -> return ()- Reason err -> withFailColor $ indent err- ExpectedButGot preface expected actual -> do- mapM_ indent preface-- b <- useDiff-- let threshold = 2 :: Seconds-- mchunks <- liftIO $ if b- then timeout threshold (evaluate $ diff expected actual)- else return Nothing-- case mchunks of- Just chunks -> do- writeDiff chunks extraChunk missingChunk- Nothing -> do- writeDiff [First expected, Second actual] write write- where- indented output text = case break (== '\n') text of- (xs, "") -> output xs- (xs, _ : ys) -> output (xs ++ "\n") >> write (indentation ++ " ") >> indented output ys-- writeDiff chunks extra missing = do- withFailColor $ write (indentation ++ "expected: ")- forM_ chunks $ \ chunk -> case chunk of- Both a _ -> indented write a- First a -> indented extra a- Second _ -> return ()- writeLine ""-- withFailColor $ write (indentation ++ " but got: ")- forM_ chunks $ \ chunk -> case chunk of- Both a _ -> indented write a- First _ -> return ()- Second a -> indented missing a- writeLine ""-- Error _ e -> withFailColor . indent $ (("uncaught exception: " ++) . formatException) e-- writeLine ""- writeLine (" To rerun use: --match " ++ show (joinPath path))- where- indentation = " "- indent message = do- forM_ (lines message) $ \line -> do- writeLine (indentation ++ line)- formatLoc (Location file line column) = " " ++ file ++ ":" ++ show line ++ ":" ++ show column ++ ": "--defaultFooter :: FormatM ()-defaultFooter = do-- writeLine =<< (++)- <$> (printf "Finished in %1.4f seconds" <$> getRealTime)- <*> (maybe "" (printf ", used %1.4f seconds of CPU time") <$> getCPUTime)-- fails <- getFailCount- pending <- getPendingCount- total <- getTotalCount-- let- output =- pluralize total "example"- ++ ", " ++ pluralize fails "failure"- ++ if pending == 0 then "" else ", " ++ show pending ++ " pending"- c | fails /= 0 = withFailColor- | pending /= 0 = withPendingColor- | otherwise = withSuccessColor- c $ writeLine output+module Test.Hspec.Core.Formatters (module V1) where+import Test.Hspec.Core.Formatters.V1 as V1
hspec-core/src/Test/Hspec/Core/Formatters/Diff.hs view
@@ -13,7 +13,6 @@ import Test.Hspec.Core.Compat import Data.Char-import Data.List (stripPrefix) import Data.Algorithm.Diff diff :: String -> String -> [Diff String]
− hspec-core/src/Test/Hspec/Core/Formatters/Free.hs
@@ -1,22 +0,0 @@-{-# LANGUAGE DeriveFunctor #-}-module Test.Hspec.Core.Formatters.Free where--import Prelude ()-import Test.Hspec.Core.Compat--data Free f a = Free (f (Free f a)) | Pure a- deriving Functor--instance Functor f => Applicative (Free f) where- pure = Pure- Pure f <*> Pure a = Pure (f a)- Pure f <*> Free m = Free (fmap f <$> m)- Free m <*> b = Free (fmap (<*> b) m)--instance Functor f => Monad (Free f) where- return = pure- Pure a >>= f = f a- Free m >>= f = Free (fmap (>>= f) m)--liftF :: Functor f => f a -> Free f a-liftF command = Free (fmap Pure command)
hspec-core/src/Test/Hspec/Core/Formatters/Internal.hs view
@@ -1,14 +1,43 @@-{-# LANGUAGE CPP, GeneralizedNewtypeDeriving #-}+{-# LANGUAGE CPP #-}+{-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# LANGUAGE RecordWildCards #-} module Test.Hspec.Core.Formatters.Internal (- FormatM-, FormatConfig(..)-, runFormatM-, interpret-, increaseSuccessCount-, increasePendingCount-, addFailMessage-, finally_+ Formatter(..)+, Item(..)+, Result(..)+, FailureReason(..)+, FormatM , formatterToFormat++, getSuccessCount+, getPendingCount+, getFailCount+, getTotalCount+, getExpectedTotalCount++, FailureRecord(..)+, getFailMessages+, usedSeed++, printTimes+, getCPUTime+, getRealTime++, write+, writeLine+, writeTransient++, withInfoColor+, withSuccessColor+, withPendingColor+, withFailColor++, outputUnicode++, useDiff+, extraChunk+, missingChunk+ #ifdef TEST , overwriteWith #endif@@ -18,65 +47,82 @@ import Test.Hspec.Core.Compat import qualified System.IO as IO-import System.IO (Handle)-import Control.Exception (AsyncException(..), bracket_, try, throwIO)+import System.IO (Handle, stdout)+import Control.Exception (bracket_) import System.Console.ANSI import Control.Monad.Trans.State hiding (state, gets, modify) import Control.Monad.IO.Class import Data.Char (isSpace) import qualified System.CPUTime as CPUTime -import qualified Test.Hspec.Core.Formatters.Monad as M-import Test.Hspec.Core.Formatters.Monad (Environment(..), interpretWith, FailureRecord(..))+import Test.Hspec.Core.Formatters.V1.Monad (FailureRecord(..)) import Test.Hspec.Core.Format import Test.Hspec.Core.Clock -formatterToFormat :: M.Formatter -> FormatConfig -> Format FormatM-formatterToFormat formatter config = Format {- formatRun = \action -> runFormatM config $ do- interpret (M.headerFormatter formatter)- a <- action `finally_` interpret (M.failedFormatter formatter)- interpret (M.footerFormatter formatter)- return a-, formatGroupStarted = \ (nesting, name) -> interpret $ M.exampleGroupStarted formatter nesting name-, formatGroupDone = \ _ -> interpret (M.exampleGroupDone formatter)-, formatProgress = \ path progress -> when useColor $ do- interpret $ M.exampleProgress formatter path progress-, formatItem = \ path (Item loc _duration info result) -> do- clearTransientOutput- case result of- Success -> do- increaseSuccessCount- interpret $ M.exampleSucceeded formatter path info- Pending reason -> do- increasePendingCount- interpret $ M.examplePending formatter path info reason- Failure err -> do- addFailMessage loc path err- interpret $ M.exampleFailed formatter path info err-} where- useColor = formatConfigUseColor config+data Formatter = Formatter {+-- | evaluated before a test run+ formatterStarted :: FormatM () -interpret :: M.FormatM a -> FormatM a-interpret = interpretWith Environment {- environmentGetSuccessCount = getSuccessCount-, environmentGetPendingCount = getPendingCount-, environmentGetFailMessages = getFailMessages-, environmentUsedSeed = usedSeed-, environmentGetCPUTime = getCPUTime-, environmentGetRealTime = getRealTime-, environmentWrite = write-, environmentWriteTransient = writeTransient-, environmentWithFailColor = withFailColor-, environmentWithSuccessColor = withSuccessColor-, environmentWithPendingColor = withPendingColor-, environmentWithInfoColor = withInfoColor-, environmentUseDiff = gets (formatConfigUseDiff . stateConfig)-, environmentExtraChunk = extraChunk-, environmentMissingChunk = missingChunk-, environmentLiftIO = liftIO+-- | evaluated before each spec group+, formatterGroupStarted :: Path -> FormatM ()++-- | evaluated after each spec group+, formatterGroupDone :: Path -> FormatM ()++-- | used to notify the progress of the currently evaluated example+, formatterProgress :: Path -> Progress -> FormatM ()++-- | evaluated before each spec item+, formatterItemStarted :: Path -> FormatM ()++-- | evaluated after each spec item+, formatterItemDone :: Path -> Item -> FormatM ()++-- | evaluated after a test run+, formatterDone :: FormatM () } +formatterToFormat :: Formatter -> FormatConfig -> IO Format+formatterToFormat Formatter{..} config = monadic (runFormatM config) $ \ event -> case event of+ Started -> formatterStarted+ GroupStarted path -> formatterGroupStarted path+ GroupDone path -> formatterGroupDone path+ Progress path progress -> formatterProgress path progress+ ItemStarted path -> formatterItemStarted path+ ItemDone path item -> do+ clearTransientOutput+ case itemResult item of+ Success {} -> increaseSuccessCount+ Pending {} -> increasePendingCount+ Failure loc err -> addFailMessage (loc <|> itemLocation item) path err+ formatterItemDone path item+ Done _ -> formatterDone++-- | Get the number of failed examples encountered so far.+getFailCount :: FormatM Int+getFailCount = length <$> getFailMessages++-- | Return `True` if the user requested colorized diffs, `False` otherwise.+useDiff :: FormatM Bool+useDiff = getConfig formatConfigUseDiff++-- | Return `True` if the user requested unicode output, `False` otherwise.+outputUnicode :: FormatM Bool+outputUnicode = getConfig formatConfigOutputUnicode++-- | The same as `write`, but adds a newline character.+writeLine :: String -> FormatM ()+writeLine s = write s >> write "\n"++-- | Return `True` if the user requested time reporting for individual spec+-- items, `False` otherwise.+printTimes :: FormatM Bool+printTimes = gets (formatConfigPrintTimes . stateConfig)++-- | Get the total number of examples encountered so far.+getTotalCount :: FormatM Int+getTotalCount = sum <$> sequence [getSuccessCount, getFailCount, getPendingCount]+ -- | A lifted version of `Control.Monad.Trans.State.gets` gets :: (FormatterState -> a) -> FormatM a gets f = FormatM $ do@@ -87,15 +133,6 @@ modify f = FormatM $ do get >>= liftIO . (`modifyIORef'` f) -data FormatConfig = FormatConfig {- formatConfigHandle :: Handle-, formatConfigUseColor :: Bool-, formatConfigUseDiff :: Bool-, formatConfigHtmlOutput :: Bool-, formatConfigPrintCpuTime :: Bool-, formatConfigUsedSeed :: Integer-} deriving (Eq, Show)- data FormatterState = FormatterState { stateSuccessCount :: !Int , statePendingCount :: !Int@@ -110,7 +147,7 @@ getConfig f = gets (f . stateConfig) getHandle :: FormatM Handle-getHandle = getConfig formatConfigHandle+getHandle = return stdout -- | The random seed that is used for QuickCheck. usedSeed :: FormatM Integer@@ -152,6 +189,11 @@ getFailMessages :: FormatM [FailureRecord] getFailMessages = reverse `fmap` gets stateFailMessages +-- | Get the number of spec items that will have been encountered when this run+-- completes (if it is not terminated early).+getExpectedTotalCount :: FormatM Int+getExpectedTotalCount = getConfig formatConfigExpectedTotalCount+ overwriteWith :: String -> String -> String overwriteWith old new | n == 0 = new@@ -161,11 +203,13 @@ writeTransient :: String -> FormatM () writeTransient new = do- old <- gets stateTransientOutput- write $ old `overwriteWith` new- modify $ \ state -> state {stateTransientOutput = new}- h <- getHandle- liftIO $ IO.hFlush h+ useColor <- getConfig formatConfigUseColor+ when (useColor) $ do+ old <- gets stateTransientOutput+ write $ old `overwriteWith` new+ modify $ \ state -> state {stateTransientOutput = new}+ h <- getHandle+ liftIO $ IO.hFlush h clearTransientOutput :: FormatM () clearTransientOutput = do@@ -229,8 +273,8 @@ -- | Output given chunk in red. extraChunk :: String -> FormatM () extraChunk s = do- useDiff <- getConfig formatConfigUseDiff- case useDiff of+ diff <- getConfig formatConfigUseDiff+ case diff of True -> extra s False -> write s where@@ -240,8 +284,8 @@ -- | Output given chunk in green. missingChunk :: String -> FormatM () missingChunk s = do- useDiff <- getConfig formatConfigUseDiff- case useDiff of+ diff <- getConfig formatConfigUseDiff+ case diff of True -> missing s False -> write s where@@ -255,22 +299,6 @@ layer | all isSpace s = Background | otherwise = Foreground---- |--- @finally_ actionA actionB@ runs @actionA@ and then @actionB@. @actionB@ is--- run even when a `UserInterrupt` occurs during @actionA@.-finally_ :: FormatM a -> FormatM () -> FormatM a-finally_ (FormatM actionA) (FormatM actionB) = FormatM . StateT $ \st -> do- r <- try (runStateT actionA st)- case r of- Left e -> do- when (e == UserInterrupt) $- runStateT actionB st >> return ()- throwIO e- Right (a, st_) -> do- runStateT actionB st_ >>= return . replaceValue a- where- replaceValue a (_, st) = (a, st) -- | Get the used CPU time since the test run has been started. getCPUTime :: FormatM (Maybe Seconds)
− hspec-core/src/Test/Hspec/Core/Formatters/Monad.hs
@@ -1,246 +0,0 @@-{-# LANGUAGE FlexibleInstances #-}-{-# LANGUAGE RankNTypes #-}-{-# LANGUAGE ScopedTypeVariables #-}-{-# LANGUAGE RecordWildCards #-}-{-# LANGUAGE StandaloneDeriving #-}-{-# LANGUAGE ExistentialQuantification #-}-module Test.Hspec.Core.Formatters.Monad (- Formatter (..)-, FailureReason (..)-, FormatM--, getSuccessCount-, getPendingCount-, getFailCount-, getTotalCount--, FailureRecord (..)-, getFailMessages-, usedSeed--, getCPUTime-, getRealTime--, write-, writeLine-, writeTransient--, withInfoColor-, withSuccessColor-, withPendingColor-, withFailColor--, useDiff-, extraChunk-, missingChunk--, Environment(..)-, interpretWith-) where--import Prelude ()-import Test.Hspec.Core.Compat--import Control.Monad.IO.Class--import Test.Hspec.Core.Formatters.Free-import Test.Hspec.Core.Example (FailureReason(..))-import Test.Hspec.Core.Util (Path)-import Test.Hspec.Core.Spec (Progress, Location)-import Test.Hspec.Core.Clock--data Formatter = Formatter {-- headerFormatter :: FormatM ()---- | evaluated before each test group-, exampleGroupStarted :: [String] -> String -> FormatM ()--, exampleGroupDone :: FormatM ()---- | used to notify the progress of the currently evaluated example------ /Note/: This is only called when interactive/color mode.-, exampleProgress :: Path -> Progress -> FormatM ()---- | evaluated after each successful example-, exampleSucceeded :: Path -> String -> FormatM ()---- | evaluated after each failed example-, exampleFailed :: Path -> String -> FailureReason -> FormatM ()---- | evaluated after each pending example-, examplePending :: Path -> String -> Maybe String -> FormatM ()---- | evaluated after a test run-, failedFormatter :: FormatM ()---- | evaluated after `failuresFormatter`-, footerFormatter :: FormatM ()-}--data FailureRecord = FailureRecord {- failureRecordLocation :: Maybe Location-, failureRecordPath :: Path-, failureRecordMessage :: FailureReason-}--data FormatF next =- GetSuccessCount (Int -> next)- | GetPendingCount (Int -> next)- | GetFailMessages ([FailureRecord] -> next)- | UsedSeed (Integer -> next)- | GetCPUTime (Maybe Seconds -> next)- | GetRealTime (Seconds -> next)- | Write String next- | WriteTransient String next- | forall a. WithFailColor (FormatM a) (a -> next)- | forall a. WithSuccessColor (FormatM a) (a -> next)- | forall a. WithPendingColor (FormatM a) (a -> next)- | forall a. WithInfoColor (FormatM a) (a -> next)- | UseDiff (Bool -> next)- | ExtraChunk String next- | MissingChunk String next- | forall a. LiftIO (IO a) (a -> next)--instance Functor FormatF where -- deriving this instance would require GHC >= 7.10.1- fmap f x = case x of- GetSuccessCount next -> GetSuccessCount (fmap f next)- GetPendingCount next -> GetPendingCount (fmap f next)- GetFailMessages next -> GetFailMessages (fmap f next)- UsedSeed next -> UsedSeed (fmap f next)- GetCPUTime next -> GetCPUTime (fmap f next)- GetRealTime next -> GetRealTime (fmap f next)- Write s next -> Write s (f next)- WriteTransient s next -> WriteTransient s (f next)- WithFailColor action next -> WithFailColor action (fmap f next)- WithSuccessColor action next -> WithSuccessColor action (fmap f next)- WithPendingColor action next -> WithPendingColor action (fmap f next)- WithInfoColor action next -> WithInfoColor action (fmap f next)- UseDiff next -> UseDiff (fmap f next)- ExtraChunk s next -> ExtraChunk s (f next)- MissingChunk s next -> MissingChunk s (f next)- LiftIO action next -> LiftIO action (fmap f next)--type FormatM = Free FormatF--instance MonadIO FormatM where- liftIO s = liftF (LiftIO s id)--data Environment m = Environment {- environmentGetSuccessCount :: m Int-, environmentGetPendingCount :: m Int-, environmentGetFailMessages :: m [FailureRecord]-, environmentUsedSeed :: m Integer-, environmentGetCPUTime :: m (Maybe Seconds)-, environmentGetRealTime :: m Seconds-, environmentWrite :: String -> m ()-, environmentWriteTransient :: String -> m ()-, environmentWithFailColor :: forall a. m a -> m a-, environmentWithSuccessColor :: forall a. m a -> m a-, environmentWithPendingColor :: forall a. m a -> m a-, environmentWithInfoColor :: forall a. m a -> m a-, environmentUseDiff :: m Bool-, environmentExtraChunk :: String -> m ()-, environmentMissingChunk :: String -> m ()-, environmentLiftIO :: forall a. IO a -> m a-}--interpretWith :: forall m a. Monad m => Environment m -> FormatM a -> m a-interpretWith Environment{..} = go- where- go :: forall b. FormatM b -> m b- go m = case m of- Pure value -> return value- Free action -> case action of- GetSuccessCount next -> environmentGetSuccessCount >>= go . next- GetPendingCount next -> environmentGetPendingCount >>= go . next- GetFailMessages next -> environmentGetFailMessages >>= go . next- UsedSeed next -> environmentUsedSeed >>= go . next- GetCPUTime next -> environmentGetCPUTime >>= go . next- GetRealTime next -> environmentGetRealTime >>= go . next- Write s next -> environmentWrite s >> go next- WriteTransient s next -> environmentWriteTransient s >> go next- WithFailColor inner next -> environmentWithFailColor (go inner) >>= go . next- WithSuccessColor inner next -> environmentWithSuccessColor (go inner) >>= go . next- WithPendingColor inner next -> environmentWithPendingColor (go inner) >>= go . next- WithInfoColor inner next -> environmentWithInfoColor (go inner) >>= go . next- UseDiff next -> environmentUseDiff >>= go . next- ExtraChunk s next -> environmentExtraChunk s >> go next- MissingChunk s next -> environmentMissingChunk s >> go next- LiftIO inner next -> environmentLiftIO inner >>= go . next---- | Get the number of successful examples encountered so far.-getSuccessCount :: FormatM Int-getSuccessCount = liftF (GetSuccessCount id)---- | Get the number of pending examples encountered so far.-getPendingCount :: FormatM Int-getPendingCount = liftF (GetPendingCount id)---- | Get the number of failed examples encountered so far.-getFailCount :: FormatM Int-getFailCount = length <$> getFailMessages---- | Get the total number of examples encountered so far.-getTotalCount :: FormatM Int-getTotalCount = sum <$> sequence [getSuccessCount, getFailCount, getPendingCount]---- | Get the list of accumulated failure messages.-getFailMessages :: FormatM [FailureRecord]-getFailMessages = liftF (GetFailMessages id)---- | The random seed that is used for QuickCheck.-usedSeed :: FormatM Integer-usedSeed = liftF (UsedSeed id)---- | Get the used CPU time since the test run has been started.-getCPUTime :: FormatM (Maybe Seconds)-getCPUTime = liftF (GetCPUTime id)---- | Get the passed real time since the test run has been started.-getRealTime :: FormatM Seconds-getRealTime = liftF (GetRealTime id)---- | Append some output to the report.-write :: String -> FormatM ()-write s = liftF (Write s ())---- | The same as `write`, but adds a newline character.-writeLine :: String -> FormatM ()-writeLine s = write s >> write "\n"--writeTransient :: String -> FormatM ()-writeTransient s = liftF (WriteTransient s ())---- | Set output color to red, run given action, and finally restore the default--- color.-withFailColor :: FormatM a -> FormatM a-withFailColor s = liftF (WithFailColor s id)---- | Set output color to green, run given action, and finally restore the--- default color.-withSuccessColor :: FormatM a -> FormatM a-withSuccessColor s = liftF (WithSuccessColor s id)---- | Set output color to yellow, run given action, and finally restore the--- default color.-withPendingColor :: FormatM a -> FormatM a-withPendingColor s = liftF (WithPendingColor s id)---- | Set output color to cyan, run given action, and finally restore the--- default color.-withInfoColor :: FormatM a -> FormatM a-withInfoColor s = liftF (WithInfoColor s id)---- | Return `True` if the user requested colorized diffs, `False` otherwise.-useDiff :: FormatM Bool-useDiff = liftF (UseDiff id)---- | Output given chunk in red.-extraChunk :: String -> FormatM ()-extraChunk s = liftF (ExtraChunk s ())---- | Output given chunk in green.-missingChunk :: String -> FormatM ()-missingChunk s = liftF (MissingChunk s ())
+ hspec-core/src/Test/Hspec/Core/Formatters/V1.hs view
@@ -0,0 +1,349 @@+{-# LANGUAGE CPP #-}+{-# LANGUAGE RecordWildCards #-}+-- |+-- Stability: deprecated+--+-- This module contains formatters that can be used with+-- `Test.Hspec.Core.Runner.hspecWith`.+module Test.Hspec.Core.Formatters.V1 (++-- * Formatters+ silent+, checks+, specdoc+, progress+, failed_examples++-- * Implementing a custom Formatter+-- |+-- A formatter is a set of actions. Each action is evaluated when a certain+-- situation is encountered during a test run.+--+-- Actions live in the `FormatM` monad. It provides access to the runner state+-- and primitives for appending to the generated report.+, Formatter (..)+, FailureReason (..)+, FormatM+, formatterToFormat++-- ** Accessing the runner state+, getSuccessCount+, getPendingCount+, getFailCount+, getTotalCount++, FailureRecord (..)+, getFailMessages+, usedSeed++, Seconds(..)+, getCPUTime+, getRealTime++-- ** Appending to the generated report+, write+, writeLine+, writeTransient++-- ** Dealing with colors+, withInfoColor+, withSuccessColor+, withPendingColor+, withFailColor++, useDiff+, extraChunk+, missingChunk++-- ** Helpers+, formatException+) where++import Prelude ()+import Test.Hspec.Core.Compat hiding (First)++import Data.Maybe+import Test.Hspec.Core.Util+import Test.Hspec.Core.Clock+import Test.Hspec.Core.Spec (Location(..))+import Text.Printf+import Control.Monad.IO.Class+import Control.Exception++-- We use an explicit import list for "Test.Hspec.Formatters.Internal", to make+-- sure, that we only use the public API to implement formatters.+--+-- Everything imported here has to be re-exported, so that users can implement+-- their own formatters.+import Test.Hspec.Core.Formatters.V1.Monad (+ Formatter(..)+ , FailureReason(..)+ , FormatM++ , getSuccessCount+ , getPendingCount+ , getFailCount+ , getTotalCount++ , FailureRecord(..)+ , getFailMessages+ , usedSeed++ , getCPUTime+ , getRealTime++ , write+ , writeLine+ , writeTransient++ , withInfoColor+ , withSuccessColor+ , withPendingColor+ , withFailColor++ , useDiff+ , extraChunk+ , missingChunk+ )++import Test.Hspec.Core.Format (FormatConfig, Format)++import Test.Hspec.Core.Formatters.Diff+import qualified Test.Hspec.Core.Formatters.V2 as V2+import Test.Hspec.Core.Formatters.V1.Monad (Item(..), Result(..), Environment(..), interpretWith)++formatterToFormat :: Formatter -> FormatConfig -> IO Format+formatterToFormat = V2.formatterToFormat . legacyFormatterToFormatter++legacyFormatterToFormatter :: Formatter -> V2.Formatter+legacyFormatterToFormatter Formatter{..} = V2.Formatter {+ V2.formatterStarted = interpret headerFormatter+, V2.formatterGroupStarted = interpret . uncurry exampleGroupStarted+, V2.formatterGroupDone = interpret . const exampleGroupDone+, V2.formatterProgress = \ path -> interpret . exampleProgress path+, V2.formatterItemStarted = interpret . exampleStarted+, V2.formatterItemDone = \ path item -> interpret $ do+ case itemResult item of+ Success -> exampleSucceeded path (itemInfo item)+ Pending _ reason -> examplePending path (itemInfo item) reason+ Failure _ reason -> exampleFailed path (itemInfo item) reason+, V2.formatterDone = interpret $ failedFormatter >> footerFormatter+}++interpret :: FormatM a -> V2.FormatM a+interpret = interpretWith Environment {+ environmentGetSuccessCount = V2.getSuccessCount+, environmentGetPendingCount = V2.getPendingCount+, environmentGetFailMessages = V2.getFailMessages+, environmentUsedSeed = V2.usedSeed+, environmentPrintTimes = V2.printTimes+, environmentGetCPUTime = V2.getCPUTime+, environmentGetRealTime = V2.getRealTime+, environmentWrite = V2.write+, environmentWriteTransient = V2.writeTransient+, environmentWithFailColor = V2.withFailColor+, environmentWithSuccessColor = V2.withSuccessColor+, environmentWithPendingColor = V2.withPendingColor+, environmentWithInfoColor = V2.withInfoColor+, environmentUseDiff = V2.useDiff+, environmentExtraChunk = V2.extraChunk+, environmentMissingChunk = V2.missingChunk+, environmentLiftIO = liftIO+}++silent :: Formatter+silent = Formatter {+ headerFormatter = return ()+, exampleGroupStarted = \_ _ -> return ()+, exampleGroupDone = return ()+, exampleStarted = \_ -> return ()+, exampleProgress = \_ _ -> return ()+, exampleSucceeded = \ _ _ -> return ()+, exampleFailed = \_ _ _ -> return ()+, examplePending = \_ _ _ -> return ()+, failedFormatter = return ()+, footerFormatter = return ()+}++checks :: Formatter+checks = specdoc {+ exampleStarted = \(nesting, requirement) -> do+ writeTransient $ indentationFor nesting ++ requirement ++ " [ ]"++, exampleProgress = \(nesting, requirement) p -> do+ writeTransient $ indentationFor nesting ++ requirement ++ " [" ++ (formatProgress p) ++ "]"++, exampleSucceeded = \(nesting, requirement) info -> do+ writeResult nesting requirement info $ withSuccessColor $ write "✔"++, exampleFailed = \(nesting, requirement) info _ -> do+ writeResult nesting requirement info $ withFailColor $ write "✘"++, examplePending = \(nesting, requirement) info reason -> do+ writeResult nesting requirement info $ withPendingColor $ write "‐"++ withPendingColor $ do+ writeLine $ indentationFor ("" : nesting) ++ "# PENDING: " ++ fromMaybe "No reason given" reason+} where+ indentationFor nesting = replicate (length nesting * 2) ' '++ writeResult :: [String] -> String -> String -> FormatM () -> FormatM ()+ writeResult nesting requirement info action = do+ write $ indentationFor nesting ++ requirement ++ " ["+ action+ writeLine "]"+ forM_ (lines info) $ \ s ->+ writeLine $ indentationFor ("" : nesting) ++ s++ formatProgress (current, total)+ | total == 0 = show current+ | otherwise = show current ++ "/" ++ show total++specdoc :: Formatter+specdoc = silent {++ headerFormatter = do+ writeLine ""++, exampleGroupStarted = \nesting name -> do+ writeLine (indentationFor nesting ++ name)++, exampleProgress = \_ p -> do+ writeTransient (formatProgress p)++, exampleSucceeded = \(nesting, requirement) info -> withSuccessColor $ do+ writeLine $ indentationFor nesting ++ requirement+ forM_ (lines info) $ \ s ->+ writeLine $ indentationFor ("" : nesting) ++ s++, exampleFailed = \(nesting, requirement) info _ -> withFailColor $ do+ n <- getFailCount+ writeLine $ indentationFor nesting ++ requirement ++ " FAILED [" ++ show n ++ "]"+ forM_ (lines info) $ \ s ->+ writeLine $ indentationFor ("" : nesting) ++ s++, examplePending = \(nesting, requirement) info reason -> withPendingColor $ do+ writeLine $ indentationFor nesting ++ requirement+ forM_ (lines info) $ \ s ->+ writeLine $ indentationFor ("" : nesting) ++ s+ writeLine $ indentationFor ("" : nesting) ++ "# PENDING: " ++ fromMaybe "No reason given" reason++, failedFormatter = defaultFailedFormatter++, footerFormatter = defaultFooter+} where+ indentationFor nesting = replicate (length nesting * 2) ' '+ formatProgress (current, total)+ | total == 0 = show current+ | otherwise = show current ++ "/" ++ show total+++progress :: Formatter+progress = silent {+ exampleSucceeded = \_ _ -> withSuccessColor $ write "."+, exampleFailed = \_ _ _ -> withFailColor $ write "F"+, examplePending = \_ _ _ -> withPendingColor $ write "."+, failedFormatter = defaultFailedFormatter+, footerFormatter = defaultFooter+}+++failed_examples :: Formatter+failed_examples = silent {+ failedFormatter = defaultFailedFormatter+, footerFormatter = defaultFooter+}++defaultFailedFormatter :: FormatM ()+defaultFailedFormatter = do+ writeLine ""++ failures <- getFailMessages++ unless (null failures) $ do+ writeLine "Failures:"+ writeLine ""++ forM_ (zip [1..] failures) $ \x -> do+ formatFailure x+ writeLine ""++ write "Randomized with seed " >> usedSeed >>= writeLine . show+ writeLine ""+ where+ formatFailure :: (Int, FailureRecord) -> FormatM ()+ formatFailure (n, FailureRecord mLoc path reason) = do+ forM_ mLoc $ \loc -> do+ withInfoColor $ writeLine (formatLoc loc)+ write (" " ++ show n ++ ") ")+ writeLine (formatRequirement path)+ case reason of+ NoReason -> return ()+ Reason err -> withFailColor $ indent err+ ExpectedButGot preface expected actual -> do+ mapM_ indent preface++ b <- useDiff++ let threshold = 2 :: Seconds++ mchunks <- liftIO $ if b+ then timeout threshold (evaluate $ diff expected actual)+ else return Nothing++ case mchunks of+ Just chunks -> do+ writeDiff chunks extraChunk missingChunk+ Nothing -> do+ writeDiff [First expected, Second actual] write write+ where+ indented output text = case break (== '\n') text of+ (xs, "") -> output xs+ (xs, _ : ys) -> output (xs ++ "\n") >> write (indentation ++ " ") >> indented output ys++ writeDiff chunks extra missing = do+ withFailColor $ write (indentation ++ "expected: ")+ forM_ chunks $ \ chunk -> case chunk of+ Both a _ -> indented write a+ First a -> indented extra a+ Second _ -> return ()+ writeLine ""++ withFailColor $ write (indentation ++ " but got: ")+ forM_ chunks $ \ chunk -> case chunk of+ Both a _ -> indented write a+ First _ -> return ()+ Second a -> indented missing a+ writeLine ""++ Error _ e -> withFailColor . indent $ (("uncaught exception: " ++) . formatException) e++ writeLine ""+ writeLine (" To rerun use: --match " ++ show (joinPath path))+ where+ indentation = " "+ indent message = do+ forM_ (lines message) $ \line -> do+ writeLine (indentation ++ line)+ formatLoc (Location file line column) = " " ++ file ++ ":" ++ show line ++ ":" ++ show column ++ ": "++defaultFooter :: FormatM ()+defaultFooter = do++ writeLine =<< (++)+ <$> (printf "Finished in %1.4f seconds" <$> getRealTime)+ <*> (maybe "" (printf ", used %1.4f seconds of CPU time") <$> getCPUTime)++ fails <- getFailCount+ pending <- getPendingCount+ total <- getTotalCount++ let+ output =+ pluralize total "example"+ ++ ", " ++ pluralize fails "failure"+ ++ if pending == 0 then "" else ", " ++ show pending ++ " pending"+ c | fails /= 0 = withFailColor+ | pending /= 0 = withPendingColor+ | otherwise = withSuccessColor+ c $ writeLine output
+ hspec-core/src/Test/Hspec/Core/Formatters/V1/Free.hs view
@@ -0,0 +1,22 @@+{-# LANGUAGE DeriveFunctor #-}+module Test.Hspec.Core.Formatters.V1.Free where++import Prelude ()+import Test.Hspec.Core.Compat++data Free f a = Free (f (Free f a)) | Pure a+ deriving Functor++instance Functor f => Applicative (Free f) where+ pure = Pure+ Pure f <*> Pure a = Pure (f a)+ Pure f <*> Free m = Free (fmap f <$> m)+ Free m <*> b = Free (fmap (<*> b) m)++instance Functor f => Monad (Free f) where+ return = pure+ Pure a >>= f = f a+ Free m >>= f = Free (fmap (>>= f) m)++liftF :: Functor f => f a -> Free f a+liftF command = Free (fmap Pure command)
+ hspec-core/src/Test/Hspec/Core/Formatters/V1/Monad.hs view
@@ -0,0 +1,258 @@+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE StandaloneDeriving #-}+{-# LANGUAGE ExistentialQuantification #-}+module Test.Hspec.Core.Formatters.V1.Monad (+ Formatter(..)+, Item(..)+, Result(..)+, FailureReason (..)+, FormatM++, getSuccessCount+, getPendingCount+, getFailCount+, getTotalCount++, FailureRecord (..)+, getFailMessages+, usedSeed++, printTimes+, getCPUTime+, getRealTime++, write+, writeLine+, writeTransient++, withInfoColor+, withSuccessColor+, withPendingColor+, withFailColor++, useDiff+, extraChunk+, missingChunk++, Environment(..)+, interpretWith+) where++import Prelude ()+import Test.Hspec.Core.Compat++import Control.Monad.IO.Class++import Test.Hspec.Core.Formatters.V1.Free+import Test.Hspec.Core.Clock+import Test.Hspec.Core.Format++data Formatter = Formatter {++ headerFormatter :: FormatM ()++-- | evaluated before each test group+, exampleGroupStarted :: [String] -> String -> FormatM ()++-- | evaluated after each test group+, exampleGroupDone :: FormatM ()++-- | evaluated before each example+, exampleStarted :: Path -> FormatM ()++-- | used to notify the progress of the currently evaluated example+, exampleProgress :: Path -> Progress -> FormatM ()++-- | evaluated after each successful example+, exampleSucceeded :: Path -> String -> FormatM ()++-- | evaluated after each failed example+, exampleFailed :: Path -> String -> FailureReason -> FormatM ()++-- | evaluated after each pending example+, examplePending :: Path -> String -> Maybe String -> FormatM ()++-- | evaluated after a test run+, failedFormatter :: FormatM ()++-- | evaluated after `failedFormatter`+, footerFormatter :: FormatM ()+}++data FailureRecord = FailureRecord {+ failureRecordLocation :: Maybe Location+, failureRecordPath :: Path+, failureRecordMessage :: FailureReason+}++data FormatF next =+ GetSuccessCount (Int -> next)+ | GetPendingCount (Int -> next)+ | GetFailMessages ([FailureRecord] -> next)+ | UsedSeed (Integer -> next)+ | PrintTimes (Bool -> next)+ | GetCPUTime (Maybe Seconds -> next)+ | GetRealTime (Seconds -> next)+ | Write String next+ | WriteTransient String next+ | forall a. WithFailColor (FormatM a) (a -> next)+ | forall a. WithSuccessColor (FormatM a) (a -> next)+ | forall a. WithPendingColor (FormatM a) (a -> next)+ | forall a. WithInfoColor (FormatM a) (a -> next)+ | UseDiff (Bool -> next)+ | ExtraChunk String next+ | MissingChunk String next+ | forall a. LiftIO (IO a) (a -> next)++instance Functor FormatF where -- deriving this instance would require GHC >= 7.10.1+ fmap f x = case x of+ GetSuccessCount next -> GetSuccessCount (fmap f next)+ GetPendingCount next -> GetPendingCount (fmap f next)+ GetFailMessages next -> GetFailMessages (fmap f next)+ UsedSeed next -> UsedSeed (fmap f next)+ PrintTimes next -> PrintTimes (fmap f next)+ GetCPUTime next -> GetCPUTime (fmap f next)+ GetRealTime next -> GetRealTime (fmap f next)+ Write s next -> Write s (f next)+ WriteTransient s next -> WriteTransient s (f next)+ WithFailColor action next -> WithFailColor action (fmap f next)+ WithSuccessColor action next -> WithSuccessColor action (fmap f next)+ WithPendingColor action next -> WithPendingColor action (fmap f next)+ WithInfoColor action next -> WithInfoColor action (fmap f next)+ UseDiff next -> UseDiff (fmap f next)+ ExtraChunk s next -> ExtraChunk s (f next)+ MissingChunk s next -> MissingChunk s (f next)+ LiftIO action next -> LiftIO action (fmap f next)++type FormatM = Free FormatF++instance MonadIO FormatM where+ liftIO s = liftF (LiftIO s id)++data Environment m = Environment {+ environmentGetSuccessCount :: m Int+, environmentGetPendingCount :: m Int+, environmentGetFailMessages :: m [FailureRecord]+, environmentUsedSeed :: m Integer+, environmentPrintTimes :: m Bool+, environmentGetCPUTime :: m (Maybe Seconds)+, environmentGetRealTime :: m Seconds+, environmentWrite :: String -> m ()+, environmentWriteTransient :: String -> m ()+, environmentWithFailColor :: forall a. m a -> m a+, environmentWithSuccessColor :: forall a. m a -> m a+, environmentWithPendingColor :: forall a. m a -> m a+, environmentWithInfoColor :: forall a. m a -> m a+, environmentUseDiff :: m Bool+, environmentExtraChunk :: String -> m ()+, environmentMissingChunk :: String -> m ()+, environmentLiftIO :: forall a. IO a -> m a+}++interpretWith :: forall m a. Monad m => Environment m -> FormatM a -> m a+interpretWith Environment{..} = go+ where+ go :: forall b. FormatM b -> m b+ go m = case m of+ Pure value -> return value+ Free action -> case action of+ GetSuccessCount next -> environmentGetSuccessCount >>= go . next+ GetPendingCount next -> environmentGetPendingCount >>= go . next+ GetFailMessages next -> environmentGetFailMessages >>= go . next+ UsedSeed next -> environmentUsedSeed >>= go . next+ PrintTimes next -> environmentPrintTimes >>= go . next+ GetCPUTime next -> environmentGetCPUTime >>= go . next+ GetRealTime next -> environmentGetRealTime >>= go . next+ Write s next -> environmentWrite s >> go next+ WriteTransient s next -> environmentWriteTransient s >> go next+ WithFailColor inner next -> environmentWithFailColor (go inner) >>= go . next+ WithSuccessColor inner next -> environmentWithSuccessColor (go inner) >>= go . next+ WithPendingColor inner next -> environmentWithPendingColor (go inner) >>= go . next+ WithInfoColor inner next -> environmentWithInfoColor (go inner) >>= go . next+ UseDiff next -> environmentUseDiff >>= go . next+ ExtraChunk s next -> environmentExtraChunk s >> go next+ MissingChunk s next -> environmentMissingChunk s >> go next+ LiftIO inner next -> environmentLiftIO inner >>= go . next++-- | Get the number of successful examples encountered so far.+getSuccessCount :: FormatM Int+getSuccessCount = liftF (GetSuccessCount id)++-- | Get the number of pending examples encountered so far.+getPendingCount :: FormatM Int+getPendingCount = liftF (GetPendingCount id)++-- | Get the number of failed examples encountered so far.+getFailCount :: FormatM Int+getFailCount = length <$> getFailMessages++-- | Get the total number of examples encountered so far.+getTotalCount :: FormatM Int+getTotalCount = sum <$> sequence [getSuccessCount, getFailCount, getPendingCount]++-- | Get the list of accumulated failure messages.+getFailMessages :: FormatM [FailureRecord]+getFailMessages = liftF (GetFailMessages id)++-- | The random seed that is used for QuickCheck.+usedSeed :: FormatM Integer+usedSeed = liftF (UsedSeed id)++-- | Return `True` if the user requested time reporting for individual spec+-- items, `False` otherwise.+printTimes :: FormatM Bool+printTimes = liftF (PrintTimes id)++-- | Get the used CPU time since the test run has been started.+getCPUTime :: FormatM (Maybe Seconds)+getCPUTime = liftF (GetCPUTime id)++-- | Get the passed real time since the test run has been started.+getRealTime :: FormatM Seconds+getRealTime = liftF (GetRealTime id)++-- | Append some output to the report.+write :: String -> FormatM ()+write s = liftF (Write s ())++-- | The same as `write`, but adds a newline character.+writeLine :: String -> FormatM ()+writeLine s = write s >> write "\n"++writeTransient :: String -> FormatM ()+writeTransient s = liftF (WriteTransient s ())++-- | Set output color to red, run given action, and finally restore the default+-- color.+withFailColor :: FormatM a -> FormatM a+withFailColor s = liftF (WithFailColor s id)++-- | Set output color to green, run given action, and finally restore the+-- default color.+withSuccessColor :: FormatM a -> FormatM a+withSuccessColor s = liftF (WithSuccessColor s id)++-- | Set output color to yellow, run given action, and finally restore the+-- default color.+withPendingColor :: FormatM a -> FormatM a+withPendingColor s = liftF (WithPendingColor s id)++-- | Set output color to cyan, run given action, and finally restore the+-- default color.+withInfoColor :: FormatM a -> FormatM a+withInfoColor s = liftF (WithInfoColor s id)++-- | Return `True` if the user requested colorized diffs, `False` otherwise.+useDiff :: FormatM Bool+useDiff = liftF (UseDiff id)++-- | Output given chunk in red.+extraChunk :: String -> FormatM ()+extraChunk s = liftF (ExtraChunk s ())++-- | Output given chunk in green.+missingChunk :: String -> FormatM ()+missingChunk s = liftF (MissingChunk s ())
+ hspec-core/src/Test/Hspec/Core/Formatters/V2.hs view
@@ -0,0 +1,333 @@+{-# LANGUAGE CPP #-}+-- |+-- Stability: experimental+--+-- This module contains formatters that can be used with+-- `Test.Hspec.Core.Runner.hspecWith`.+module Test.Hspec.Core.Formatters.V2 (++-- * Formatters+ silent+, checks+, specdoc+, progress+, failed_examples++-- * Implementing a custom Formatter+-- |+-- A formatter is a set of actions. Each action is evaluated when a certain+-- situation is encountered during a test run.+--+-- Actions live in the `FormatM` monad. It provides access to the runner state+-- and primitives for appending to the generated report.+, Formatter (..)+, Item(..)+, Result(..)+, FailureReason (..)+, FormatM+, formatterToFormat++-- ** Accessing the runner state+, getSuccessCount+, getPendingCount+, getFailCount+, getTotalCount+, getExpectedTotalCount++, FailureRecord (..)+, getFailMessages+, usedSeed++, printTimes++, Seconds(..)+, getCPUTime+, getRealTime++-- ** Appending to the generated report+, write+, writeLine+, writeTransient++-- ** Dealing with colors+, withInfoColor+, withSuccessColor+, withPendingColor+, withFailColor++, outputUnicode++, useDiff+, extraChunk+, missingChunk++-- ** Helpers+, formatLocation+, formatException+) where++import Prelude ()+import Test.Hspec.Core.Compat hiding (First)++import Data.Maybe+import Test.Hspec.Core.Util+import Test.Hspec.Core.Clock+import Test.Hspec.Core.Spec (Location(..))+import Text.Printf+import Control.Monad.IO.Class+import Control.Exception++-- We use an explicit import list for "Test.Hspec.Formatters.Monad", to make+-- sure, that we only use the public API to implement formatters.+--+-- Everything imported here has to be re-exported, so that users can implement+-- their own formatters.+import Test.Hspec.Core.Formatters.Internal (+ Formatter(..)+ , Item(..)+ , Result(..)+ , FailureReason (..)+ , FormatM+ , formatterToFormat++ , getSuccessCount+ , getPendingCount+ , getFailCount+ , getTotalCount+ , getExpectedTotalCount++ , FailureRecord (..)+ , getFailMessages+ , usedSeed++ , printTimes+ , getCPUTime+ , getRealTime++ , write+ , writeLine+ , writeTransient++ , withInfoColor+ , withSuccessColor+ , withPendingColor+ , withFailColor++ , outputUnicode++ , useDiff+ , extraChunk+ , missingChunk+ )++import Test.Hspec.Core.Formatters.Diff++silent :: Formatter+silent = Formatter {+ formatterStarted = return ()+, formatterGroupStarted = \ _ -> return ()+, formatterGroupDone = \ _ -> return ()+, formatterProgress = \ _ _ -> return ()+, formatterItemStarted = \ _ -> return ()+, formatterItemDone = \ _ _ -> return ()+, formatterDone = return ()+}++checks :: Formatter+checks = specdoc {+ formatterProgress = \(nesting, requirement) p -> do+ writeTransient $ indentationFor nesting ++ requirement ++ " [" ++ (formatProgress p) ++ "]"++, formatterItemStarted = \(nesting, requirement) -> do+ writeTransient $ indentationFor nesting ++ requirement ++ " [ ]"++, formatterItemDone = \ (nesting, requirement) item -> do+ unicode <- outputUnicode+ let fallback a b = if unicode then a else b+ uncurry (writeResult nesting requirement (itemDuration item) (itemInfo item)) $ case itemResult item of+ Success {} -> (withSuccessColor, fallback "✔" "v")+ Pending {} -> (withPendingColor, fallback "‐" "-")+ Failure {} -> (withFailColor, fallback "✘" "x")+ case itemResult item of+ Success {} -> return ()+ Failure {} -> return ()+ Pending _ reason -> withPendingColor $ do+ writeLine $ indentationFor ("" : nesting) ++ "# PENDING: " ++ fromMaybe "No reason given" reason+} where+ indentationFor nesting = replicate (length nesting * 2) ' '++ writeResult :: [String] -> String -> Seconds -> String -> (FormatM () -> FormatM ()) -> String -> FormatM ()+ writeResult nesting requirement duration info withColor symbol = do+ shouldPrintTimes <- printTimes+ write $ indentationFor nesting ++ requirement ++ " ["+ withColor $ write symbol+ writeLine $ "]" ++ if shouldPrintTimes then times else ""+ forM_ (lines info) $ \ s ->+ writeLine $ indentationFor ("" : nesting) ++ s+ where+ dt :: Int+ dt = toMilliseconds duration++ times+ | dt == 0 = ""+ | otherwise = " (" ++ show dt ++ "ms)"++ formatProgress (current, total)+ | total == 0 = show current+ | otherwise = show current ++ "/" ++ show total++specdoc :: Formatter+specdoc = silent {++ formatterStarted = do+ writeLine ""++, formatterGroupStarted = \ (nesting, name) -> do+ writeLine (indentationFor nesting ++ name)++, formatterProgress = \_ p -> do+ writeTransient (formatProgress p)++, formatterItemDone = \(nesting, requirement) item -> do+ let duration = itemDuration item+ info = itemInfo item++ case itemResult item of+ Success -> withSuccessColor $ do+ writeResult nesting requirement duration info+ Pending _ reason -> withPendingColor $ do+ writeResult nesting requirement duration info+ writeLine $ indentationFor ("" : nesting) ++ "# PENDING: " ++ fromMaybe "No reason given" reason+ Failure {} -> withFailColor $ do+ n <- getFailCount+ writeResult nesting (requirement ++ " FAILED [" ++ show n ++ "]") duration info++, formatterDone = defaultFailedFormatter >> defaultFooter+} where+ indentationFor nesting = replicate (length nesting * 2) ' '++ writeResult nesting requirement (Seconds duration) info = do+ shouldPrintTimes <- printTimes+ writeLine $ indentationFor nesting ++ requirement ++ if shouldPrintTimes then times else ""+ forM_ (lines info) $ \ s ->+ writeLine $ indentationFor ("" : nesting) ++ s+ where+ dt :: Int+ dt = floor (duration * 1000)++ times+ | dt == 0 = ""+ | otherwise = " (" ++ show dt ++ "ms)"++ formatProgress (current, total)+ | total == 0 = show current+ | otherwise = show current ++ "/" ++ show total++progress :: Formatter+progress = failed_examples {+ formatterItemDone = \ _ item -> case itemResult item of+ Success{} -> withSuccessColor $ write "."+ Pending{} -> withPendingColor $ write "."+ Failure{} -> withFailColor $ write "F"+}++failed_examples :: Formatter+failed_examples = silent {+ formatterDone = defaultFailedFormatter >> defaultFooter+}++defaultFailedFormatter :: FormatM ()+defaultFailedFormatter = do+ writeLine ""++ failures <- getFailMessages++ unless (null failures) $ do+ writeLine "Failures:"+ writeLine ""++ forM_ (zip [1..] failures) $ \x -> do+ formatFailure x+ writeLine ""++ write "Randomized with seed " >> usedSeed >>= writeLine . show+ writeLine ""+ where+ formatFailure :: (Int, FailureRecord) -> FormatM ()+ formatFailure (n, FailureRecord mLoc path reason) = do+ forM_ mLoc $ \loc -> do+ withInfoColor $ writeLine (" " ++ formatLocation loc)+ write (" " ++ show n ++ ") ")+ writeLine (formatRequirement path)+ case reason of+ NoReason -> return ()+ Reason err -> withFailColor $ indent err+ ExpectedButGot preface expected actual -> do+ mapM_ indent preface++ b <- useDiff++ let threshold = 2 :: Seconds++ mchunks <- liftIO $ if b+ then timeout threshold (evaluate $ diff expected actual)+ else return Nothing++ case mchunks of+ Just chunks -> do+ writeDiff chunks extraChunk missingChunk+ Nothing -> do+ writeDiff [First expected, Second actual] write write+ where+ indented output text = case break (== '\n') text of+ (xs, "") -> output xs+ (xs, _ : ys) -> output (xs ++ "\n") >> write (indentation ++ " ") >> indented output ys++ writeDiff chunks extra missing = do+ withFailColor $ write (indentation ++ "expected: ")+ forM_ chunks $ \ chunk -> case chunk of+ Both a _ -> indented write a+ First a -> indented extra a+ Second _ -> return ()+ writeLine ""++ withFailColor $ write (indentation ++ " but got: ")+ forM_ chunks $ \ chunk -> case chunk of+ Both a _ -> indented write a+ First _ -> return ()+ Second a -> indented missing a+ writeLine ""++ Error _ e -> withFailColor . indent $ (("uncaught exception: " ++) . formatException) e++ writeLine ""+ writeLine (" To rerun use: --match " ++ show (joinPath path))+ where+ indentation = " "+ indent message = do+ forM_ (lines message) $ \line -> do+ writeLine (indentation ++ line)++defaultFooter :: FormatM ()+defaultFooter = do++ writeLine =<< (++)+ <$> (printf "Finished in %1.4f seconds" <$> getRealTime)+ <*> (maybe "" (printf ", used %1.4f seconds of CPU time") <$> getCPUTime)++ fails <- getFailCount+ pending <- getPendingCount+ total <- getTotalCount++ let+ output =+ pluralize total "example"+ ++ ", " ++ pluralize fails "failure"+ ++ if pending == 0 then "" else ", " ++ show pending ++ " pending"+ c | fails /= 0 = withFailColor+ | pending /= 0 = withPendingColor+ | otherwise = withSuccessColor+ c $ writeLine output++formatLocation :: Location -> String+formatLocation (Location file line column) = file ++ ":" ++ show line ++ ":" ++ show column ++ ": "
hspec-core/src/Test/Hspec/Core/Hooks.hs view
@@ -1,4 +1,6 @@ {-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE ConstraintKinds #-} -- | Stability: provisional module Test.Hspec.Core.Hooks ( before@@ -17,10 +19,14 @@ , aroundAll , aroundAll_ , aroundAllWith++, mapSubject+, ignoreSubject ) where import Prelude () import Test.Hspec.Core.Compat+import Data.CallStack import Control.Exception (SomeException, finally, throwIO, try, catch) import Control.Concurrent.MVar@@ -88,11 +94,11 @@ around action = aroundWith $ \e () -> action e -- | Run a custom action after the last spec item.-afterAll :: ActionWith a -> SpecWith a -> SpecWith a-afterAll action spec = runIO (runSpecM spec) >>= fromSpecList . return . NodeWithCleanup action+afterAll :: HasCallStack => ActionWith a -> SpecWith a -> SpecWith a+afterAll action spec = runIO (runSpecM spec) >>= fromSpecList . return . NodeWithCleanup location action -- | Run a custom action after the last spec item.-afterAll_ :: IO () -> SpecWith a -> SpecWith a+afterAll_ :: HasCallStack => IO () -> SpecWith a -> SpecWith a afterAll_ action = afterAll (\_ -> action) -- | Run a custom action before and/or after every spec item.@@ -163,3 +169,15 @@ waitFor :: BinarySemaphore -> IO () waitFor = takeMVar++-- | Modify the subject under test.+--+-- Note that this resembles a contravariant functor on the first type parameter+-- of `SpecM`. This is because the subject is passed inwards, as an argument+-- to the spec item.+mapSubject :: (b -> a) -> SpecWith a -> SpecWith b+mapSubject f = aroundWith (. f)++-- | Ignore the subject under test for a given spec.+ignoreSubject :: SpecWith () -> SpecWith a+ignoreSubject = mapSubject (const ())
hspec-core/src/Test/Hspec/Core/QuickCheck.hs view
@@ -7,6 +7,9 @@ , modifyMaxShrinks ) where +import Prelude ()+import Test.Hspec.Core.Compat+ import Test.QuickCheck import Test.Hspec.Core.Spec
hspec-core/src/Test/Hspec/Core/QuickCheckUtil.hs view
@@ -5,7 +5,6 @@ import Test.Hspec.Core.Compat import Control.Exception-import Data.List import Data.Maybe import Data.Int import System.Random
hspec-core/src/Test/Hspec/Core/Runner.hs view
@@ -1,4 +1,5 @@ {-# LANGUAGE CPP #-}+{-# LANGUAGE BangPatterns #-} -- | -- Stability: provisional@@ -10,6 +11,7 @@ -- * Config , Config (..) , ColorMode (..)+, UnicodeMode(..) , Path , defaultConfig , configAddFilter@@ -29,6 +31,8 @@ #ifdef TEST , rerunAll , specToEvalForest+, colorOutputSupported+, unicodeOutputSupported #endif ) where @@ -43,6 +47,7 @@ import System.Random import Control.Monad.ST import Data.STRef+import System.Console.ANSI (hSupportsANSI) import System.Console.ANSI (hHideCursor, hShowCursor) import qualified Test.QuickCheck as QC@@ -50,12 +55,14 @@ import Test.Hspec.Core.Util (Path) import Test.Hspec.Core.Spec import Test.Hspec.Core.Config-import Test.Hspec.Core.Formatters-import Test.Hspec.Core.Formatters.Internal+import Test.Hspec.Core.Format (FormatConfig(..))+import qualified Test.Hspec.Core.Formatters.V1 as V1+import qualified Test.Hspec.Core.Formatters.V2 as V2 import Test.Hspec.Core.FailureReport import Test.Hspec.Core.QuickCheckUtil import Test.Hspec.Core.Shuffle +import Test.Hspec.Core.Runner.PrintSlowSpecItems import Test.Hspec.Core.Runner.Eval applyFilterPredicates :: Config -> [EvalTree] -> [EvalTree]@@ -198,43 +205,52 @@ runSpec_ :: Config -> Spec -> IO Summary runSpec_ config spec = do filteredSpec <- specToEvalForest config spec- withHandle config $ \h -> do- let formatter = fromMaybe specdoc (configFormatter config)- seed = (fromJust . configQuickCheckSeed) config- qcArgs = configQuickCheckArgs config+ let+ seed = (fromJust . configQuickCheckSeed) config+ qcArgs = configQuickCheckArgs config+ !numberOfItems = countSpecItems filteredSpec - concurrentJobs <- case configConcurrentJobs config of- Nothing -> getDefaultConcurrentJobs- Just n -> return n+ concurrentJobs <- case configConcurrentJobs config of+ Nothing -> getDefaultConcurrentJobs+ Just n -> return n - useColor <- doesUseColor h config+ useColor <- colorOutputSupported (configColorMode config) (hSupportsANSI stdout)+ outputUnicode <- unicodeOutputSupported (configUnicodeMode config) stdout - results <- withHiddenCursor useColor h $ do- let- formatConfig = FormatConfig {- formatConfigHandle = h- , formatConfigUseColor = useColor- , formatConfigUseDiff = configDiff config- , formatConfigHtmlOutput = configHtmlOutput config- , formatConfigPrintCpuTime = configPrintCpuTime config- , formatConfigUsedSeed = seed- }- evalConfig = EvalConfig {- evalConfigFormat = formatterToFormat formatter formatConfig- , evalConfigConcurrentJobs = concurrentJobs- , evalConfigFastFail = configFastFail config- }- runFormatter evalConfig filteredSpec+ results <- withHiddenCursor useColor stdout $ do+ let+ formatConfig = FormatConfig {+ formatConfigUseColor = useColor+ , formatConfigOutputUnicode = outputUnicode+ , formatConfigUseDiff = configDiff config+ , formatConfigPrintTimes = configTimes config+ , formatConfigHtmlOutput = configHtmlOutput config+ , formatConfigPrintCpuTime = configPrintCpuTime config+ , formatConfigUsedSeed = seed+ , formatConfigExpectedTotalCount = numberOfItems+ } - let failures = filter resultItemIsFailure results+ formatter = fromMaybe (V2.formatterToFormat V2.checks) (configFormat config <|> V1.formatterToFormat <$> configFormatter config) - dumpFailureReport config seed qcArgs (map fst failures)+ format <- maybe id printSlowSpecItems (configPrintSlowItems config) <$> formatter formatConfig - return Summary {- summaryExamples = length results- , summaryFailures = length failures- }+ let+ evalConfig = EvalConfig {+ evalConfigFormat = format+ , evalConfigConcurrentJobs = concurrentJobs+ , evalConfigFailFast = configFailFast config+ }+ runFormatter evalConfig filteredSpec + let failures = filter resultItemIsFailure results++ dumpFailureReport config seed qcArgs (map fst failures)++ return Summary {+ summaryExamples = length results+ , summaryFailures = length failures+ }+ specToEvalForest :: Config -> Spec -> IO [EvalTree] specToEvalForest config spec = do let@@ -273,17 +289,21 @@ | useColor = E.bracket_ (hHideCursor h) (hShowCursor h) | otherwise = id -doesUseColor :: Handle -> Config -> IO Bool-doesUseColor h c = case configColorMode c of- ColorAuto -> (&&) <$> hIsTerminalDevice h <*> (not <$> isDumb)+colorOutputSupported :: ColorMode -> IO Bool -> IO Bool+colorOutputSupported mode isTerminalDevice = case mode of+ ColorAuto -> (&&) <$> (not <$> noColor) <*> isTerminalDevice ColorNever -> return False ColorAlways -> return True -withHandle :: Config -> (Handle -> IO a) -> IO a-withHandle c action = case configOutputFile c of- Left h -> action h- Right path -> withFile path WriteMode action+unicodeOutputSupported :: UnicodeMode -> Handle -> IO Bool+unicodeOutputSupported mode h = case mode of+ UnicodeAuto -> (== Just "UTF-8") . fmap show <$> hGetEncoding h+ UnicodeNever -> return False+ UnicodeAlways -> return True +noColor :: IO Bool+noColor = lookupEnv "NO_COLOR" <&> (/= Nothing)+ rerunAll :: Config -> Maybe FailureReport -> Summary -> Bool rerunAll _ Nothing _ = False rerunAll config (Just oldFailureReport) summary =@@ -292,9 +312,6 @@ && isSuccess summary && (not . null) (failureReportPaths oldFailureReport) -isDumb :: IO Bool-isDumb = maybe False (== "dumb") <$> lookupEnv "TERM"- -- | Summary of a test run. data Summary = Summary { summaryExamples :: Int@@ -303,14 +320,21 @@ instance Monoid Summary where mempty = Summary 0 0-#if !MIN_VERSION_base(4,11,0)- (Summary x1 x2) `mappend` (Summary y1 y2) = Summary (x1 + y1) (x2 + y2)-#else+#if MIN_VERSION_base(4,11,0) instance Semigroup Summary where- (Summary x1 x2) <> (Summary y1 y2) = Summary (x1 + y1) (x2 + y2) #endif+ (Summary x1 x2)+#if MIN_VERSION_base(4,11,0)+ <>+#else+ `mappend`+#endif+ (Summary y1 y2) = Summary (x1 + y1) (x2 + y2) randomizeForest :: Integer -> [Tree c a] -> [Tree c a] randomizeForest seed t = runST $ do ref <- newSTRef (mkStdGen $ fromIntegral seed) shuffleForest ref t++countSpecItems :: [EvalTree] -> Int+countSpecItems = getSum . foldMap (foldMap . const $ Sum 1)
hspec-core/src/Test/Hspec/Core/Runner/Eval.hs view
@@ -3,6 +3,7 @@ {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE RecordWildCards #-} {-# LANGUAGE ConstraintKinds #-}+{-# LANGUAGE RankNTypes #-} #if MIN_VERSION_base(4,6,0) && !MIN_VERSION_base(4,7,0) -- Control.Concurrent.QSem is deprecated in base-4.6.0.*@@ -31,13 +32,13 @@ import Control.Monad.IO.Class (liftIO) import qualified Control.Monad.IO.Class as M -import Control.Monad.Trans.State hiding (State, state)+import Control.Monad.Trans.Reader import Control.Monad.Trans.Class import Test.Hspec.Core.Util import Test.Hspec.Core.Spec (Tree(..), Progress, FailureReason(..), Result(..), ResultStatus(..), ProgressCallback) import Test.Hspec.Core.Timer-import Test.Hspec.Core.Format (Format(..))+import Test.Hspec.Core.Format (Format) import qualified Test.Hspec.Core.Format as Format import Test.Hspec.Core.Clock import Test.Hspec.Core.Example.Location@@ -47,52 +48,59 @@ type Monad m = (Functor m, Applicative m, M.Monad m) type MonadIO m = (Monad m, M.MonadIO m) -data EvalConfig m = EvalConfig {- evalConfigFormat :: Format m+data EvalConfig = EvalConfig {+ evalConfigFormat :: Format , evalConfigConcurrentJobs :: Int-, evalConfigFastFail :: Bool+, evalConfigFailFast :: Bool } -data State m = State {- stateConfig :: EvalConfig m-, stateResults :: [(Path, Format.Item)]+data Env = Env {+ envConfig :: EvalConfig+, envResults :: IORef [(Path, Format.Item)] } -type EvalM m = StateT (State m) m+formatEvent :: Format.Event -> EvalM ()+formatEvent event = do+ format <- asks $ evalConfigFormat . envConfig+ liftIO $ format event -addResult :: Monad m => Path -> Format.Item -> EvalM m ()-addResult path item = modify $ \ state -> state {stateResults = (path, item) : stateResults state}+type EvalM = ReaderT Env IO -getFormat :: Monad m => (Format m -> a) -> EvalM m a-getFormat format = gets (format . evalConfigFormat . stateConfig)+addResult :: Path -> Format.Item -> EvalM ()+addResult path item = do+ ref <- asks envResults+ liftIO $ modifyIORef ref ((path, item) :) -reportItem :: Monad m => Path -> Format.Item -> EvalM m ()-reportItem path item = do- addResult path item- format <- getFormat formatItem- lift (format path item)+getResults :: EvalM [(Path, Format.Item)]+getResults = reverse <$> (asks envResults >>= liftIO . readIORef) -failureItem :: Maybe Location -> Seconds -> String -> FailureReason -> Format.Item-failureItem loc duration info err = Format.Item loc duration info (Format.Failure err)+reportItem :: Path -> Maybe Location -> EvalM (Seconds, Result) -> EvalM ()+reportItem path loc action = do+ reportItemStarted path+ action >>= reportResult path loc -reportResult :: Monad m => Path -> Maybe Location -> (Seconds, Result) -> EvalM m ()+reportItemStarted :: Path -> EvalM ()+reportItemStarted = formatEvent . Format.ItemStarted++reportItemDone :: Path -> Format.Item -> EvalM ()+reportItemDone path item = do+ addResult path item+ formatEvent $ Format.ItemDone path item++reportResult :: Path -> Maybe Location -> (Seconds, Result) -> EvalM () reportResult path loc (duration, result) = do case result of- Result info status -> case status of- Success -> reportItem path (Format.Item loc duration info Format.Success)- Pending loc_ reason -> reportItem path (Format.Item (loc_ <|> loc) duration info $ Format.Pending reason)- Failure loc_ err@(Error _ e) -> reportItem path (failureItem (loc_ <|> extractLocation e <|> loc) duration info err)- Failure loc_ err -> reportItem path (failureItem (loc_ <|> loc) duration info err)+ Result info status -> reportItemDone path $ Format.Item loc duration info $ case status of+ Success -> Format.Success+ Pending loc_ reason -> Format.Pending loc_ reason+ Failure loc_ err@(Error _ e) -> Format.Failure (loc_ <|> extractLocation e) err+ Failure loc_ err -> Format.Failure loc_ err -groupStarted :: Monad m => Path -> EvalM m ()-groupStarted path = do- format <- getFormat formatGroupStarted- lift $ format path+groupStarted :: Path -> EvalM ()+groupStarted = formatEvent . Format.GroupStarted -groupDone :: Monad m => Path -> EvalM m ()-groupDone path = do- format <- getFormat formatGroupDone- lift $ format path+groupDone :: Path -> EvalM ()+groupDone = formatEvent . Format.GroupDone data EvalItem = EvalItem { evalItemDescription :: String@@ -103,28 +111,32 @@ type EvalTree = Tree (IO ()) EvalItem -runEvalM :: Monad m => EvalConfig m -> EvalM m () -> m (State m)-runEvalM config action = execStateT action (State config [])- -- | Evaluate all examples of a given spec and produce a report.-runFormatter :: forall m. MonadIO m => EvalConfig m -> [EvalTree] -> IO ([(Path, Format.Item)])+runFormatter :: EvalConfig -> [EvalTree] -> IO ([(Path, Format.Item)]) runFormatter config specs = do+ ref <- newIORef []+ let start = parallelizeTree (evalConfigConcurrentJobs config) specs cancel = cancelMany . concatMap toList . map (fmap fst)+ E.bracket start cancel $ \ runningSpecs -> do withTimer 0.05 $ \ timer -> do- state <- formatRun format $ do- runEvalM config $- run $ map (fmap (fmap (. reportProgress timer) . snd)) runningSpecs- return (reverse $ stateResults state)++ format Format.Started+ runReaderT (run $ map (fmap (fmap (. reportProgress timer) . snd)) runningSpecs) (Env config ref) `E.finally` do+ results <- reverse <$> readIORef ref+ format (Format.Done results)++ results <- reverse <$> readIORef ref+ return results where format = evalConfigFormat config - reportProgress :: IO Bool -> Path -> Progress -> m () reportProgress timer path progress = do- r <- liftIO timer- when r (formatProgress format path progress)+ r <- timer+ when r $ do+ format (Format.Progress path progress) cancelMany :: [Async a] -> IO () cancelMany asyncs = do@@ -196,12 +208,12 @@ replaceMVar :: MVar a -> a -> IO () replaceMVar mvar p = tryTakeMVar mvar >> putMVar mvar p -run :: forall m. MonadIO m => [RunningTree m] -> EvalM m ()+run :: [RunningTree IO] -> EvalM () run specs = do- fastFail <- gets (evalConfigFastFail . stateConfig)+ fastFail <- asks (evalConfigFailFast . envConfig) sequenceActions fastFail (concatMap foldSpec specs) where- foldSpec :: RunningTree m -> [EvalM m ()]+ foldSpec :: RunningTree IO -> [EvalM ()] foldSpec = foldTree FoldTree { onGroupStarted = groupStarted , onGroupDone = groupDone@@ -209,18 +221,18 @@ , onLeafe = evalItem } - runCleanup :: [String] -> IO () -> EvalM m ()- runCleanup groups action = do+ runCleanup :: Maybe Location -> [String] -> IO () -> EvalM ()+ runCleanup loc groups action = do r <- liftIO $ measure $ safeEvaluate (action >> return (Result "" Success)) case r of (_, Result "" Success) -> return ()- _ -> reportResult path Nothing r+ _ -> reportItem path loc (return r) where path = (groups, "afterAll-hook") - evalItem :: [String] -> RunningItem m -> EvalM m ()+ evalItem :: [String] -> RunningItem IO -> EvalM () evalItem groups (Item requirement loc action) = do- lift (action path) >>= reportResult path loc+ reportItem path loc $ lift (action path) where path :: Path path = (groups, requirement)@@ -228,7 +240,7 @@ data FoldTree c a r = FoldTree { onGroupStarted :: Path -> r , onGroupDone :: Path -> r-, onCleanup :: [String] -> c -> r+, onCleanup :: Maybe Location -> [String] -> c -> r , onLeafe :: [String] -> a -> r } @@ -241,20 +253,20 @@ start = onGroupStarted path children = concatMap (go (group : rGroups)) xs done = onGroupDone path- go rGroups (NodeWithCleanup action xs) = children ++ [cleanup]+ go rGroups (NodeWithCleanup loc action xs) = children ++ [cleanup] where children = concatMap (go rGroups) xs- cleanup = onCleanup (reverse rGroups) action+ cleanup = onCleanup loc (reverse rGroups) action go rGroups (Leaf a) = [onLeafe (reverse rGroups) a] -sequenceActions :: Monad m => Bool -> [EvalM m ()] -> EvalM m ()+sequenceActions :: Bool -> [EvalM ()] -> EvalM () sequenceActions fastFail = go where- go :: Monad m => [EvalM m ()] -> EvalM m ()+ go :: [EvalM ()] -> EvalM () go [] = return () go (action : actions) = do action- hasFailures <- any resultItemIsFailure <$> gets stateResults+ hasFailures <- any resultItemIsFailure <$> getResults let stopNow = fastFail && hasFailures unless stopNow (go actions)
+ hspec-core/src/Test/Hspec/Core/Runner/PrintSlowSpecItems.hs view
@@ -0,0 +1,41 @@+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE RecordWildCards #-}+module Test.Hspec.Core.Runner.PrintSlowSpecItems (+ printSlowSpecItems+) where++import Prelude ()+import Test.Hspec.Core.Compat++import Test.Hspec.Core.Util+import Test.Hspec.Core.Format++import Test.Hspec.Core.Clock+import Test.Hspec.Core.Formatters.V2 (formatLocation)++data SlowItem = SlowItem {+ location :: Maybe Location+, path :: Path+, duration :: Int+}++printSlowSpecItems :: Int -> Format -> Format+printSlowSpecItems n format event = do+ format event+ case event of+ Done items -> do+ let xs = slowItems n $ map toSlowItem items+ unless (null xs) $ do+ putStrLn "\nSlow spec items:"+ mapM_ printSlowSpecItem xs+ _ -> return ()++toSlowItem :: (Path, Item) -> SlowItem+toSlowItem (path, item) = SlowItem (itemLocation item) path (toMilliseconds $ itemDuration item)++slowItems :: Int -> [SlowItem] -> [SlowItem]+slowItems n = take n . reverse . sortOn duration . filter ((/= 0) . duration)++printSlowSpecItem :: SlowItem -> IO ()+printSlowSpecItem SlowItem{..} = do+ putStrLn $ " " ++ maybe "" formatLocation location ++ joinPath path ++ " (" ++ show duration ++ "ms)"
hspec-core/src/Test/Hspec/Core/Shuffle.hs view
@@ -22,7 +22,7 @@ shuffleTree :: STRef s StdGen -> Tree c a -> ST s (Tree c a) shuffleTree ref t = case t of Node d xs -> Node d <$> shuffleForest ref xs- NodeWithCleanup c xs -> NodeWithCleanup c <$> shuffleForest ref xs+ NodeWithCleanup loc c xs -> NodeWithCleanup loc c <$> shuffleForest ref xs Leaf {} -> return t shuffle :: STRef s StdGen -> [a] -> ST s [a]
hspec-core/src/Test/Hspec/Core/Tree.hs view
@@ -33,7 +33,7 @@ -- | Internal tree data structure data Tree c a = Node String [Tree c a]- | NodeWithCleanup c [Tree c a]+ | NodeWithCleanup (Maybe Location) c [Tree c a] | Leaf a deriving (Show, Eq, Functor, Foldable, Traversable) @@ -49,7 +49,7 @@ where go spec = case spec of Node d xs -> Node d (map go xs)- NodeWithCleanup cleanup xs -> NodeWithCleanup (g cleanup) (map go xs)+ NodeWithCleanup loc cleanup xs -> NodeWithCleanup loc (g cleanup) (map go xs) Leaf item -> Leaf (f item) filterTree :: (a -> Bool) -> Tree c a -> Maybe (Tree c a)@@ -70,7 +70,7 @@ filterTree_ :: [String] -> ([String] -> a -> Bool) -> Tree c a -> Maybe (Tree c a) filterTree_ groups p tree = case tree of Node group xs -> Just $ Node group $ filterForest_ (groups ++ [group]) p xs- NodeWithCleanup action xs -> Just $ NodeWithCleanup action $ filterForest_ groups p xs+ NodeWithCleanup loc action xs -> Just $ NodeWithCleanup loc action $ filterForest_ groups p xs Leaf item -> Leaf <$> guarded (p groups) item pruneForest :: [Tree c a] -> [Tree c a]@@ -79,7 +79,7 @@ pruneTree :: Tree c a -> Maybe (Tree c a) pruneTree node = case node of Node group xs -> Node group <$> prune xs- NodeWithCleanup action xs -> NodeWithCleanup action <$> prune xs+ NodeWithCleanup loc action xs -> NodeWithCleanup loc action <$> prune xs Leaf{} -> Just node where prune = guarded (not . null) . pruneForest
hspec-core/src/Test/Hspec/Core/Util.hs view
@@ -16,13 +16,13 @@ , formatException ) where -import Data.List+import Prelude ()+import Test.Hspec.Core.Compat hiding (join)+ import Data.Char (isSpace) import GHC.IO.Exception import Control.Exception import Control.Concurrent.Async--import Test.Hspec.Core.Compat (showType) -- | -- @pluralize count singular@ pluralizes the given @singular@ word unless given
hspec-core/vendor/Control/Concurrent/Async.hs view
@@ -6,7 +6,7 @@ #if __GLASGOW_HASKELL__ < 710 {-# LANGUAGE DeriveDataTypeable #-} #endif-{-# OPTIONS -Wall #-}+{-# OPTIONS -Wall -fno-warn-implicit-prelude #-} ----------------------------------------------------------------------------- -- |
hspec-core/vendor/Data/Algorithm/Diff.hs view
@@ -1,4 +1,5 @@ {-# LANGUAGE DeriveFunctor #-}+{-# OPTIONS_GHC -fno-warn-incomplete-uni-patterns #-} ----------------------------------------------------------------------------- -- | -- Module : Data.Algorithm.Diff
hspec-discover/src/Test/Hspec/Discover/Run.hs view
@@ -10,12 +10,13 @@ -- exported for testing , Spec(..) , importList-, fileToSpec-, findSpecs-, getFilesRecursive , driverWithFormatter , moduleNameFromId , pathToModule+, Tree(..)+, Forest(..)+, Hook(..)+, discover ) where import Control.Monad import Control.Applicative@@ -35,10 +36,8 @@ instance IsString ShowS where fromString = showString -data Spec = Spec {- specFile :: FilePath-, specModule :: String-} deriving (Eq, Show)+data Spec = Spec String | Hook String [Spec]+ deriving (Eq, Show) run :: [String] -> IO () run args_ = do@@ -49,16 +48,19 @@ hPutStrLn stderr err exitFailure Right conf -> do- when (configNested conf) (hPutStrLn stderr "hspec-discover: WARNING - The `--nested' option is deprecated and will be removed in a future release!")+ when (configNested conf) (hPutStrLn stderr "hspec-discover: WARNING - The `--nested' option is deprecated and will be removed in a future release!")+ when (configNoMain conf) (hPutStrLn stderr "hspec-discover: WARNING - The `--no-main' option is deprecated and will be removed in a future release!")+ when (isJust $ configFormatter conf) (hPutStrLn stderr "hspec-discover: WARNING - The `--formatter' option is deprecated and will be removed in a future release!") specs <- findSpecs src writeFile dst (mkSpecModule src conf specs) _ -> do hPutStrLn stderr (usage name) exitFailure -mkSpecModule :: FilePath -> Config -> [Spec] -> String+mkSpecModule :: FilePath -> Config -> Maybe [Spec] -> String mkSpecModule src conf nodes = ( "{-# LINE 1 " . shows src . " #-}\n"+ . showString "{-# LANGUAGE NoImplicitPrelude #-}\n" . showString "{-# OPTIONS_GHC -fno-warn-warnings-deprecations #-}\n" . showString ("module " ++ moduleName src conf ++" where\n") . importList nodes@@ -97,42 +99,58 @@ moduleNameFromId = reverse . dropWhile (== '.') . dropWhile (/= '.') . reverse -- | Generate imports for a list of specs.-importList :: [Spec] -> ShowS-importList = foldr (.) "" . map f+importList :: Maybe [Spec] -> ShowS+importList = foldr (.) "" . map f . maybe [] moduleNames where- f :: Spec -> ShowS- f spec = "import qualified " . showString (specModule spec) . "Spec\n"+ f :: String -> ShowS+ f spec = "import qualified " . showString spec . "\n" +moduleNames :: [Spec] -> [String]+moduleNames = fromForest+ where+ fromForest :: [Spec] -> [String]+ fromForest = concatMap fromTree++ fromTree :: Spec -> [String]+ fromTree tree = case tree of+ Spec name -> [name ++ "Spec"]+ Hook name forest -> name : fromForest forest+ -- | Combine a list of strings with (>>). sequenceS :: [ShowS] -> ShowS sequenceS = foldr (.) "" . intersperse " >> " --- | Convert a list of specs to code.-formatSpecs :: [Spec] -> ShowS-formatSpecs xs- | null xs = "return ()"- | otherwise = sequenceS (map formatSpec xs)+formatSpecs :: Maybe [Spec] -> ShowS+formatSpecs specs = case specs of+ Nothing -> "return ()"+ Just xs -> fromForest xs+ where+ fromForest :: [Spec] -> ShowS+ fromForest = sequenceS . map fromTree --- | Convert a spec to code.-formatSpec :: Spec -> ShowS-formatSpec (Spec file name) = "postProcessSpec " . shows file . " (describe " . shows name . " " . showString name . "Spec.spec)"+ fromTree :: Spec -> ShowS+ fromTree tree = case tree of+ Spec name -> "describe " . shows name . " " . showString name . "Spec.spec"+ Hook name forest -> "(" . showString name . ".hook $ " . fromForest forest . ")" -findSpecs :: FilePath -> IO [Spec]-findSpecs src = do- let (dir, file) = splitFileName src- mapMaybe (fileToSpec dir) . filter (/= file) <$> getFilesRecursive dir+findSpecs :: FilePath -> IO (Maybe [Spec])+findSpecs = fmap (fmap toSpecs) . discover -fileToSpec :: FilePath -> FilePath -> Maybe Spec-fileToSpec dir file = case reverse $ splitDirectories file of- x:xs -> case stripSuffix "Spec.hs" x <|> stripSuffix "Spec.lhs" x of- Just name | isValidModuleName name && all isValidModuleName xs ->- Just . Spec (dir </> file) $ (intercalate "." . reverse) (name : xs)- _ -> Nothing- _ -> Nothing+toSpecs :: Forest -> [Spec]+toSpecs = fromForest [] where- stripSuffix :: Eq a => [a] -> [a] -> Maybe [a]- stripSuffix suffix str = reverse <$> stripPrefix (reverse suffix) (reverse str)+ fromForest :: [String] -> Forest -> [Spec]+ fromForest names (Forest WithHook xs) = [Hook (mkModule ("SpecHook" : names)) $ concatMap (fromTree names) xs]+ fromForest names (Forest WithoutHook xs) = concatMap (fromTree names) xs + fromTree :: [String] -> Tree -> [Spec]+ fromTree names spec = case spec of+ Leaf name -> [Spec $ mkModule (name : names )]+ Node name forest -> fromForest (name : names) forest++ mkModule :: [String] -> String+ mkModule = intercalate "." . reverse+ -- See `Cabal.Distribution.ModuleName` (http://git.io/bj34) isValidModuleName :: String -> Bool isValidModuleName [] = False@@ -141,13 +159,71 @@ isValidModuleChar :: Char -> Bool isValidModuleChar c = isAlphaNum c || c == '_' || c == '\'' -getFilesRecursive :: FilePath -> IO [FilePath]-getFilesRecursive baseDir = sortNaturally <$> go []+data Tree = Leaf String | Node String Forest+ deriving (Eq, Show) +data Forest = Forest Hook [Tree]+ deriving (Eq, Show)++data Hook = WithHook | WithoutHook+ deriving (Eq, Show)++sortKey :: Tree -> (String, Int)+sortKey tree = case tree of+ Leaf name -> (name, 0)+ Node name _ -> (name, 1)++discover :: FilePath -> IO (Maybe Forest)+discover src = (>>= filterSrc) <$> specForest dir where- go :: FilePath -> IO [FilePath]- go dir = do- c <- map (dir </>) . filter (`notElem` [".", ".."]) <$> getDirectoryContents (baseDir </> dir)- dirs <- filterM (doesDirectoryExist . (baseDir </>)) c >>= mapM go- files <- filterM (doesFileExist . (baseDir </>)) c- return (files ++ concat dirs)+ filterSrc :: Forest -> Maybe Forest+ filterSrc (Forest hook xs) = ensureForest hook $ maybe id (filter . (/=)) (toSpec file) xs++ (dir, file) = splitFileName src++specForest :: FilePath -> IO (Maybe Forest)+specForest dir = do+ files <- listDirectory dir+ hook <- mkHook dir files+ ensureForest hook . sortNaturallyBy sortKey . catMaybes <$> mapM toSpecTree files+ where+ toSpecTree :: FilePath -> IO (Maybe Tree)+ toSpecTree name+ | isValidModuleName name = do+ doesDirectoryExist (dir </> name) `fallback` Nothing $ do+ xs <- specForest (dir </> name)+ return $ Node name <$> xs+ | otherwise = do+ doesFileExist (dir </> name) `fallback` Nothing $ do+ return $ toSpec name++mkHook :: FilePath -> [FilePath] -> IO Hook+mkHook dir files+ | "SpecHook.hs" `elem` files = do+ doesFileExist (dir </> "SpecHook.hs") `fallback` WithoutHook $ do+ return WithHook+ | otherwise = return WithoutHook++fallback :: IO Bool -> a -> IO a -> IO a+fallback p def action = do+ bool <- p+ if bool then action else return def++toSpec :: FilePath -> Maybe Tree+toSpec file = Leaf <$> (spec >>= ensure isValidModuleName)+ where+ spec :: Maybe String+ spec = stripSuffix "Spec.hs" file <|> stripSuffix "Spec.lhs" file++ stripSuffix :: Eq a => [a] -> [a] -> Maybe [a]+ stripSuffix suffix str = reverse <$> stripPrefix (reverse suffix) (reverse str)++ensure :: (a -> Bool) -> a -> Maybe a+ensure p a = guard (p a) >> Just a++ensureForest :: Hook -> [Tree] -> Maybe Forest+ensureForest hook = fmap (Forest hook) . ensure (not . null)++listDirectory :: FilePath -> IO [FilePath]+listDirectory path = filter f <$> getDirectoryContents path+ where f filename = filename /= "." && filename /= ".."
hspec-discover/src/Test/Hspec/Discover/Sort.hs view
@@ -1,5 +1,8 @@+-- |+-- /NOTE:/ This module is not meant for public consumption. For user+-- documentation look at http://hspec.github.io/hspec-discover.html. module Test.Hspec.Discover.Sort (- sortNaturally+ sortNaturallyBy , NaturalSortKey , naturalSortKey ) where@@ -9,8 +12,8 @@ import Data.List import Data.Ord -sortNaturally :: [String] -> [String]-sortNaturally = sortBy (comparing naturalSortKey)+sortNaturallyBy :: (a -> (String, Int)) -> [a] -> [a]+sortNaturallyBy f = sortBy (comparing ((\ (k, t) -> (naturalSortKey k, t)) . f)) data NaturalSortKey = NaturalSortKey [Chunk] deriving (Eq, Ord)
hspec-meta.cabal view
@@ -1,11 +1,11 @@ cabal-version: 1.12 --- This file has been generated from package.yaml by hpack version 0.34.3.+-- This file has been generated from package.yaml by hpack version 0.34.5. -- -- see: https://github.com/sol/hpack name: hspec-meta-version: 2.7.8+version: 2.9.0 synopsis: A version of Hspec which is used to test Hspec itself description: A stable version of Hspec which is used to test the in-development version of Hspec.@@ -36,25 +36,33 @@ Test.Hspec.Formatters Test.Hspec.QuickCheck Test.Hspec.Runner+ GetOpt.Declarative+ GetOpt.Declarative.Environment+ GetOpt.Declarative.Interpret+ GetOpt.Declarative.Types+ GetOpt.Declarative.Util Test.Hspec.Core.Clock Test.Hspec.Core.Compat Test.Hspec.Core.Config+ Test.Hspec.Core.Config.Definition Test.Hspec.Core.Config.Options- Test.Hspec.Core.Config.Util Test.Hspec.Core.Example Test.Hspec.Core.Example.Location Test.Hspec.Core.FailureReport Test.Hspec.Core.Format Test.Hspec.Core.Formatters Test.Hspec.Core.Formatters.Diff- Test.Hspec.Core.Formatters.Free Test.Hspec.Core.Formatters.Internal- Test.Hspec.Core.Formatters.Monad+ Test.Hspec.Core.Formatters.V1+ Test.Hspec.Core.Formatters.V1.Free+ Test.Hspec.Core.Formatters.V1.Monad+ Test.Hspec.Core.Formatters.V2 Test.Hspec.Core.Hooks Test.Hspec.Core.QuickCheck Test.Hspec.Core.QuickCheckUtil Test.Hspec.Core.Runner Test.Hspec.Core.Runner.Eval+ Test.Hspec.Core.Runner.PrintSlowSpecItems Test.Hspec.Core.Shuffle Test.Hspec.Core.Spec Test.Hspec.Core.Spec.Monad
src/Test/Hspec.hs view
@@ -64,6 +64,11 @@ , around , around_ , aroundWith+, aroundAll+, aroundAll_+, aroundAllWith+, mapSubject+, ignoreSubject -- * Running a spec , hspec
src/Test/Hspec/Discover.hs view
@@ -10,8 +10,6 @@ , describe ) where -import Prelude hiding (mapM)- import Test.Hspec.Core.Spec import Test.Hspec.Core.Runner import Test.Hspec.Formatters
version.yaml view
@@ -1,1 +1,1 @@-&version 2.7.8+&version 2.9.0