hspec-meta 2.9.3 → 2.10.5
raw patch · 29 files changed
+922/−324 lines, 29 filesPVP ok
version bump matches the API change (PVP)
API changes (from Hackage documentation)
- Test.Hspec.Meta: type family Arg e
+ Test.Hspec.Meta: type Arg e
- Test.Hspec.Meta: aroundAll :: (ActionWith a -> IO ()) -> SpecWith a -> Spec
+ Test.Hspec.Meta: aroundAll :: HasCallStack => (ActionWith a -> IO ()) -> SpecWith a -> Spec
- Test.Hspec.Meta: aroundAllWith :: forall a b. (ActionWith a -> ActionWith b) -> SpecWith a -> SpecWith b
+ Test.Hspec.Meta: aroundAllWith :: forall a b. HasCallStack => (ActionWith a -> ActionWith b) -> SpecWith a -> SpecWith b
- Test.Hspec.Meta: aroundAll_ :: (IO () -> IO ()) -> SpecWith a -> SpecWith a
+ Test.Hspec.Meta: aroundAll_ :: HasCallStack => (IO () -> IO ()) -> SpecWith a -> SpecWith a
- Test.Hspec.Meta: beforeAll :: IO a -> SpecWith a -> Spec
+ Test.Hspec.Meta: beforeAll :: HasCallStack => IO a -> SpecWith a -> Spec
- Test.Hspec.Meta: beforeAllWith :: (b -> IO a) -> SpecWith a -> SpecWith b
+ Test.Hspec.Meta: beforeAllWith :: HasCallStack => (b -> IO a) -> SpecWith a -> SpecWith b
- Test.Hspec.Meta: beforeAll_ :: IO () -> SpecWith a -> SpecWith a
+ Test.Hspec.Meta: beforeAll_ :: HasCallStack => IO () -> SpecWith a -> SpecWith a
Files
- LICENSE +1/−1
- hspec-core/src/NonEmpty.hs +37/−0
- hspec-core/src/Test/Hspec/Core/Clock.hs +13/−3
- hspec-core/src/Test/Hspec/Core/Compat.hs +7/−0
- hspec-core/src/Test/Hspec/Core/Config/Definition.hs +80/−4
- hspec-core/src/Test/Hspec/Core/Example.hs +16/−7
- hspec-core/src/Test/Hspec/Core/Example/Location.hs +21/−3
- hspec-core/src/Test/Hspec/Core/Format.hs +4/−2
- hspec-core/src/Test/Hspec/Core/Formatters.hs +5/−1
- hspec-core/src/Test/Hspec/Core/Formatters/Internal.hs +66/−49
- hspec-core/src/Test/Hspec/Core/Formatters/Pretty.hs +1/−2
- hspec-core/src/Test/Hspec/Core/Formatters/Pretty/Parser.hs +72/−28
- hspec-core/src/Test/Hspec/Core/Formatters/Pretty/Parser/Types.hs +1/−1
- hspec-core/src/Test/Hspec/Core/Formatters/V2.hs +9/−6
- hspec-core/src/Test/Hspec/Core/Hooks.hs +68/−47
- hspec-core/src/Test/Hspec/Core/Runner.hs +226/−80
- hspec-core/src/Test/Hspec/Core/Runner/Eval.hs +84/−31
- hspec-core/src/Test/Hspec/Core/Runner/Result.hs +58/−0
- hspec-core/src/Test/Hspec/Core/Spec.hs +67/−11
- hspec-core/src/Test/Hspec/Core/Spec/Monad.hs +37/−13
- hspec-core/src/Test/Hspec/Core/Tree.hs +21/−14
- hspec-core/src/Test/Hspec/Core/Util.hs +6/−4
- hspec-discover/src/Test/Hspec/Discover/Config.hs +2/−2
- hspec-discover/src/Test/Hspec/Discover/Run.hs +3/−5
- hspec-discover/src/Test/Hspec/Discover/Sort.hs +1/−1
- hspec-meta.cabal +7/−5
- src/Test/Hspec/Discover.hs +1/−1
- src/Test/Hspec/Formatters.hs +7/−2
- version.yaml +1/−1
LICENSE view
@@ -1,4 +1,4 @@-Copyright (c) 2011-2021 Simon Hengel <sol@typeful.net>+Copyright (c) 2011-2022 Simon Hengel <sol@typeful.net> Copyright (c) 2011-2012 Trystan Spangler <trystan.s@comcast.net> Copyright (c) 2011-2011 Greg Weber <greg@gregweber.info>
+ hspec-core/src/NonEmpty.hs view
@@ -0,0 +1,37 @@+{-# LANGUAGE CPP #-}+{-# LANGUAGE DeriveFunctor #-}+{-# LANGUAGE DeriveFoldable #-}+{-# LANGUAGE DeriveTraversable #-}+module NonEmpty (+ NonEmpty(..)+, nonEmpty+, reverse+#ifdef TEST+, fromList+#endif+) where++import Prelude ()+import Test.Hspec.Core.Compat hiding (reverse)++import qualified Data.List as List+import qualified Data.Foldable as Foldable++data NonEmpty a = a :| [a]+ deriving (Eq, Show, Functor, Foldable, Traversable)++infixr 5 :|++nonEmpty :: [a] -> Maybe (NonEmpty a)+nonEmpty [] = Nothing+nonEmpty (a:as) = Just (a :| as)++reverse :: NonEmpty a -> NonEmpty a+reverse = lift List.reverse++lift :: Foldable f => ([a] -> [b]) -> f a -> NonEmpty b+lift f = fromList . f . Foldable.toList++fromList :: [a] -> NonEmpty a+fromList (a:as) = a :| as+fromList [] = error "NonEmpty.fromList: empty list"
hspec-core/src/Test/Hspec/Core/Clock.hs view
@@ -1,4 +1,5 @@ {-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# LANGUAGE CPP #-} module Test.Hspec.Core.Clock ( Seconds(..) , toMilliseconds@@ -13,10 +14,15 @@ import Test.Hspec.Core.Compat import Text.Printf-import System.Clock import Control.Concurrent import qualified System.Timeout as System +#if MIN_VERSION_base(4,11,0)+import qualified GHC.Clock as GHC+#else+import Data.Time.Clock.POSIX+#endif+ newtype Seconds = Seconds Double deriving (Eq, Show, Ord, Num, Fractional, PrintfArg) @@ -27,9 +33,13 @@ toMicroseconds (Seconds s) = floor (s * 1000000) getMonotonicTime :: IO Seconds+#if MIN_VERSION_base(4,11,0)+getMonotonicTime = Seconds <$> GHC.getMonotonicTime+#else getMonotonicTime = do- t <- getTime Monotonic- return $ Seconds ((fromIntegral . toNanoSecs $ t) / 1000000000)+ t <- getPOSIXTime+ return $ Seconds (realToFrac t)+#endif measure :: IO a -> IO (Seconds, a) measure action = do
hspec-core/src/Test/Hspec/Core/Compat.hs view
@@ -4,6 +4,7 @@ , module Test.Hspec.Core.Compat ) where +import Control.Arrow as Imports ((>>>), (&&&), first, second) import Control.Applicative as Imports import Control.Monad as Imports hiding ( mapM@@ -68,6 +69,7 @@ import Text.Read as Imports (readMaybe) import System.Environment as Imports (lookupEnv) #else+import Control.Exception import Text.Read import System.Environment import qualified Text.ParserCombinators.ReadP as P@@ -87,6 +89,11 @@ #endif #if !MIN_VERSION_base(4,6,0)+forkFinally :: IO a -> (Either SomeException a -> IO ()) -> IO ThreadId+forkFinally action and_then =+ mask $ \restore ->+ forkIO $ try (restore action) >>= and_then+ -- |Strict version of 'modifyIORef' modifyIORef' :: IORef a -> (a -> a) -> IO () modifyIORef' ref f = do
hspec-core/src/Test/Hspec/Core/Config/Definition.hs view
@@ -13,7 +13,7 @@ , runnerOptions #ifdef TEST-, formatOrList+, splitOn #endif ) where @@ -22,6 +22,7 @@ import Test.Hspec.Core.Example (Params(..), defaultParams) import Test.Hspec.Core.Format (Format, FormatConfig)+import Test.Hspec.Core.Formatters.Pretty (pretty2) import qualified Test.Hspec.Core.Formatters.V1 as V1 import qualified Test.Hspec.Core.Formatters.V2 as V2 import Test.Hspec.Core.Util@@ -39,7 +40,9 @@ configIgnoreConfigFile :: Bool , configDryRun :: Bool , configFocusedOnly :: Bool+, configFailOnEmpty :: Bool , configFailOnFocused :: Bool+, configFailOnPending :: Bool , configPrintSlowItems :: Maybe Int , configPrintCpuTime :: Bool , configFailFast :: Bool@@ -58,11 +61,12 @@ , configQuickCheckMaxDiscardRatio :: Maybe Int , configQuickCheckMaxSize :: Maybe Int , configQuickCheckMaxShrinks :: Maybe Int-, configSmallCheckDepth :: Int+, configSmallCheckDepth :: Maybe Int , configColorMode :: ColorMode , configUnicodeMode :: UnicodeMode , configDiff :: Bool , configPrettyPrint :: Bool+, configPrettyPrintFunction :: Bool -> String -> String -> (String, String) , configTimes :: Bool , configAvailableFormatters :: [(String, FormatConfig -> IO Format)] , configFormat :: Maybe (FormatConfig -> IO Format)@@ -76,7 +80,9 @@ configIgnoreConfigFile = False , configDryRun = False , configFocusedOnly = False+, configFailOnEmpty = False , configFailOnFocused = False+, configFailOnPending = False , configPrintSlowItems = Nothing , configPrintCpuTime = False , configFailFast = False@@ -96,6 +102,7 @@ , configUnicodeMode = UnicodeAuto , configDiff = True , configPrettyPrint = True+, configPrettyPrintFunction = pretty2 , configTimes = False , configAvailableFormatters = map (fmap V2.formatterToFormat) [ ("checks", V2.checks)@@ -195,7 +202,7 @@ ] setDepth :: Int -> Config -> Config-setDepth n c = c {configSmallCheckDepth = n}+setDepth n c = c {configSmallCheckDepth = Just n} quickCheckOptions :: [Option Config] quickCheckOptions = [@@ -224,11 +231,46 @@ setSeed :: Integer -> Config -> Config setSeed n c = c {configQuickCheckSeed = Just n} +data FailOn =+ FailOnEmpty+ | FailOnFocused+ | FailOnPending+ deriving (Bounded, Enum)++allFailOnItems :: [FailOn]+allFailOnItems = [minBound .. maxBound]++showFailOn :: FailOn -> String+showFailOn item = case item of+ FailOnEmpty -> "empty"+ FailOnFocused -> "focused"+ FailOnPending -> "pending"++readFailOn :: String -> Maybe FailOn+readFailOn = (`lookup` items)+ where+ items = map (showFailOn &&& id) allFailOnItems++splitOn :: Char -> String -> [String]+splitOn sep = go+ where+ go xs = case break (== sep) xs of+ ("", "") -> []+ (y, "") -> [y]+ (y, _ : ys) -> y : go ys+ 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"++ , undocumented $ mkFlag "fail-on-focused" setFailOnFocused "fail on focused spec items"+ , undocumented $ mkFlag "fail-on-pending" setFailOnPending "fail on pending spec items"++ , mkOption "fail-on" Nothing (argument "ITEMS" readFailOnItems (setFailOnItems True )) helpForFailOn+ , mkOption "no-fail-on" Nothing (argument "ITEMS" readFailOnItems (setFailOnItems False)) helpForFailOn+ , mkFlag "strict" setStrict $ "same as --fail-on=" <> showFailOnItems strict+ , 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)"@@ -237,6 +279,31 @@ , mkOption "jobs" (Just 'j') (argument "N" readMaxJobs setMaxJobs) "run at most N parallelizable tests simultaneously (default: number of available processors)" ] where+ strict = allFailOnItems++ readFailOnItems :: String -> Maybe [FailOn]+ readFailOnItems = mapM readFailOn . splitOn ','++ showFailOnItems :: [FailOn] -> String+ showFailOnItems = intercalate "," . map showFailOn++ helpForFailOn :: String+ helpForFailOn = unlines . flip map allFailOnItems $ \ item ->+ showFailOn item <> ": " <> help item+ where+ help item = case item of+ FailOnEmpty -> "fail if no spec items have been run"+ FailOnFocused -> "fail on focused spec items"+ FailOnPending -> "fail on pending spec items"++ setFailOnItems :: Bool -> [FailOn] -> Config -> Config+ setFailOnItems value = flip $ foldr (`setItem` value)+ where+ setItem item = case item of+ FailOnEmpty -> setFailOnEmpty+ FailOnFocused -> setFailOnFocused+ FailOnPending -> setFailOnPending+ readMaxJobs :: String -> Maybe Int readMaxJobs s = do n <- readMaybe s@@ -255,8 +322,17 @@ setFocusedOnly :: Bool -> Config -> Config setFocusedOnly value config = config {configFocusedOnly = value} + setFailOnEmpty :: Bool -> Config -> Config+ setFailOnEmpty value config = config {configFailOnEmpty = value}+ setFailOnFocused :: Bool -> Config -> Config setFailOnFocused value config = config {configFailOnFocused = value}++ setFailOnPending :: Bool -> Config -> Config+ setFailOnPending value config = config {configFailOnPending = value}++ setStrict :: Bool -> Config -> Config+ setStrict = (`setFailOnItems` strict) setFailFast :: Bool -> Config -> Config setFailFast value config = config {configFailFast = value}
hspec-core/src/Test/Hspec/Core/Example.hs view
@@ -5,8 +5,8 @@ {-# LANGUAGE TypeFamilies #-} {-# LANGUAGE TypeSynonymInstances #-} --- NOTE: re-exported from Test.Hspec.Core.Spec module Test.Hspec.Core.Example (+-- RE-EXPORTED from Test.Hspec.Core.Spec Example (..) , Params (..) , defaultParams@@ -19,6 +19,8 @@ , FailureReason (..) , safeEvaluate , safeEvaluateExample+-- END RE-EXPORTED from Test.Hspec.Core.Spec+, safeEvaluateResultStatus ) where import Prelude ()@@ -49,13 +51,13 @@ data Params = Params { paramsQuickCheckArgs :: QC.Args-, paramsSmallCheckDepth :: Int+, paramsSmallCheckDepth :: Maybe Int } deriving (Show) defaultParams :: Params defaultParams = Params { paramsQuickCheckArgs = QC.stdArgs-, paramsSmallCheckDepth = 5+, paramsSmallCheckDepth = Nothing } type Progress = (Int, Int)@@ -106,12 +108,19 @@ safeEvaluate :: IO Result -> IO Result safeEvaluate action = do- r <- safeTry $ action+ r <- safeTry action return $ case r of- Left e | Just result <- fromException e -> Result "" result- Left e | Just hunit <- fromException e -> Result "" $ hunitFailureToResult Nothing hunit- Left e -> Result "" $ Failure Nothing $ Error Nothing e+ Left e -> Result "" (toResultStatus e) Right result -> result++safeEvaluateResultStatus :: IO ResultStatus -> IO ResultStatus+safeEvaluateResultStatus action = either toResultStatus id <$> safeTry action++toResultStatus :: SomeException -> ResultStatus+toResultStatus e+ | Just result <- fromException e = result+ | Just hunit <- fromException e = hunitFailureToResult Nothing hunit+ | otherwise = Failure Nothing $ Error Nothing e instance Example Result where type Arg Result = ()
hspec-core/src/Test/Hspec/Core/Example/Location.hs view
@@ -3,11 +3,14 @@ Location(..) , extractLocation --- for testing+#ifdef TEST , parseAssertionFailed , parseCallStack , parseLocation , parseSourceSpan++, workaroundForIssue19236+#endif ) where import Prelude ()@@ -18,6 +21,10 @@ import Data.Maybe import GHC.IO.Exception +#ifdef mingw32_HOST_OS+import System.FilePath+#endif+ -- | @Location@ is used to represent source locations. data Location = Location { locationFile :: FilePath@@ -96,11 +103,11 @@ parseLocation :: String -> Maybe Location parseLocation input = case fmap breakColon (breakColon input) of- (file, (line, column)) -> Location file <$> readMaybe line <*> readMaybe column+ (file, (line, column)) -> mkLocation file <$> readMaybe line <*> readMaybe column parseSourceSpan :: String -> Maybe Location parseSourceSpan input = case breakColon input of- (file, xs) -> (uncurry $ Location file) <$> (tuple <|> colonSeparated)+ (file, xs) -> (uncurry $ mkLocation file) <$> (tuple <|> colonSeparated) where lineAndColumn :: String lineAndColumn = takeWhile (/= '-') xs@@ -114,3 +121,14 @@ breakColon :: String -> (String, String) breakColon = fmap (drop 1) . break (== ':')++mkLocation :: FilePath -> Int -> Int -> Location+mkLocation file line column = Location (workaroundForIssue19236 file) line column++workaroundForIssue19236 :: FilePath -> FilePath -- https://gitlab.haskell.org/ghc/ghc/-/issues/19236+workaroundForIssue19236 =+#ifdef mingw32_HOST_OS+ joinPath . splitDirectories+#else+ id+#endif
hspec-core/src/Test/Hspec/Core/Format.hs view
@@ -56,15 +56,17 @@ data FormatConfig = FormatConfig { formatConfigUseColor :: Bool+, formatConfigReportProgress :: Bool , formatConfigOutputUnicode :: Bool , formatConfigUseDiff :: Bool-, formatConfigPrettyPrint :: Bool+, formatConfigPrettyPrint :: Bool -- ^ Deprecated: use `formatConfigPrettyPrintFunction` instead+, formatConfigPrettyPrintFunction :: Maybe (String -> String -> (String, String)) , formatConfigPrintTimes :: Bool , formatConfigHtmlOutput :: Bool , formatConfigPrintCpuTime :: Bool , formatConfigUsedSeed :: Integer , formatConfigExpectedTotalCount :: Int-} deriving (Eq, Show)+} data Signal = Ok | NotOk SomeException
hspec-core/src/Test/Hspec/Core/Formatters.hs view
@@ -1,2 +1,6 @@-module Test.Hspec.Core.Formatters (module V1) where+-- |+-- Deprecated\: Use "Test.Hspec.Core.Formatters.V1" instead.+module Test.Hspec.Core.Formatters+-- {-# DEPRECATED "Use \"Test.Hspec.Core.Formatters.V1\" instead." #-}+(module V1) where import Test.Hspec.Core.Formatters.V1 as V1
hspec-core/src/Test/Hspec/Core/Formatters/Internal.hs view
@@ -36,11 +36,13 @@ , useDiff , prettyPrint+, prettyPrintFunction , extraChunk , missingChunk #ifdef TEST-, overwriteWith+, runFormatM+, splitLines #endif ) where @@ -49,11 +51,12 @@ import qualified System.IO as IO import System.IO (Handle, stdout)-import Control.Exception (bracket_)+import Control.Exception (bracket_, bracket) import System.Console.ANSI import Control.Monad.Trans.State hiding (state, gets, modify) import Control.Monad.IO.Class import Data.Char (isSpace)+import Data.List (groupBy) import qualified System.CPUTime as CPUTime import Test.Hspec.Core.Formatters.V1.Monad (FailureRecord(..))@@ -91,13 +94,14 @@ 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+ Failure loc err -> addFailure $ FailureRecord (loc <|> itemLocation item) path err formatterItemDone path item Done _ -> formatterDone+ where+ addFailure r = modify $ \ s -> s { stateFailMessages = r : stateFailMessages s } -- | Get the number of failed examples encountered so far. getFailCount :: FormatM Int@@ -109,8 +113,16 @@ -- | Return `True` if the user requested pretty diffs, `False` otherwise. prettyPrint :: FormatM Bool-prettyPrint = getConfig formatConfigPrettyPrint+prettyPrint = maybe False (const True) <$> getConfig formatConfigPrettyPrintFunction+{-# DEPRECATED prettyPrint "use `prettyPrintFunction` instead" #-} +-- | Return a function for pretty-printing if the user requested pretty diffs,+-- `Nothing` otherwise.+--+-- @since 2.10.0+prettyPrintFunction :: FormatM (Maybe (String -> String -> (String, String)))+prettyPrintFunction = getConfig formatConfigPrettyPrintFunction+ -- | Return `True` if the user requested unicode output, `False` otherwise. outputUnicode :: FormatM Bool outputUnicode = getConfig formatConfigOutputUnicode@@ -144,8 +156,8 @@ , stateFailMessages :: [FailureRecord] , stateCpuStartTime :: Maybe Integer , stateStartTime :: Seconds-, stateTransientOutput :: String-, stateConfig :: FormatConfig+, stateConfig :: FormatConfig+, stateColor :: Maybe SGR } getConfig :: (FormatConfig -> a) -> FormatM a@@ -164,12 +176,27 @@ deriving (Functor, Applicative, Monad, MonadIO) runFormatM :: FormatConfig -> FormatM a -> IO a-runFormatM config (FormatM action) = do+runFormatM config (FormatM action) = withLineBuffering $ do time <- getMonotonicTime cpuTime <- if (formatConfigPrintCpuTime config) then Just <$> CPUTime.getCPUTime else pure Nothing- st <- newIORef (FormatterState 0 0 [] cpuTime time "" config)- evalStateT action st + let+ progress = formatConfigReportProgress config && not (formatConfigHtmlOutput config)+ state = FormatterState {+ stateSuccessCount = 0+ , statePendingCount = 0+ , stateFailMessages = []+ , stateCpuStartTime = cpuTime+ , stateStartTime = time+ , stateConfig = config { formatConfigReportProgress = progress }+ , stateColor = Nothing+ }+ newIORef state >>= evalStateT action++withLineBuffering :: IO a -> IO a+withLineBuffering action = bracket (IO.hGetBuffering stdout) (IO.hSetBuffering stdout) $ \ _ -> do+ IO.hSetBuffering stdout IO.LineBuffering >> action+ -- | Increase the counter for successful examples increaseSuccessCount :: FormatM () increaseSuccessCount = modify $ \s -> s {stateSuccessCount = succ $ stateSuccessCount s}@@ -186,10 +213,6 @@ getPendingCount :: FormatM Int getPendingCount = gets statePendingCount --- | Append to the list of accumulated failure messages.-addFailMessage :: Maybe Location -> Path -> FailureReason -> FormatM ()-addFailMessage loc p m = modify $ \s -> s {stateFailMessages = FailureRecord loc p m : stateFailMessages s}- -- | Get the list of accumulated failure messages. getFailMessages :: FormatM [FailureRecord] getFailMessages = reverse `fmap` gets stateFailMessages@@ -199,35 +222,35 @@ getExpectedTotalCount :: FormatM Int getExpectedTotalCount = getConfig formatConfigExpectedTotalCount -overwriteWith :: String -> String -> String-overwriteWith old new- | n == 0 = new- | otherwise = '\r' : new ++ replicate (n - length new) ' '- where- n = length old- writeTransient :: String -> FormatM () writeTransient new = do- useColor <- getConfig formatConfigUseColor- when (useColor) $ do- old <- gets stateTransientOutput- write $ old `overwriteWith` new- modify $ \ state -> state {stateTransientOutput = new}+ reportProgress <- getConfig formatConfigReportProgress+ when (reportProgress) $ do h <- getHandle+ write $ new liftIO $ IO.hFlush h--clearTransientOutput :: FormatM ()-clearTransientOutput = do- n <- length <$> gets stateTransientOutput- unless (n == 0) $ do- write ("\r" ++ replicate n ' ' ++ "\r")- modify $ \ state -> state {stateTransientOutput = ""}+ write $ "\r" ++ replicate (length new) ' ' ++ "\r" -- | Append some output to the report. write :: String -> FormatM ()-write s = do+write = mapM_ writeChunk . splitLines++splitLines :: String -> [String]+splitLines = groupBy (\ a b -> isNewline a == isNewline b)+ where+ isNewline = (== '\n')++writeChunk :: String -> FormatM ()+writeChunk str = do h <- getHandle- liftIO $ IO.hPutStr h s+ let+ plainOutput = IO.hPutStr h str+ colorOutput color = bracket_ (hSetSGR h [color]) (hSetSGR h [Reset]) plainOutput+ mColor <- gets stateColor+ liftIO $ case mColor of+ Just (SetColor Foreground _ _) | all isSpace str -> plainOutput+ Just color -> colorOutput color+ Nothing -> plainOutput -- | Set output color to red, run given action, and finally restore the default -- color.@@ -259,21 +282,15 @@ htmlSpan cls action = write ("<span class=\"" ++ cls ++ "\">") *> action <* write "</span>" withColor_ :: SGR -> FormatM a -> FormatM a-withColor_ color (FormatM action) = do- useColor <- getConfig formatConfigUseColor- h <- getHandle-- FormatM . StateT $ \st -> do- bracket_-- -- set color- (when useColor $ hSetSGR h [color])-- -- reset colors- (when useColor $ hSetSGR h [Reset])+withColor_ color action = do+ oldColor <- gets stateColor+ setColor (Just color) *> action <* setColor oldColor - -- run action- (runStateT action st)+setColor :: Maybe SGR -> FormatM ()+setColor color = do+ useColor <- getConfig formatConfigUseColor+ when useColor $ do+ modify (\ state -> state { stateColor = color }) -- | Output given chunk in red. extraChunk :: String -> FormatM ()
hspec-core/src/Test/Hspec/Core/Formatters/Pretty.hs view
@@ -11,7 +11,6 @@ import Prelude () import Test.Hspec.Core.Compat hiding (shows, intercalate) -import Control.Arrow import Data.Char import Data.String import Data.List (intersperse)@@ -99,7 +98,7 @@ Char c -> shows c String str -> if unicode then Builder $ ushows str else shows str Integer n -> shows n- Rational n -> shows n+ Rational n -> fromString n render :: Expression -> Builder render expr = case expr of
hspec-core/src/Test/Hspec/Core/Formatters/Pretty/Parser.hs view
@@ -16,8 +16,14 @@ import Test.Hspec.Core.Compat hiding (fail) import Test.Hspec.Core.Formatters.Pretty.Parser.Types -#if __GLASGOW_HASKELL__ < 802 || __GLASGOW_HASKELL__ > 902+#ifndef __GHCJS__+#if __GLASGOW_HASKELL__ >= 802 && __GLASGOW_HASKELL__ <= 904+#define PRETTY_PRINTING_SUPPORTED+#endif+#endif +#ifndef PRETTY_PRINTING_SUPPORTED+ parseExpression :: String -> Maybe Expression parseExpression _ = Nothing @@ -29,7 +35,12 @@ import GHC.Stack import GHC.Exception (throw, errorCallWithCallStackException) -#if __GLASGOW_HASKELL__ >= 804+#if __GLASGOW_HASKELL__ >= 904+import GHC.Utils.Error+import GHC.Utils.Outputable+#endif++#if __GLASGOW_HASKELL__ >= 804 && __GLASGOW_HASKELL__ < 904 import GHC.LanguageExtensions.Type #endif @@ -46,7 +57,6 @@ import GHC.Data.StringBuffer import GHC.Data.FastString import GHC.Types.SrcLoc-import qualified GHC.Data.EnumSet as EnumSet import GHC.Types.Name import GHC.Types.Name.Reader import GHC.Parser.PostProcess hiding (Tuple)@@ -60,7 +70,12 @@ import RdrName import BasicTypes import Module-#if __GLASGOW_HASKELL__ >= 804+#endif++#if __GLASGOW_HASKELL__ >= 804 && __GLASGOW_HASKELL__ < 904+#if __GLASGOW_HASKELL__ >= 900+import qualified GHC.Data.EnumSet as EnumSet+#else import qualified EnumSet #endif #endif@@ -140,21 +155,30 @@ HsOverLit _x lit -> toExpression lit HsApp _x f x -> App <$> toExpression f <*> toExpression x NegApp _x e _ -> toExpression e >>= \ x -> case x of- Literal (Rational n) -> return $ Literal (Rational $ negate n)+ Literal (Rational n) -> return $ Literal (Rational $ '-' : n) Literal (Integer n) -> return $ Literal (Integer $ negate n) _ -> fail "NegApp"- HsPar _x e -> Parentheses <$> toExpression e++ HsPar _x+#if __GLASGOW_HASKELL__ >= 904+ _ e _ ->+#else+ e ->+#endif+ Parentheses <$> toExpression e ExplicitTuple _x xs _ -> Tuple <$> mapM toExpression xs ExplicitList _ _listSynExpr xs -> List <$> mapM toExpression xs RecCon(name, fields) -> Record (showRdrName name) <$> (recordFields $ rec_flds fields) where+#if __GLASGOW_HASKELL__ >= 904+ hsRecFieldLbl = hfbLHS+ hsRecFieldArg = hfbRHS+#endif fieldName = showFieldLabel . unLoc . hsRecFieldLbl recordFields = mapM (recordField . unLoc) recordField field = (,) (fieldName field) <$> toExpression (hsRecFieldArg field) REJECT(HsUnboundVar)- REJECT(HsConLikeOut)- REJECT(HsRecFld) REJECT(HsOverLabel) REJECT(HsIPVar) REJECT(HsLam)@@ -172,14 +196,15 @@ REJECT(RecordUpd) REJECT(ExprWithTySig) REJECT(ArithSeq)- REJECT(HsBracket)- REJECT(HsRnBracketOut)- REJECT(HsTcBracketOut) REJECT(HsSpliceE) REJECT(HsProc) REJECT(HsStatic)- REJECT(HsTick)- REJECT(HsBinTick)++#if __GLASGOW_HASKELL__ >= 904+ REJECT(HsRecSel)+ REJECT(HsTypedBracket)+ REJECT(HsUntypedBracket)+#endif #if __GLASGOW_HASKELL__ >= 902 REJECT(HsGetField) REJECT(HsProjection)@@ -187,13 +212,23 @@ #if __GLASGOW_HASKELL__ >= 900 REJECT(HsPragE) #endif-#if __GLASGOW_HASKELL__ <= 810++#if __GLASGOW_HASKELL__ < 904+ REJECT(HsConLikeOut)+ REJECT(HsRecFld)+ REJECT(HsBracket)+ REJECT(HsRnBracketOut)+ REJECT(HsTcBracketOut)+ REJECT(HsTick)+ REJECT(HsBinTick)+#endif+#if __GLASGOW_HASKELL__ < 900 REJECT(HsSCC) REJECT(HsCoreAnn) REJECT(HsTickPragma) REJECT(HsWrap) #endif-#if __GLASGOW_HASKELL__ <= 808+#if __GLASGOW_HASKELL__ < 810 REJECT(HsArrApp) REJECT(HsArrForm) REJECT(EWildPat)@@ -201,7 +236,7 @@ REJECT(EViewPat) REJECT(ELazyPat) #endif-#if __GLASGOW_HASKELL__ <= 804+#if __GLASGOW_HASKELL__ < 806 REJECT(HsAppTypeOut) REJECT(ExplicitPArr) REJECT(ExprWithTySigOut)@@ -238,12 +273,14 @@ HsIsString _ str -> toExpression str instance ToExpression FractionalLit where- toExpression fl = toExpression (fl_value fl)--#if __GLASGOW_HASKELL__ >= 902-fl_value :: FractionalLit -> Rational-fl_value = rationalFromFractionalLit+ toExpression fl = case fl_text fl of+#if __GLASGOW_HASKELL__ > 802+ REJECT(NoSourceText)+ SourceText n+#else+ n #endif+ -> return . Literal $ Rational n instance ToExpression FastString where toExpression = return . Literal . String . unpackFS@@ -251,9 +288,6 @@ instance ToExpression Integer where toExpression = return . Literal . Integer -instance ToExpression Rational where- toExpression = return . Literal . Rational- instance ToExpression Char where toExpression = return . Literal . Char @@ -317,14 +351,24 @@ location = mkRealSrcLoc "" 1 1 input = stringToStringBuffer str parseState = initParserState opts input location- opts = mkParserOpts warn extensions False False False True+ opts = mkParserOpts warn+#if __GLASGOW_HASKELL__ >= 904+ (DiagOpts mempty mempty False False Nothing defaultSDocContext)+#endif+ extensions False False False True -#if __GLASGOW_HASKELL__ >= 804- extensions = EnumSet.fromList [TraditionalRecordSyntax]+#if __GLASGOW_HASKELL__ >= 804 && __GLASGOW_HASKELL__ < 904 warn = EnumSet.empty #else- extensions = mempty warn = mempty+#endif++#if __GLASGOW_HASKELL__ >= 904+ extensions = ["TraditionalRecordSyntax"]+#elif __GLASGOW_HASKELL__ >= 804+ extensions = EnumSet.fromList [TraditionalRecordSyntax]+#else+ extensions = mempty #endif #if __GLASGOW_HASKELL__ <= 900
hspec-core/src/Test/Hspec/Core/Formatters/Pretty/Parser/Types.hs view
@@ -17,5 +17,5 @@ Char Char | String String | Integer Integer- | Rational Rational+ | Rational String deriving (Eq, Show)
hspec-core/src/Test/Hspec/Core/Formatters/V2.hs view
@@ -59,6 +59,7 @@ , useDiff , prettyPrint+, prettyPrintFunction , extraChunk , missingChunk @@ -126,12 +127,12 @@ , useDiff , prettyPrint+ , prettyPrintFunction , extraChunk , missingChunk ) import Test.Hspec.Core.Formatters.Diff-import Test.Hspec.Core.Formatters.Pretty (pretty2) silent :: Formatter silent = Formatter {@@ -275,11 +276,11 @@ NoReason -> return () Reason err -> withFailColor $ indent err ExpectedButGot preface expected_ actual_ -> do- pretty <- prettyPrint+ pretty <- prettyPrintFunction let- (expected, actual)- | pretty = pretty2 unicode expected_ actual_- | otherwise = (expected_, actual_)+ (expected, actual) = case pretty of+ Just f -> f expected_ actual_+ Nothing -> (expected_, actual_) mapM_ indent preface @@ -311,7 +312,9 @@ where indentation_ = indentation ++ replicate (length pre) ' ' - Error _ e -> withFailColor . indent $ (("uncaught exception: " ++) . formatException) e+ Error info e -> do+ mapM_ indent info+ withFailColor . indent $ "uncaught exception: " ++ formatException e writeLine ""
hspec-core/src/Test/Hspec/Core/Hooks.hs view
@@ -1,3 +1,4 @@+{-# LANGUAGE CPP #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE ConstraintKinds #-}@@ -22,15 +23,18 @@ , mapSubject , ignoreSubject++#ifdef TEST+, decompose+#endif ) where import Prelude () import Test.Hspec.Core.Compat-import Data.CallStack+import Data.CallStack (HasCallStack) -import Control.Exception (SomeException, finally, throwIO, try, catch)-import Control.Concurrent.MVar-import Control.Concurrent.Async+import Control.Exception (SomeException, finally, throwIO, try)+import Control.Concurrent import Test.Hspec.Core.Example import Test.Hspec.Core.Tree@@ -49,19 +53,19 @@ beforeWith action = aroundWith $ \e x -> action x >>= e -- | Run a custom action before the first spec item.-beforeAll :: IO a -> SpecWith a -> Spec+beforeAll :: HasCallStack => IO a -> SpecWith a -> Spec beforeAll action spec = do mvar <- runIO (newMVar Empty) before (memoize mvar action) spec -- | Run a custom action before the first spec item.-beforeAll_ :: IO () -> SpecWith a -> SpecWith a+beforeAll_ :: HasCallStack => IO () -> SpecWith a -> SpecWith a beforeAll_ action spec = do mvar <- runIO (newMVar Empty) before_ (memoize mvar action) spec -- | Run a custom action with an argument before the first spec item.-beforeAllWith :: (b -> IO a) -> SpecWith a -> SpecWith b+beforeAllWith :: HasCallStack => (b -> IO a) -> SpecWith a -> SpecWith b beforeAllWith action spec = do mvar <- runIO (newMVar Empty) beforeWith (memoize mvar . action) spec@@ -71,14 +75,14 @@ | Memoized a | Failed SomeException -memoize :: MVar (Memoized a) -> IO a -> IO a+memoize :: HasCallStack => MVar (Memoized a) -> IO a -> IO a memoize mvar action = do result <- modifyMVar mvar $ \ma -> case ma of Empty -> do a <- try action return (either Failed Memoized a, a) Memoized a -> return (ma, Right a)- Failed _ -> throwIO (Pending Nothing (Just "exception in beforeAll-hook (see previous failure)"))+ Failed _ -> throwIO (Pending Nothing (Just $ "exception in " <> maybe "beforeAll" fst callSite <> "-hook (see previous failure)")) either throwIO return result -- | Run a custom action after every spec item.@@ -95,11 +99,11 @@ -- | Run a custom action after the last spec item. afterAll :: HasCallStack => ActionWith a -> SpecWith a -> SpecWith a-afterAll action spec = runIO (runSpecM spec) >>= fromSpecList . return . NodeWithCleanup location action+afterAll action = aroundAllWith (\ hook a -> hook a >> action a) -- | Run a custom action after the last spec item. afterAll_ :: HasCallStack => IO () -> SpecWith a -> SpecWith a-afterAll_ action = afterAll (\_ -> action)+afterAll_ action = mapSpecForest (return . NodeWithCleanup callSite action) -- | Run a custom action before and/or after every spec item. around_ :: (IO () -> IO ()) -> SpecWith a -> SpecWith a@@ -107,60 +111,77 @@ -- | Run a custom action before and/or after every spec item. aroundWith :: (ActionWith a -> ActionWith b) -> SpecWith a -> SpecWith b-aroundWith action = mapSpecItem action (modifyAroundAction action)+aroundWith = mapSpecItem_ . modifyAroundAction modifyAroundAction :: (ActionWith a -> ActionWith b) -> Item a -> Item b modifyAroundAction action item@Item{itemExample = e} = item{ itemExample = \params aroundAction -> e params (aroundAction . action) } -- | Wrap an action around the given spec.-aroundAll :: (ActionWith a -> IO ()) -> SpecWith a -> Spec+aroundAll :: HasCallStack => (ActionWith a -> IO ()) -> SpecWith a -> Spec aroundAll action = aroundAllWith $ \ e () -> action e -- | Wrap an action around the given spec.-aroundAll_ :: (IO () -> IO ()) -> SpecWith a -> SpecWith a+aroundAll_ :: HasCallStack => (IO () -> IO ()) -> SpecWith a -> SpecWith a aroundAll_ action spec = do- allSpecItemsDone <- runIO newEmptyMVar- workerRef <- runIO newEmptyMVar- let- acquire :: IO ()- acquire = do- resource <- newEmptyMVar- worker <- async $ do- action $ do- signal resource- waitFor allSpecItemsDone- putMVar workerRef worker- unwrapExceptionsFromLinkedThread $ do- link worker- waitFor resource- release :: IO ()- release = signal allSpecItemsDone >> takeMVar workerRef >>= wait- beforeAll_ acquire $ afterAll_ release spec+ (acquire, release) <- runIO $ decompose (action .)+ beforeAll_ (acquire ()) $ afterAll_ release spec -- | Wrap an action around the given spec. Changes the arg type inside.-aroundAllWith :: forall a b. (ActionWith a -> ActionWith b) -> SpecWith a -> SpecWith b+aroundAllWith :: forall a b. HasCallStack => (ActionWith a -> ActionWith b) -> SpecWith a -> SpecWith b aroundAllWith action spec = do- allSpecItemsDone <- runIO newEmptyMVar- workerRef <- runIO newEmptyMVar+ (acquire, release) <- runIO $ decompose action+ beforeAllWith acquire $ afterAll_ release spec++data Acquired a = Acquired a | ExceptionDuringAcquire SomeException+data Released = Released | ExceptionDuringRelease SomeException++decompose :: forall a b. ((a -> IO ()) -> b -> IO ()) -> IO (b -> IO a, IO ())+decompose action = do+ doCleanupNow <- newEmptyMVar+ acquired <- newEmptyMVar+ released <- newEmptyMVar+ let+ notify :: Either SomeException () -> IO ()+ -- `notify` is guaranteed to run without being interrupted by an async+ -- exception for the following reasons:+ --+ -- 1. `forkFinally` runs the final action within `mask`+ -- 2. `tryPutMVar` is guaranteed not to be interruptible+ -- 3. `putMVar` is guaranteed not to be interruptible on an empty `MVar`+ notify r = case r of+ Left err -> do+ exceptionDuringAcquire <- tryPutMVar acquired (ExceptionDuringAcquire err)+ putMVar released $ if exceptionDuringAcquire then Released else ExceptionDuringRelease err+ Right () -> do+ putMVar released Released++ forkWorker :: b -> IO ()+ forkWorker b = void . flip forkFinally notify $ do+ flip action b $ \ a -> do+ putMVar acquired (Acquired a)+ waitFor doCleanupNow+ acquire :: b -> IO a acquire b = do- resource <- newEmptyMVar- worker <- async $ do- flip action b $ \ a -> do- putMVar resource a- waitFor allSpecItemsDone- putMVar workerRef worker- unwrapExceptionsFromLinkedThread $ do- link worker- takeMVar resource+ forkWorker b+ r <- readMVar acquired -- This does not work reliably with base < 4.7+ case r of+ Acquired a -> return a+ ExceptionDuringAcquire err -> throwIO err+ release :: IO ()- release = signal allSpecItemsDone >> takeMVar workerRef >>= wait- beforeAllWith acquire $ afterAll_ release spec+ release = do+ acquireHasNotBeenCalled <- isEmptyMVar acquired -- NOTE: This can happen if an outer beforeAll fails+ unless acquireHasNotBeenCalled $ do+ signal doCleanupNow+ r <- takeMVar released+ case r of+ Released -> return ()+ ExceptionDuringRelease err -> throwIO err -unwrapExceptionsFromLinkedThread :: IO a -> IO a-unwrapExceptionsFromLinkedThread = (`catch` \ (ExceptionInLinkedThread _ e) -> throwIO e)+ return (acquire, release) type BinarySemaphore = MVar ()
hspec-core/src/Test/Hspec/Core/Runner.hs view
@@ -5,8 +5,34 @@ -- Stability: provisional module Test.Hspec.Core.Runner ( -- * Running a spec+{- |+To run a spec `hspec` performs a sequence of steps:++1. Evaluate a `Spec` to a forest of `SpecTree`s+1. Read config values from the command-line, config files and the process environment+1. Execute each spec item of the forest and report results to `stdout`+1. Exit with `exitFailure` if at least on spec item fails++The four primitives `evalSpec`, `readConfig`, `runSpecForest` and+`evaluateResult` each perform one of these steps respectively.++`hspec` is defined in terms of these primitives:++@+hspec = `evalSpec` `defaultConfig` >=> \\ (config, spec) ->+ `getArgs`+ >>= `readConfig` config+ >>= `withArgs` [] . `runSpecForest` spec+ >>= `evaluateResult`+@++If you need more control over how a spec is run use these primitives individually.++-} hspec-, runSpec+, evalSpec+, runSpecForest+, evaluateResult -- * Config , Config (..)@@ -14,20 +40,40 @@ , UnicodeMode(..) , Path , defaultConfig+, registerFormatter+, registerDefaultFormatter , configAddFilter , readConfig --- * Summary-, Summary (..)-, isSuccess-, evaluateSummary+-- * Result +-- ** Spec Result+, Test.Hspec.Core.Runner.Result.SpecResult+, Test.Hspec.Core.Runner.Result.specResultItems+, Test.Hspec.Core.Runner.Result.specResultSuccess++-- ** Result Item+, Test.Hspec.Core.Runner.Result.ResultItem+, Test.Hspec.Core.Runner.Result.resultItemPath+, Test.Hspec.Core.Runner.Result.resultItemStatus+, Test.Hspec.Core.Runner.Result.resultItemIsFailure++-- ** Result Item Status+, Test.Hspec.Core.Runner.Result.ResultItemStatus(..)+ -- * Legacy--- | The following primitives are deprecated. Use `runSpec` instead.+-- | The following primitives are deprecated. Use `runSpecForest` instead. , hspecWith , hspecResult , hspecWithResult+, runSpec +-- ** Summary+, Summary (..)+, toSummary+, isSuccess+, evaluateSummary+ #ifdef TEST , rerunAll , specToEvalForest@@ -40,6 +86,7 @@ import Test.Hspec.Core.Compat import Data.Maybe+import NonEmpty (nonEmpty) import System.IO import System.Environment (getArgs, withArgs) import System.Exit@@ -47,15 +94,14 @@ import System.Random import Control.Monad.ST import Data.STRef-import System.Console.ANSI (hSupportsANSI) -import System.Console.ANSI (hHideCursor, hShowCursor)+import System.Console.ANSI (hSupportsANSI, hHideCursor, hShowCursor) import qualified Test.QuickCheck as QC import Test.Hspec.Core.Util (Path)-import Test.Hspec.Core.Spec+import Test.Hspec.Core.Spec hiding (pruneTree, pruneForest) import Test.Hspec.Core.Config-import Test.Hspec.Core.Format (FormatConfig(..))+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.FailureReport@@ -63,9 +109,21 @@ import Test.Hspec.Core.Shuffle import Test.Hspec.Core.Runner.PrintSlowSpecItems-import Test.Hspec.Core.Runner.Eval+import Test.Hspec.Core.Runner.Eval hiding (Tree(..))+import qualified Test.Hspec.Core.Runner.Eval as Eval+import Test.Hspec.Core.Runner.Result -applyFilterPredicates :: Config -> [EvalTree] -> [EvalTree]+-- |+-- Make a formatter available for use with @--format@.+registerFormatter :: (String, FormatConfig -> IO Format) -> Config -> Config+registerFormatter formatter config = config { configAvailableFormatters = formatter : configAvailableFormatters config }++-- |+-- Make a formatter available for use with @--format@ and use it by default.+registerDefaultFormatter :: (String, FormatConfig -> IO Format) -> Config -> Config+registerDefaultFormatter formatter@(_, format) config = (registerFormatter formatter config) { configFormat = Just format }++applyFilterPredicates :: Config -> [Tree c EvalItem] -> [Tree c EvalItem] applyFilterPredicates c = filterForestWithLabels p where include :: Path -> Bool@@ -79,7 +137,7 @@ where path = (groups, evalItemDescription item) -applyDryRun :: Config -> [EvalTree] -> [EvalTree]+applyDryRun :: Config -> [EvalItemTree] -> [EvalItemTree] applyDryRun c | configDryRun c = bimapForest removeCleanup markSuccess | otherwise = id@@ -94,15 +152,29 @@ -- Exit with `exitFailure` if at least one spec item fails. -- -- /Note/: `hspec` handles command-line options and reads config files. This--- is not always desired. Use `runSpec` if you need more control over these--- aspects.+-- is not always desirable. Use `evalSpec` and `runSpecForest` if you need+-- more control over these aspects. hspec :: Spec -> IO ()-hspec spec =+hspec = evalSpec defaultConfig >=> \ (config, spec) -> getArgs- >>= readConfig defaultConfig- >>= doNotLeakCommandLineArgumentsToExamples . runSpec spec- >>= evaluateSummary+ >>= readConfig config+ >>= doNotLeakCommandLineArgumentsToExamples . runSpecForest spec+ >>= evaluateResult +-- |+-- Evaluate a `Spec` to a forest of `SpecTree`s. This does not execute any+-- spec items, but it does run any IO that is used during spec construction+-- time (see `runIO`).+--+-- A `Spec` may modify a `Config` through `modifyConfig`. These modifications+-- are applied to the given config (the first argument).+--+-- @since 2.10.0+evalSpec :: Config -> SpecWith a -> IO (Config, [SpecTree a])+evalSpec config spec = do+ (Endo f, forest) <- runSpecM spec+ return (f config, forest)+ -- Add a seed to given config if there is none. That way the same seed is used -- for all properties. This helps with --seed and --rerun. ensureSeed :: Config -> IO Config@@ -115,7 +187,7 @@ -- | Run given spec with custom options. -- This is similar to `hspec`, but more flexible. hspecWith :: Config -> Spec -> IO ()-hspecWith config spec = getArgs >>= readConfig config >>= doNotLeakCommandLineArgumentsToExamples . runSpec spec >>= evaluateSummary+hspecWith defaults = hspecWithResult defaults >=> evaluateSummary -- | `True` if the given `Summary` indicates that there were no -- failures, `False` otherwise.@@ -127,13 +199,16 @@ evaluateSummary :: Summary -> IO () evaluateSummary summary = unless (isSuccess summary) exitFailure +evaluateResult :: SpecResult -> IO ()+evaluateResult result = unless (specResultSuccess result) exitFailure+ -- | Run given spec and returns a summary of the test run. -- -- /Note/: `hspecResult` does not exit with `exitFailure` on failing spec -- items. If you need this, you have to check the `Summary` yourself and act -- accordingly. hspecResult :: Spec -> IO Summary-hspecResult spec = getArgs >>= readConfig defaultConfig >>= doNotLeakCommandLineArgumentsToExamples . runSpec spec+hspecResult = hspecWithResult defaultConfig -- | Run given spec with custom options and returns a summary of the test run. --@@ -141,21 +216,32 @@ -- items. If you need this, you have to check the `Summary` yourself and act -- accordingly. hspecWithResult :: Config -> Spec -> IO Summary-hspecWithResult config spec = getArgs >>= readConfig config >>= doNotLeakCommandLineArgumentsToExamples . runSpec spec+hspecWithResult defaults = evalSpec defaults >=> \ (config, spec) ->+ getArgs+ >>= readConfig config+ >>= doNotLeakCommandLineArgumentsToExamples . fmap toSummary . runSpecForest spec -- |--- `runSpec` is the most basic primitive to run a spec. `hspec` is defined in--- terms of @runSpec@:+-- /Note/: `runSpec` is deprecated. It ignores any modifications applied+-- through `modifyConfig`. Use `evalSpec` and `runSpecForest` instead.+runSpec :: Spec -> Config -> IO Summary+runSpec spec config = evalSpec defaultConfig spec >>= fmap toSummary . flip runSpecForest config . snd++-- |+-- `runSpecForest` is the most basic primitive to run a spec. `hspec` is+-- defined in terms of @runSpecForest@: -- -- @--- hspec spec =+-- hspec = `evalSpec` `defaultConfig` >=> \\ (config, spec) -> -- `getArgs`--- >>= `readConfig` `defaultConfig`--- >>= `withArgs` [] . runSpec spec--- >>= `evaluateSummary`+-- >>= `readConfig` config+-- >>= `withArgs` [] . runSpecForest spec+-- >>= `evaluateResult` -- @-runSpec :: Spec -> Config -> IO Summary-runSpec spec c_ = do+--+-- @since 2.10.0+runSpecForest :: [SpecTree ()] -> Config -> IO SpecResult+runSpecForest spec c_ = do oldFailureReport <- readFailureReportOnRerun c_ c <- ensureSeed (applyFailureReport oldFailureReport c_)@@ -172,17 +258,28 @@ then rerunAllMode c oldFailureReport else normalMode c where- normalMode c = runSpec_ c spec+ normalMode c = runSpecForest_ c spec rerunAllMode c oldFailureReport = do- summary <- runSpec_ c spec- if rerunAll c oldFailureReport summary- then runSpec spec c_- else return summary+ result <- runSpecForest_ c spec+ if rerunAll c oldFailureReport result+ then runSpecForest spec c_+ else return result +runSpecForest_ :: Config -> [SpecTree ()] -> IO SpecResult+runSpecForest_ config spec = runEvalTree config (specToEvalForest config spec)++mapItem :: (Item a -> Item b) -> [SpecTree a] -> [SpecTree b]+mapItem f = map (fmap f)++failFocusedItems :: Config -> [SpecTree a] -> [SpecTree a]+failFocusedItems config+ | configFailOnFocused config = mapItem failFocused+ | otherwise = id+ failFocused :: Item a -> Item a failFocused item = item {itemExample = example} where- failure = Failure Nothing (Reason "item is focused; failing due to --fail-on-focused")+ failure = Failure Nothing (Reason "item is focused; failing due to --fail-on=focused") example | itemIsFocused item = \ params hook p -> do Result info status <- itemExample item params hook p@@ -192,38 +289,49 @@ Failure{} -> status | otherwise = itemExample item -failFocusedItems :: Config -> Spec -> Spec-failFocusedItems config spec- | configFailOnFocused config = mapSpecItem_ failFocused spec- | otherwise = spec+failPendingItems :: Config -> [SpecTree a] -> [SpecTree a]+failPendingItems config+ | configFailOnPending config = mapItem failPending+ | otherwise = id -focusSpec :: Config -> Spec -> Spec+failPending :: Item a -> Item a+failPending item = item {itemExample = example}+ where+ example params hook p = do+ Result info status <- itemExample item params hook p+ return $ Result info $ case status of+ Pending loc _ -> Failure loc (Reason "item is pending; failing due to --fail-on=pending")+ _ -> status++focusSpec :: Config -> [SpecTree a] -> [SpecTree a] focusSpec config spec | configFocusedOnly config = spec- | otherwise = focus spec+ | otherwise = focusForest spec -runSpec_ :: Config -> Spec -> IO Summary-runSpec_ config spec = do- filteredSpec <- specToEvalForest config spec+runEvalTree :: Config -> [EvalTree] -> IO SpecResult+runEvalTree config spec = do let+ failOnEmpty = configFailOnEmpty config seed = (fromJust . configQuickCheckSeed) config qcArgs = configQuickCheckArgs config- !numberOfItems = countSpecItems filteredSpec+ !numberOfItems = countSpecItems spec concurrentJobs <- case configConcurrentJobs config of Nothing -> getDefaultConcurrentJobs Just n -> return n - useColor <- colorOutputSupported (configColorMode config) (hSupportsANSI stdout)+ (reportProgress, useColor) <- colorOutputSupported (configColorMode config) (hSupportsANSI stdout) outputUnicode <- unicodeOutputSupported (configUnicodeMode config) stdout - results <- withHiddenCursor useColor stdout $ do+ results <- fmap (toSpecResult failOnEmpty) . withHiddenCursor reportProgress stdout $ do let formatConfig = FormatConfig { formatConfigUseColor = useColor+ , formatConfigReportProgress = reportProgress , formatConfigOutputUnicode = outputUnicode , formatConfigUseDiff = configDiff config , formatConfigPrettyPrint = configPrettyPrint config+ , formatConfigPrettyPrintFunction = if configPrettyPrint config then Just (configPrettyPrintFunction config outputUnicode) else Nothing , formatConfigPrintTimes = configTimes config , formatConfigHtmlOutput = configHtmlOutput config , formatConfigPrintCpuTime = configPrintCpuTime config@@ -241,31 +349,57 @@ , evalConfigConcurrentJobs = concurrentJobs , evalConfigFailFast = configFailFast config }- runFormatter evalConfig filteredSpec+ runFormatter evalConfig spec - let failures = filter resultItemIsFailure results+ let+ failures :: [Path]+ failures = map resultItemPath $ filter resultItemIsFailure $ specResultItems results - dumpFailureReport config seed qcArgs (map fst failures)+ dumpFailureReport config seed qcArgs failures - return Summary {- summaryExamples = length results- , summaryFailures = length failures- }+ return results -specToEvalForest :: Config -> Spec -> IO [EvalTree]-specToEvalForest config spec = do- let+toSummary :: SpecResult -> Summary+toSummary result = Summary {+ summaryExamples = length items+, summaryFailures = length failures+} where+ items = specResultItems result+ failures = filter resultItemIsFailure items++specToEvalForest :: Config -> [SpecTree ()] -> [EvalTree]+specToEvalForest config =+ failFocusedItems config+ >>> failPendingItems config+ >>> focusSpec config+ >>> toEvalItemForest params+ >>> applyDryRun config+ >>> applyFilterPredicates config+ >>> randomize+ >>> pruneForest+ where seed = (fromJust . configQuickCheckSeed) config- focusedSpec = focusSpec config (failFocusedItems config spec) params = Params (configQuickCheckArgs config) (configSmallCheckDepth config) randomize | configRandomize config = randomizeForest seed | otherwise = id- randomize . pruneForest . applyFilterPredicates config . applyDryRun config . toEvalForest params <$> runSpecM focusedSpec -toEvalForest :: Params -> [SpecTree ()] -> [EvalTree]-toEvalForest params = bimapForest withUnit toEvalItem . filterForest itemIsFocused+pruneForest :: [Tree c a] -> [Eval.Tree c a]+pruneForest = mapMaybe pruneTree++pruneTree :: Tree c a -> Maybe (Eval.Tree c a)+pruneTree node = case node of+ Node group xs -> Eval.Node group <$> prune xs+ NodeWithCleanup loc action xs -> Eval.NodeWithCleanup loc action <$> prune xs+ Leaf item -> Just (Eval.Leaf item) where+ prune = nonEmpty . pruneForest++type EvalItemTree = Tree (IO ()) EvalItem++toEvalItemForest :: Params -> [SpecTree ()] -> [EvalItemTree]+toEvalItemForest params = bimapForest id toEvalItem . filterForest itemIsFocused+ where toEvalItem :: Item () -> EvalItem toEvalItem (Item requirement loc isParallelizable _isFocused e) = EvalItem requirement loc (fromMaybe False isParallelizable) (e params withUnit) @@ -290,33 +424,45 @@ | useColor = E.bracket_ (hHideCursor h) (hShowCursor h) | otherwise = id -colorOutputSupported :: ColorMode -> IO Bool -> IO Bool-colorOutputSupported mode isTerminalDevice = case mode of- ColorAuto -> (&&) <$> (not <$> noColor) <*> isTerminalDevice- ColorNever -> return False- ColorAlways -> return True+colorOutputSupported :: ColorMode -> IO Bool -> IO (Bool, Bool)+colorOutputSupported mode isTerminalDevice = do+ github <- githubActions+ buildkite <- lookupEnv "BUILDKITE" <&> (== Just "true")+ useColor <- case mode of+ ColorAuto -> (github ||) <$> colorTerminal+ ColorNever -> return False+ ColorAlways -> return True+ let reportProgress = not github && not buildkite && useColor+ return (reportProgress, useColor)+ where+ githubActions :: IO Bool+ githubActions = lookupEnv "GITHUB_ACTIONS" <&> (== Just "true") + colorTerminal :: IO Bool+ colorTerminal = (&&) <$> (not <$> noColor) <*> isTerminalDevice++ noColor :: IO Bool+ noColor = lookupEnv "NO_COLOR" <&> (/= Nothing)+ 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 =- configRerunAllOnSuccess config- && configRerun config- && isSuccess summary- && (not . null) (failureReportPaths oldFailureReport)+rerunAll :: Config -> Maybe FailureReport -> SpecResult -> Bool+rerunAll config mOldFailureReport result = case mOldFailureReport of+ Nothing -> False+ Just oldFailureReport ->+ configRerunAllOnSuccess config+ && configRerun config+ && specResultSuccess result+ && (not . null) (failureReportPaths oldFailureReport) -- | Summary of a test run. data Summary = Summary {- summaryExamples :: Int-, summaryFailures :: Int+ summaryExamples :: !Int+, summaryFailures :: !Int } deriving (Eq, Show) instance Monoid Summary where@@ -337,5 +483,5 @@ ref <- newSTRef (mkStdGen $ fromIntegral seed) shuffleForest ref t -countSpecItems :: [EvalTree] -> Int+countSpecItems :: [Eval.Tree c a] -> Int countSpecItems = getSum . foldMap (foldMap . const $ Sum 1)
hspec-core/src/Test/Hspec/Core/Runner/Eval.hs view
@@ -1,5 +1,7 @@ {-# LANGUAGE CPP #-} {-# LANGUAGE DeriveFunctor #-}+{-# LANGUAGE DeriveFoldable #-}+{-# LANGUAGE DeriveTraversable #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE RecordWildCards #-} {-# LANGUAGE ConstraintKinds #-}@@ -13,10 +15,11 @@ module Test.Hspec.Core.Runner.Eval ( EvalConfig(..) , EvalTree+, Tree(..) , EvalItem(..) , runFormatter-, resultItemIsFailure #ifdef TEST+, mergeResults , runSequentially #endif ) where@@ -36,14 +39,23 @@ 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.Spec (Progress, FailureReason(..), Result(..), ResultStatus(..), ProgressCallback) import Test.Hspec.Core.Timer 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-import Test.Hspec.Core.Example (safeEvaluate)+import Test.Hspec.Core.Example (safeEvaluateResultStatus) +import qualified NonEmpty+import NonEmpty (NonEmpty(..))++data Tree c a =+ Node String (NonEmpty (Tree c a))+ | NodeWithCleanup (Maybe (String, Location)) c (NonEmpty (Tree c a))+ | Leaf a+ deriving (Eq, Show, Functor, Foldable, Traversable)+ -- for compatibility with GHC < 7.10.1 type Monad m = (Functor m, Applicative m, M.Monad m) type MonadIO m = (Monad m, M.MonadIO m)@@ -124,7 +136,7 @@ withTimer 0.05 $ \ timer -> do format Format.Started- runReaderT (run $ map (fmap (fmap (. reportProgress timer) . snd)) runningSpecs) (Env config ref) `E.finally` do+ runReaderT (run . applyCleanup $ map (fmap (fmap (. reportProgress timer) . snd)) runningSpecs) (Env config ref) `E.finally` do results <- reverse <$> readIORef ref format (Format.Done results) @@ -146,14 +158,60 @@ data Item a = Item { _itemDescription :: String , _itemLocation :: Maybe Location-, _itemAction :: a+, itemAction :: a } deriving Functor type Job m p a = (p -> m ()) -> m a -type RunningItem m = Item (Path -> m (Seconds, Result))-type RunningTree m = Tree (IO ()) (RunningItem m)+type RunningItem = Item (Path -> IO (Seconds, Result))+type RunningTree c = Tree c RunningItem +applyCleanup :: [RunningTree (IO ())] -> [RunningTree ()]+applyCleanup = map go+ where+ go t = case t of+ Leaf a -> Leaf a+ Node label xs -> Node label (go <$> xs)+ NodeWithCleanup loc cleanup xs -> NodeWithCleanup loc () (addCleanupToLastLeaf loc cleanup $ go <$> xs)++addCleanupToLastLeaf :: Maybe (String, Location) -> IO () -> NonEmpty (RunningTree ()) -> NonEmpty (RunningTree ())+addCleanupToLastLeaf loc cleanup = go+ where+ go = NonEmpty.reverse . mapHead goNode . NonEmpty.reverse++ goNode node = case node of+ Leaf item -> Leaf (addCleanupToItem loc cleanup item)+ NodeWithCleanup loc_ () zs -> NodeWithCleanup loc_ () (go zs)+ Node description zs -> Node description (go zs)++mapHead :: (a -> a) -> NonEmpty a -> NonEmpty a+mapHead f xs = case xs of+ y :| ys -> f y :| ys++addCleanupToItem :: Maybe (String, Location) -> IO () -> RunningItem -> RunningItem+addCleanupToItem loc cleanup item = item {+ itemAction = \ path -> do+ (t1, r1) <- itemAction item path+ (t2, r2) <- measure $ safeEvaluateResultStatus (cleanup >> return Success)+ let t = t1 + t2+ return (t, mergeResults loc r1 r2)+}++mergeResults :: Maybe (String, Location) -> Result -> ResultStatus -> Result+mergeResults mCallSite (Result info r1) r2 = Result info $ case (r1, r2) of+ (_, Success) -> r1+ (Failure{}, _) -> r1+ (Pending{}, Pending{}) -> r1+ (Success, Pending{}) -> r2+ (_, Failure mLoc err) -> Failure (mLoc <|> hookLoc) $ case err of+ Error message e -> Error (message <|> hookFailed) e+ _ -> err+ where+ hookLoc = snd <$> mCallSite+ hookFailed = case mCallSite of+ Just (name, _) -> Just $ "in " ++ name ++ "-hook:"+ Nothing -> Nothing+ type RunningItem_ m = (Async (), Item (Job m Progress (Seconds, Result))) type RunningTree_ m = Tree (IO ()) (RunningItem_ m) @@ -208,12 +266,12 @@ replaceMVar :: MVar a -> a -> IO () replaceMVar mvar p = tryTakeMVar mvar >> putMVar mvar p -run :: [RunningTree IO] -> EvalM ()+run :: [RunningTree ()] -> EvalM () run specs = do- fastFail <- asks (evalConfigFailFast . envConfig)- sequenceActions fastFail (concatMap foldSpec specs)+ failFast <- asks (evalConfigFailFast . envConfig)+ sequenceActions failFast (concatMap foldSpec specs) where- foldSpec :: RunningTree IO -> [EvalM ()]+ foldSpec :: RunningTree () -> [EvalM ()] foldSpec = foldTree FoldTree { onGroupStarted = groupStarted , onGroupDone = groupDone@@ -221,16 +279,10 @@ , onLeafe = evalItem } - 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 ()- _ -> reportItem path loc (return r)- where- path = (groups, "afterAll-hook")+ runCleanup :: Maybe (String, Location) -> [String] -> () -> EvalM ()+ runCleanup _loc _groups = return - evalItem :: [String] -> RunningItem IO -> EvalM ()+ evalItem :: [String] -> RunningItem -> EvalM () evalItem groups (Item requirement loc action) = do reportItem path loc $ lift (action path) where@@ -240,7 +292,7 @@ data FoldTree c a r = FoldTree { onGroupStarted :: Path -> r , onGroupDone :: Path -> r-, onCleanup :: Maybe Location -> [String] -> c -> r+, onCleanup :: Maybe (String, Location) -> [String] -> c -> r , onLeafe :: [String] -> a -> r } @@ -260,20 +312,21 @@ go rGroups (Leaf a) = [onLeafe (reverse rGroups) a] sequenceActions :: Bool -> [EvalM ()] -> EvalM ()-sequenceActions fastFail = go+sequenceActions failFast = go where go :: [EvalM ()] -> EvalM () go [] = return () go (action : actions) = do action- hasFailures <- any resultItemIsFailure <$> getResults- let stopNow = fastFail && hasFailures+ stopNow <- case failFast of+ False -> return False+ True -> any itemIsFailure <$> getResults unless stopNow (go actions) -resultItemIsFailure :: (Path, Format.Item) -> Bool-resultItemIsFailure = isFailure . Format.itemResult . snd- where- isFailure r = case r of- Format.Success{} -> False- Format.Pending{} -> False- Format.Failure{} -> True+ itemIsFailure :: (Path, Format.Item) -> Bool+ itemIsFailure = isFailure . Format.itemResult . snd+ where+ isFailure r = case r of+ Format.Success{} -> False+ Format.Pending{} -> False+ Format.Failure{} -> True
+ hspec-core/src/Test/Hspec/Core/Runner/Result.hs view
@@ -0,0 +1,58 @@+module Test.Hspec.Core.Runner.Result (+-- RE-EXPORTED from Test.Hspec.Core.Runner+ SpecResult(SpecResult)+, specResultItems+, specResultSuccess++, ResultItem(ResultItem)+, resultItemPath+, resultItemStatus+, resultItemIsFailure++, ResultItemStatus(..)+-- END RE-EXPORTED from Test.Hspec.Core.Runner++, toSpecResult+) where++import Prelude ()+import Test.Hspec.Core.Compat++import Test.Hspec.Core.Util+import qualified Test.Hspec.Core.Format as Format++data SpecResult = SpecResult {+ specResultItems :: [ResultItem]+, specResultSuccess :: !Bool+} deriving (Eq, Show)++data ResultItem = ResultItem {+ resultItemPath :: Path+, resultItemStatus :: ResultItemStatus+} deriving (Eq, Show)++resultItemIsFailure :: ResultItem -> Bool+resultItemIsFailure item = case resultItemStatus item of+ ResultItemSuccess -> False+ ResultItemPending -> False+ ResultItemFailure -> True++data ResultItemStatus =+ ResultItemSuccess+ | ResultItemPending+ | ResultItemFailure+ deriving (Eq, Show)++toSpecResult :: Bool -> [(Path, Format.Item)] -> SpecResult+toSpecResult failOnEmpty results = SpecResult items success+ where+ items = map toResultItem results+ success = not (failOnEmpty && null results) && all (not . resultItemIsFailure) items++toResultItem :: (Path, Format.Item) -> ResultItem+toResultItem (path, item) = ResultItem path status+ where+ status = case Format.itemResult item of+ Format.Success{} -> ResultItemSuccess+ Format.Pending{} -> ResultItemPending+ Format.Failure{} -> ResultItemFailure
hspec-core/src/Test/Hspec/Core/Spec.hs view
@@ -29,13 +29,50 @@ , sequential -- * The @SpecM@ monad-, module Test.Hspec.Core.Spec.Monad+, Test.Hspec.Core.Spec.Monad.Spec+, Test.Hspec.Core.Spec.Monad.SpecWith+, Test.Hspec.Core.Spec.Monad.SpecM(..)+, Test.Hspec.Core.Spec.Monad.runSpecM+, Test.Hspec.Core.Spec.Monad.fromSpecList+, Test.Hspec.Core.Spec.Monad.runIO+, Test.Hspec.Core.Spec.Monad.mapSpecForest+, Test.Hspec.Core.Spec.Monad.mapSpecItem+, Test.Hspec.Core.Spec.Monad.mapSpecItem_+, Test.Hspec.Core.Spec.Monad.modifyParams+, Test.Hspec.Core.Spec.Monad.modifyConfig+, getSpecDescriptionPath -- * A type class for examples-, module Test.Hspec.Core.Example+, Test.Hspec.Core.Example.Example (..)+, Test.Hspec.Core.Example.Params (..)+, Test.Hspec.Core.Example.defaultParams+, Test.Hspec.Core.Example.ActionWith+, Test.Hspec.Core.Example.Progress+, Test.Hspec.Core.Example.ProgressCallback+, Test.Hspec.Core.Example.Result(..)+, Test.Hspec.Core.Example.ResultStatus (..)+, Test.Hspec.Core.Example.Location (..)+, Test.Hspec.Core.Example.FailureReason (..)+, Test.Hspec.Core.Example.safeEvaluate+, Test.Hspec.Core.Example.safeEvaluateExample -- * Internal representation of a spec tree-, module Test.Hspec.Core.Tree+, Test.Hspec.Core.Tree.SpecTree+, Test.Hspec.Core.Tree.Tree (..)+, Test.Hspec.Core.Tree.Item (..)+, Test.Hspec.Core.Tree.specGroup+, Test.Hspec.Core.Tree.specItem+, Test.Hspec.Core.Tree.bimapTree+, Test.Hspec.Core.Tree.bimapForest+, Test.Hspec.Core.Tree.filterTree+, Test.Hspec.Core.Tree.filterForest+, Test.Hspec.Core.Tree.filterTreeWithLabels+, Test.Hspec.Core.Tree.filterForestWithLabels+, Test.Hspec.Core.Tree.pruneTree -- unused+, Test.Hspec.Core.Tree.pruneForest -- unused+, Test.Hspec.Core.Tree.location++, focusForest ) where import Prelude ()@@ -43,6 +80,8 @@ import qualified Control.Exception as E import Data.CallStack+import Control.Monad.Trans.Class (lift)+import Control.Monad.Trans.Reader (asks) import Test.Hspec.Expectations (Expectation) @@ -53,7 +92,9 @@ -- | The @describe@ function combines a list of specs into a larger spec. describe :: HasCallStack => String -> SpecWith a -> SpecWith a-describe label spec = runIO (runSpecM spec) >>= fromSpecList . return . specGroup label+describe label = withEnv pushLabel . mapSpecForest (return . specGroup label)+ where+ pushLabel (Env labels) = Env $ label : labels -- | @context@ is an alias for `describe`. context :: HasCallStack => String -> SpecWith a -> SpecWith a@@ -103,14 +144,13 @@ -- -- Applying `focus` to a spec with focused spec items has no effect. focus :: SpecWith a -> SpecWith a-focus spec = do- xs <- runIO (runSpecM spec)- let- ys- | any (any itemIsFocused) xs = xs- | otherwise = bimapForest id (\ item -> item {itemIsFocused = True}) xs- fromSpecList ys+focus = mapSpecForest focusForest +focusForest :: [SpecTree a] -> [SpecTree a]+focusForest xs+ | any (any itemIsFocused) xs = xs+ | otherwise = bimapForest id (\ item -> item {itemIsFocused = True}) xs+ -- | @fit@ is an alias for @fmap focus . it@ fit :: (HasCallStack, Example a) => String -> a -> SpecWith (Arg a) fit = fmap focus . it@@ -158,3 +198,19 @@ -- argument that can be used to specify the reason for why the spec item is pending. pendingWith :: HasCallStack => String -> Expectation pendingWith = E.throwIO . Pending location . Just++-- | Get the path of `describe` labels, from the root all the way in to the+-- call-site of this function.+--+-- ==== __Example__+-- >>> :{+-- runSpecM $ do+-- describe "foo" $ do+-- describe "bar" $ do+-- getSpecDescriptionPath >>= runIO . print+-- :}+-- ["foo","bar"]+--+-- @since 2.10.0+getSpecDescriptionPath :: SpecM a [String]+getSpecDescriptionPath = SpecM $ lift $ reverse <$> asks envSpecDescriptionPath
hspec-core/src/Test/Hspec/Core/Spec/Monad.hs view
@@ -1,44 +1,61 @@ {-# LANGUAGE GeneralizedNewtypeDeriving #-}---- NOTE: re-exported from Test.Hspec.Core.Spec module Test.Hspec.Core.Spec.Monad (+-- RE-EXPORTED from Test.Hspec.Core.Spec Spec , SpecWith-, SpecM (..)+, SpecM (SpecM) , runSpecM , fromSpecList , runIO +, mapSpecForest , mapSpecItem , mapSpecItem_ , modifyParams++, modifyConfig+-- END RE-EXPORTED from Test.Hspec.Core.Spec++, Env(..)+, withEnv ) where import Prelude () import Test.Hspec.Core.Compat -import Control.Arrow-import Control.Monad.Trans.Writer import Control.Monad.IO.Class (liftIO)+import Control.Monad.Trans.Reader+import Control.Monad.Trans.Writer import Test.Hspec.Core.Example import Test.Hspec.Core.Tree +import Test.Hspec.Core.Config.Definition (Config)+ type Spec = SpecWith () type SpecWith a = SpecM a () +-- |+-- @since 2.10.0+modifyConfig :: (Config -> Config) -> SpecWith a+modifyConfig f = SpecM $ tell (Endo f, mempty)+ -- | A writer monad for `SpecTree` forests-newtype SpecM a r = SpecM (WriterT [SpecTree a] IO r)+newtype SpecM a r = SpecM { unSpecM :: WriterT (Endo Config, [SpecTree a]) (ReaderT Env IO) r } deriving (Functor, Applicative, Monad) -- | Convert a `Spec` to a forest of `SpecTree`s.-runSpecM :: SpecWith a -> IO [SpecTree a]-runSpecM (SpecM specs) = execWriterT specs+runSpecM :: SpecWith a -> IO (Endo Config, [SpecTree a])+runSpecM = flip runReaderT (Env []) . execWriterT . unSpecM -- | Create a `Spec` from a forest of `SpecTree`s.+fromSpecForest :: (Endo Config, [SpecTree a]) -> SpecWith a+fromSpecForest = SpecM . tell++-- | Create a `Spec` from a forest of `SpecTree`s. fromSpecList :: [SpecTree a] -> SpecWith a-fromSpecList = SpecM . tell+fromSpecList = fromSpecForest . (,) mempty -- | Run an IO action while constructing the spec tree. --@@ -52,13 +69,20 @@ runIO = SpecM . liftIO mapSpecForest :: ([SpecTree a] -> [SpecTree b]) -> SpecM a r -> SpecM b r-mapSpecForest f (SpecM specs) = SpecM (mapWriterT (fmap (second f)) specs)+mapSpecForest f (SpecM specs) = SpecM (mapWriterT (fmap (fmap (second f))) specs) mapSpecItem :: (ActionWith a -> ActionWith b) -> (Item a -> Item b) -> SpecWith a -> SpecWith b-mapSpecItem g f = mapSpecForest (bimapForest g f)+mapSpecItem _ = mapSpecItem_ -mapSpecItem_ :: (Item a -> Item a) -> SpecWith a -> SpecWith a-mapSpecItem_ = mapSpecItem id+mapSpecItem_ :: (Item a -> Item b) -> SpecWith a -> SpecWith b+mapSpecItem_ = mapSpecForest . bimapForest id modifyParams :: (Params -> Params) -> SpecWith a -> SpecWith a modifyParams f = mapSpecItem_ $ \item -> item {itemExample = \p -> (itemExample item) (f p)}++newtype Env = Env {+ envSpecDescriptionPath :: [String]+}++withEnv :: (Env -> Env) -> SpecM a r -> SpecM a r+withEnv f = SpecM . WriterT . local f . runWriterT . unSpecM
hspec-core/src/Test/Hspec/Core/Tree.hs view
@@ -4,8 +4,8 @@ {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE ConstraintKinds #-} --- NOTE: re-exported from Test.Hspec.Core.Spec module Test.Hspec.Core.Tree (+-- RE-EXPORTED from Test.Hspec.Core.Spec SpecTree , Tree (..) , Item (..)@@ -17,15 +17,18 @@ , filterForest , filterTreeWithLabels , filterForestWithLabels-, pruneTree-, pruneForest+, pruneTree -- unused+, pruneForest -- unused , location+-- END RE-EXPORTED from Test.Hspec.Core.Spec+, callSite ) where import Prelude () import Test.Hspec.Core.Compat -import Data.CallStack+import Data.CallStack (HasCallStack, SrcLoc(..))+import qualified Data.CallStack as CallStack import Data.Maybe import Test.Hspec.Core.Example@@ -33,13 +36,13 @@ -- | Internal tree data structure data Tree c a = Node String [Tree c a]- | NodeWithCleanup (Maybe Location) c [Tree c a]+ | NodeWithCleanup (Maybe (String, Location)) c [Tree c a] | Leaf a- deriving (Show, Eq, Functor, Foldable, Traversable)+ deriving (Eq, Show, Functor, Foldable, Traversable) --- | A tree is used to represent a spec internally. The tree is parametrize+-- | A tree is used to represent a spec internally. The tree is parameterized -- over the type of cleanup actions and the type of the actual spec items.-type SpecTree a = Tree (ActionWith a) (Item a)+type SpecTree a = Tree (IO ()) (Item a) bimapForest :: (a -> b) -> (c -> d) -> [Tree a c] -> [Tree b d] bimapForest g f = map (bimapTree g f)@@ -132,11 +135,15 @@ | otherwise = s location :: HasCallStack => Maybe Location-location = case reverse callStack of- (_, loc) : _ -> Just (Location (srcLocFile loc) (srcLocStartLine loc) (srcLocStartCol loc))- _ -> Nothing+location = snd <$> callSite +callSite :: HasCallStack => Maybe (String, Location)+callSite = fmap toLocation <$> CallStack.callSite+ defaultDescription :: HasCallStack => Maybe String-defaultDescription = case reverse callStack of- (_, loc) : _ -> Just (srcLocModule loc ++ "[" ++ show (srcLocStartLine loc) ++ ":" ++ show (srcLocStartCol loc) ++ "]")- _ -> Nothing+defaultDescription = case CallStack.callSite of+ Just (_, loc) -> Just (srcLocModule loc ++ "[" ++ show (srcLocStartLine loc) ++ ":" ++ show (srcLocStartCol loc) ++ "]")+ Nothing -> Nothing++toLocation :: SrcLoc -> Location+toLocation loc = Location (srcLocFile loc) (srcLocStartLine loc) (srcLocStartCol loc)
hspec-core/src/Test/Hspec/Core/Util.hs view
@@ -50,10 +50,12 @@ -- ensure that lines are not longer than given `n`, insert line breaks at word -- boundaries lineBreaksAt :: Int -> String -> [String]-lineBreaksAt n input = case words input of- [] -> []- x:xs -> go (x, xs)+lineBreaksAt n = concatMap f . lines where+ f input = case words input of+ [] -> []+ x:xs -> go (x, xs)+ go :: (String, [String]) -> [String] go c = case c of (s, []) -> [s]@@ -102,7 +104,7 @@ -- This is different from `show`. The type of the exception is included, e.g.: -- -- >>> formatException (toException DivideByZero)--- "ArithException (divide by zero)"+-- "ArithException\ndivide by zero" -- -- For `IOException`s the `IOErrorType` is included, as well. formatException :: SomeException -> String
hspec-discover/src/Test/Hspec/Discover/Config.hs view
@@ -35,9 +35,9 @@ parseConfig :: String -> [String] -> Either String Config parseConfig prog args = case getOpt Permute options args of (opts, [], []) -> let- c = (foldl (flip id) defaultConfig opts)+ c = foldl (flip id) defaultConfig opts in- if (configNoMain c && isJust (configFormatter c))+ if configNoMain c && isJust (configFormatter c) then formatError "option `--formatter=<fmt>' does not make sense with `--no-main'\n" else
hspec-discover/src/Test/Hspec/Discover/Run.hs view
@@ -1,4 +1,4 @@-{-# LANGUAGE TypeSynonymInstances, FlexibleInstances, OverloadedStrings #-}+{-# LANGUAGE FlexibleInstances, OverloadedStrings #-} {-# OPTIONS_GHC -fno-warn-orphans #-} -- | A preprocessor that finds and combines specs. --@@ -61,7 +61,7 @@ mkSpecModule src conf nodes = ( "{-# LINE 1 " . shows src . " #-}\n" . showString "{-# LANGUAGE NoImplicitPrelude #-}\n"- . showString "{-# OPTIONS_GHC -fno-warn-warnings-deprecations #-}\n"+ . showString "{-# OPTIONS_GHC -w -Wall -fno-warn-warnings-deprecations #-}\n" . showString ("module " ++ moduleName src conf ++" where\n") . importList nodes . showString "import Test.Hspec.Discover\n"@@ -121,9 +121,7 @@ sequenceS = foldr (.) "" . intersperse " >> " formatSpecs :: Maybe [Spec] -> ShowS-formatSpecs specs = case specs of- Nothing -> "return ()"- Just xs -> fromForest xs+formatSpecs = maybe "return ()" fromForest where fromForest :: [Spec] -> ShowS fromForest = sequenceS . map fromTree
hspec-discover/src/Test/Hspec/Discover/Sort.hs view
@@ -15,7 +15,7 @@ sortNaturallyBy :: (a -> (String, Int)) -> [a] -> [a] sortNaturallyBy f = sortBy (comparing ((\ (k, t) -> (naturalSortKey k, t)) . f)) -data NaturalSortKey = NaturalSortKey [Chunk]+newtype NaturalSortKey = NaturalSortKey [Chunk] deriving (Eq, Ord) data Chunk = Numeric Integer Int | Textual [(Char, Char)]
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.6.+-- This file has been generated from package.yaml by hpack version 0.35.0. -- -- see: https://github.com/sol/hpack name: hspec-meta-version: 2.9.3+version: 2.10.5 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.@@ -14,7 +14,7 @@ homepage: http://hspec.github.io/ bug-reports: https://github.com/hspec/hspec/issues maintainer: Simon Hengel <sol@typeful.net>-copyright: (c) 2011-2021 Simon Hengel,+copyright: (c) 2011-2022 Simon Hengel, (c) 2011-2012 Trystan Spangler, (c) 2011 Greg Weber license: MIT@@ -41,6 +41,7 @@ GetOpt.Declarative.Interpret GetOpt.Declarative.Types GetOpt.Declarative.Util+ NonEmpty Test.Hspec.Core.Clock Test.Hspec.Core.Compat Test.Hspec.Core.Config@@ -67,6 +68,7 @@ Test.Hspec.Core.Runner Test.Hspec.Core.Runner.Eval Test.Hspec.Core.Runner.PrintSlowSpecItems+ Test.Hspec.Core.Runner.Result Test.Hspec.Core.Shuffle Test.Hspec.Core.Spec Test.Hspec.Core.Spec.Monad@@ -106,6 +108,7 @@ , setenv , time , transformers >=0.2.2.0+ default-language: Haskell2010 if impl(ghc >= 8.2.1) build-depends: ghc@@ -118,7 +121,6 @@ Control.Concurrent.STM.TMVar hs-source-dirs: hspec-core/vendor/stm-2.5.0.1/- default-language: Haskell2010 executable hspec-meta-discover main-is: hspec-discover.hs@@ -146,8 +148,8 @@ , setenv , time , transformers >=0.2.2.0+ default-language: Haskell2010 if impl(ghc >= 8.2.1) build-depends: ghc , ghc-boot-th- default-language: Haskell2010
src/Test/Hspec/Discover.hs view
@@ -13,7 +13,7 @@ import Test.Hspec.Core.Spec import Test.Hspec.Core.Runner-import Test.Hspec.Formatters+import Test.Hspec.Core.Formatters.V1 class IsFormatter a where toFormatter :: a -> IO Formatter
src/Test/Hspec/Formatters.hs view
@@ -1,2 +1,7 @@-module Test.Hspec.Formatters (module Test.Hspec.Core.Formatters) where-import Test.Hspec.Core.Formatters+-- |+-- Deprecated\: Use "Test.Hspec.Core.Formatters.V1" instead.+module Test.Hspec.Formatters+-- {-# DEPRECATED "Use \"Test.Hspec.Core.Formatters.V1\" instead." #-}+(module Test.Hspec.Core.Formatters.V1)+where+import Test.Hspec.Core.Formatters.V1
version.yaml view
@@ -1,1 +1,1 @@-&version 2.9.3+&version 2.10.5