hspec-core 2.10.8 → 2.10.9
raw patch · 30 files changed
+422/−227 lines, 30 filesPVP: major bump suggested
API removals or changes: PVP suggests a major version bump
API changes (from Hackage documentation)
+ Test.Hspec.Core.Formatters.V2: Location :: FilePath -> Int -> Int -> Location
+ Test.Hspec.Core.Formatters.V2: [locationColumn] :: Location -> Int
+ Test.Hspec.Core.Formatters.V2: [locationFile] :: Location -> FilePath
+ Test.Hspec.Core.Formatters.V2: [locationLine] :: Location -> Int
+ Test.Hspec.Core.Formatters.V2: data Location
+ Test.Hspec.Core.Formatters.V2: type Path = ([String], String)
+ Test.Hspec.Core.Formatters.V2: type Progress = (Int, Int)
+ Test.Hspec.Core.Hooks: type ActionWith a = a -> IO ()
+ Test.Hspec.Core.Hooks: type Spec = SpecWith ()
+ Test.Hspec.Core.Hooks: type SpecWith a = SpecM a ()
+ Test.Hspec.Core.Runner: type Spec = SpecWith ()
+ Test.Hspec.Core.Runner: type SpecWith a = SpecM a ()
+ Test.Hspec.Core.Spec: type Expectation = Assertion
+ Test.Hspec.Core.Spec: type HasCallStack = ?callStack :: CallStack
- Test.Hspec.Core.Spec: specItem :: (HasCallStack, Example a) => String -> a -> SpecTree (Arg a)
+ Test.Hspec.Core.Spec: specItem :: (HasCallStack, Example e) => String -> e -> SpecTree (Arg e)
Files
- LICENSE +1/−1
- help.txt +1/−1
- hspec-core.cabal +7/−4
- src/GetOpt/Declarative/Interpret.hs +0/−1
- src/Test/Hspec/Core/Compat.hs +11/−1
- src/Test/Hspec/Core/Config.hs +11/−2
- src/Test/Hspec/Core/Config/Definition.hs +6/−13
- src/Test/Hspec/Core/Example.hs +1/−0
- src/Test/Hspec/Core/Example/Location.hs +0/−1
- src/Test/Hspec/Core/Formatters/Internal.hs +4/−4
- src/Test/Hspec/Core/Formatters/Pretty.hs +13/−10
- src/Test/Hspec/Core/Formatters/V1.hs +0/−1
- src/Test/Hspec/Core/Formatters/V2.hs +4/−2
- src/Test/Hspec/Core/Hooks.hs +11/−5
- src/Test/Hspec/Core/QuickCheckUtil.hs +0/−1
- src/Test/Hspec/Core/Runner.hs +36/−18
- src/Test/Hspec/Core/Runner/Eval.hs +68/−109
- src/Test/Hspec/Core/Runner/JobQueue.hs +121/−0
- src/Test/Hspec/Core/Runner/Result.hs +3/−3
- src/Test/Hspec/Core/Spec.hs +4/−0
- src/Test/Hspec/Core/Tree.hs +8/−3
- test/Helper.hs +1/−0
- test/Test/Hspec/Core/Formatters/PrettySpec.hs +40/−10
- test/Test/Hspec/Core/Formatters/V2Spec.hs +2/−2
- test/Test/Hspec/Core/Runner/EvalSpec.hs +0/−10
- test/Test/Hspec/Core/Runner/JobQueueSpec.hs +44/−0
- test/Test/Hspec/Core/Runner/ResultSpec.hs +3/−12
- test/Test/Hspec/Core/RunnerSpec.hs +12/−5
- test/Test/Hspec/Core/SpecSpec.hs +9/−7
- version.yaml +1/−1
LICENSE view
@@ -1,4 +1,4 @@-Copyright (c) 2011-2022 Simon Hengel <sol@typeful.net>+Copyright (c) 2011-2023 Simon Hengel <sol@typeful.net> Copyright (c) 2011-2012 Trystan Spangler <trystan.s@comcast.net> Copyright (c) 2011-2011 Greg Weber <greg@gregweber.info>
help.txt view
@@ -11,7 +11,7 @@ anything --[no-]focused-only do not run anything, unless there are focused spec items- --[no-]fail-on=ITEMS empty: fail if no spec items have been run+ --[no-]fail-on=ITEMS empty: fail if all spec items have been filtered focused: fail on focused spec items pending: fail on pending spec items --[no-]strict same as --fail-on=focused,pending
hspec-core.cabal view
@@ -1,14 +1,14 @@ cabal-version: 1.12 --- This file has been generated from package.yaml by hpack version 0.35.0.+-- This file has been generated from package.yaml by hpack version 0.35.1. -- -- see: https://github.com/sol/hpack name: hspec-core-version: 2.10.8+version: 2.10.9 license: MIT license-file: LICENSE-copyright: (c) 2011-2022 Simon Hengel,+copyright: (c) 2011-2023 Simon Hengel, (c) 2011-2012 Trystan Spangler, (c) 2011 Greg Weber maintainer: Simon Hengel <sol@typeful.net>@@ -19,7 +19,7 @@ category: Testing stability: experimental bug-reports: https://github.com/hspec/hspec/issues-homepage: http://hspec.github.io/+homepage: https://hspec.github.io/ synopsis: A Testing Framework for Haskell description: This package exposes internal types and functions that can be used to extend Hspec's functionality. @@ -87,6 +87,7 @@ Test.Hspec.Core.Formatters.V1.Monad Test.Hspec.Core.QuickCheckUtil Test.Hspec.Core.Runner.Eval+ Test.Hspec.Core.Runner.JobQueue Test.Hspec.Core.Runner.PrintSlowSpecItems Test.Hspec.Core.Runner.Result Test.Hspec.Core.Shuffle@@ -172,6 +173,7 @@ Test.Hspec.Core.QuickCheckUtil Test.Hspec.Core.Runner Test.Hspec.Core.Runner.Eval+ Test.Hspec.Core.Runner.JobQueue Test.Hspec.Core.Runner.PrintSlowSpecItems Test.Hspec.Core.Runner.Result Test.Hspec.Core.Shuffle@@ -206,6 +208,7 @@ Test.Hspec.Core.HooksSpec Test.Hspec.Core.QuickCheckUtilSpec Test.Hspec.Core.Runner.EvalSpec+ Test.Hspec.Core.Runner.JobQueueSpec Test.Hspec.Core.Runner.PrintSlowSpecItemsSpec Test.Hspec.Core.Runner.ResultSpec Test.Hspec.Core.RunnerSpec
src/GetOpt/Declarative/Interpret.hs view
@@ -7,7 +7,6 @@ import Prelude () import Test.Hspec.Core.Compat-import Data.Maybe import System.Console.GetOpt (OptDescr, ArgOrder(..), getOpt) import qualified System.Console.GetOpt as GetOpt
src/Test/Hspec/Core/Compat.hs view
@@ -16,9 +16,14 @@ , sequence , sequence_ )+import Data.Maybe as Imports import Data.Foldable as Imports import Data.CallStack as Imports (HasCallStack) +import System.IO+import System.Exit+import System.Environment+ #if MIN_VERSION_base(4,11,0) import Data.Functor as Imports #endif@@ -73,7 +78,6 @@ import System.Environment as Imports (lookupEnv) #else import Text.Read-import System.Environment import qualified Text.ParserCombinators.ReadP as P #endif @@ -182,3 +186,9 @@ pass :: Monad m => m () pass = return () #endif++die :: String -> IO a+die err = do+ name <- getProgName+ hPutStrLn stderr $ name <> ": " <> err+ exitFailure
src/Test/Hspec/Core/Config.hs view
@@ -18,7 +18,6 @@ import Prelude () import Test.Hspec.Core.Compat -import Data.Maybe import System.IO import System.IO.Error import System.Exit@@ -29,10 +28,20 @@ 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.Config.Definition (Config(..), ColorMode(..), UnicodeMode(..), mkDefaultConfig, filterOr) import Test.Hspec.Core.FailureReport import Test.Hspec.Core.QuickCheckUtil (mkGen) import Test.Hspec.Core.Example (Params(..), defaultParams)+import qualified Test.Hspec.Core.Formatters.V2 as V2++defaultConfig :: Config+defaultConfig = mkDefaultConfig $ map (fmap V2.formatterToFormat) [+ ("checks", V2.checks)+ , ("specdoc", V2.specdoc)+ , ("progress", V2.progress)+ , ("failed-examples", V2.failed_examples)+ , ("silent", V2.silent)+ ] -- | Add a filter predicate to config. If there is already a filter predicate, -- then combine them with `||`.
src/Test/Hspec/Core/Config/Definition.hs view
@@ -4,7 +4,7 @@ , ColorMode(..) , UnicodeMode(..) , filterOr-, defaultConfig+, mkDefaultConfig , commandLineOnlyOptions , formatterOptions@@ -27,8 +27,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 qualified Test.Hspec.Core.Formatters.V1.Monad as V1 import Test.Hspec.Core.Util import GetOpt.Declarative@@ -89,8 +88,8 @@ , configConcurrentJobs :: Maybe Int } -defaultConfig :: Config-defaultConfig = Config {+mkDefaultConfig :: [(String, FormatConfig -> IO Format)] -> Config+mkDefaultConfig formatters = Config { configIgnoreConfigFile = False , configDryRun = False , configFocusedOnly = False@@ -120,13 +119,7 @@ , configPrettyPrint = True , configPrettyPrintFunction = pretty2 , configTimes = False-, configAvailableFormatters = map (fmap V2.formatterToFormat) [- ("checks", V2.checks)- , ("specdoc", V2.specdoc)- , ("progress", V2.progress)- , ("failed-examples", V2.failed_examples)- , ("silent", V2.silent)- ]+, configAvailableFormatters = formatters , configFormat = Nothing , configFormatter = Nothing , configHtmlOutput = False@@ -346,7 +339,7 @@ showFailOn item <> ": " <> help item where help item = case item of- FailOnEmpty -> "fail if no spec items have been run"+ FailOnEmpty -> "fail if all spec items have been filtered" FailOnFocused -> "fail on focused spec items" FailOnPending -> "fail on pending spec items"
src/Test/Hspec/Core/Example.hs view
@@ -21,6 +21,7 @@ , safeEvaluateExample -- END RE-EXPORTED from Test.Hspec.Core.Spec , safeEvaluateResultStatus+, exceptionToResultStatus , toLocation ) where
src/Test/Hspec/Core/Example/Location.hs view
@@ -17,7 +17,6 @@ import Test.Hspec.Core.Compat import Data.Char-import Data.Maybe import GHC.IO.Exception #ifdef mingw32_HOST_OS
src/Test/Hspec/Core/Formatters/Internal.hs view
@@ -198,7 +198,7 @@ runFormatM :: FormatConfig -> FormatM a -> IO a runFormatM config (FormatM action) = withLineBuffering $ do time <- getMonotonicTime- cpuTime <- if (formatConfigPrintCpuTime config) then Just <$> CPUTime.getCPUTime else pure Nothing+ cpuTime <- if formatConfigPrintCpuTime config then Just <$> CPUTime.getCPUTime else pure Nothing let progress = formatConfigReportProgress config && not (formatConfigHtmlOutput config)@@ -245,9 +245,9 @@ writeTransient :: String -> FormatM () writeTransient new = do reportProgress <- getConfig formatConfigReportProgress- when (reportProgress) $ do+ when reportProgress $ do h <- getHandle- write $ new+ write new liftIO $ IO.hFlush h write $ "\r" ++ replicate (length new) ' ' ++ "\r" @@ -347,7 +347,7 @@ getCPUTime = do t1 <- liftIO CPUTime.getCPUTime mt0 <- gets stateCpuStartTime- return $ toSeconds <$> ((-) <$> pure t1 <*> mt0)+ return $ toSeconds <$> ((t1 -) <$> mt0) where toSeconds x = Seconds (fromIntegral x / (10.0 ^ (12 :: Integer)))
src/Test/Hspec/Core/Formatters/Pretty.hs view
@@ -5,6 +5,7 @@ #ifdef TEST , pretty , recoverString+, recoverMultiLineString #endif ) where @@ -20,25 +21,27 @@ import Test.Hspec.Core.Formatters.Pretty.Parser pretty2 :: Bool -> String -> String -> (String, String)-pretty2 unicode expected actual = case (recoverString unicode expected, recoverString unicode actual) of+pretty2 unicode expected actual = case (recoverMultiLineString unicode expected, recoverMultiLineString unicode actual) of (Just expected_, Just actual_) -> (expected_, actual_) _ -> case (pretty unicode expected, pretty unicode actual) of- (Just expected_, Just actual_) -> (expected_, actual_)- _ -> (rec expected, rec actual)- where- rec = if unicode then urecover else id+ (Just expected_, Just actual_) | expected_ /= actual_ -> (expected_, actual_)+ _ -> (expected, actual) - urecover :: String -> String- urecover xs = maybe xs ushow $ readMaybe xs+recoverString :: String -> Maybe String+recoverString xs = case xs of+ '"' : _ -> case reverse xs of+ '"' : _ -> readMaybe xs+ _ -> Nothing+ _ -> Nothing -recoverString :: Bool -> String -> Maybe String-recoverString unicode input = case readMaybe input of+recoverMultiLineString :: Bool -> String -> Maybe String+recoverMultiLineString unicode input = case recoverString input of Just r | shouldParseBack r -> Just r _ -> Nothing where shouldParseBack = (&&) <$> all isSafe <*> isMultiLine isMultiLine = lines >>> length >>> (> 1)- isSafe c = (unicode || isAscii c) && (not $ isControl c) || c == '\n'+ isSafe c = (unicode || isAscii c) && not (isControl c) || c == '\n' pretty :: Bool -> String -> Maybe String pretty unicode = parseValue >=> render_
src/Test/Hspec/Core/Formatters/V1.hs view
@@ -62,7 +62,6 @@ 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.Example (Location(..))
src/Test/Hspec/Core/Formatters/V2.hs view
@@ -21,6 +21,9 @@ -- Actions live in the `FormatM` monad. It provides access to the runner state -- and primitives for appending to the generated report. , Formatter (..)+, Path+, Progress+, Location(..) , Item(..) , Result(..) , FailureReason (..)@@ -80,10 +83,9 @@ import Test.Hspec.Core.Compat hiding (First) import Data.Char-import Data.Maybe import Test.Hspec.Core.Util import Test.Hspec.Core.Clock-import Test.Hspec.Core.Example (Location(..))+import Test.Hspec.Core.Example (Location(..), Progress) import Text.Printf import Test.Hspec.Core.Formatters.Pretty.Unicode (ushow) import Control.Monad.IO.Class
src/Test/Hspec/Core/Hooks.hs view
@@ -4,7 +4,12 @@ {-# LANGUAGE ConstraintKinds #-} -- | Stability: provisional module Test.Hspec.Core.Hooks (- before+-- * Types+ Spec+, SpecWith+, ActionWith+-- * Hooks+, before , before_ , beforeWith , beforeAll@@ -109,11 +114,12 @@ -- | Run a custom action before and/or after every spec item. aroundWith :: (ActionWith a -> ActionWith b) -> SpecWith a -> SpecWith b-aroundWith = mapSpecItem_ . modifyAroundAction+aroundWith = mapSpecItem_ . modifyHook -modifyAroundAction :: (ActionWith a -> ActionWith b) -> Item a -> Item b-modifyAroundAction action item@Item{itemExample = e} =- item{ itemExample = \params aroundAction -> e params (aroundAction . action) }+modifyHook :: (ActionWith a -> ActionWith b) -> Item a -> Item b+modifyHook action item = item {+ itemExample = \ params hook -> itemExample item params (hook . action)+ } -- | Wrap an action around the given spec. aroundAll :: HasCallStack => (ActionWith a -> IO ()) -> SpecWith a -> Spec
src/Test/Hspec/Core/QuickCheckUtil.hs view
@@ -22,7 +22,6 @@ import Prelude () import Test.Hspec.Core.Compat -import Data.Maybe import Data.Int import System.Random
src/Test/Hspec/Core/Runner.hs view
@@ -75,6 +75,10 @@ , isSuccess , evaluateSummary +-- * Re-exports+, Spec+, SpecWith+ #ifdef TEST , rerunAll , specToEvalForest@@ -86,11 +90,10 @@ import Prelude () import Test.Hspec.Core.Compat -import Data.Maybe import NonEmpty (nonEmpty) import System.IO import System.Environment (getArgs, withArgs)-import System.Exit+import System.Exit (exitFailure) import System.Random import Control.Monad.ST import Data.STRef@@ -99,6 +102,7 @@ import qualified Test.QuickCheck as QC import Test.Hspec.Core.Util (Path)+import Test.Hspec.Core.Clock import Test.Hspec.Core.Spec hiding (pruneTree, pruneForest) import Test.Hspec.Core.Config import Test.Hspec.Core.Format (Format, FormatConfig(..))@@ -150,7 +154,7 @@ removeCleanup _ = pass markSuccess :: EvalItem -> EvalItem- markSuccess item = item {evalItemAction = \ _ -> return $ Result "" Success}+ markSuccess item = item {evalItemAction = \ _ -> return (0, Result "" Success)} -- | Run a given spec and write a report to `stdout`. -- Exit with `exitFailure` if at least one spec item fails.@@ -273,9 +277,6 @@ 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) @@ -320,14 +321,18 @@ | configFocusedOnly config = spec | otherwise = focusForest spec -runEvalTree :: Config -> [EvalTree] -> IO SpecResult-runEvalTree config spec = do+runSpecForest_ :: Config -> [SpecTree ()] -> IO SpecResult+runSpecForest_ config spec = do let- failOnEmpty = configFailOnEmpty config- seed = (fromJust . configQuickCheckSeed) config- qcArgs = configQuickCheckArgs config- !numberOfItems = countSpecItems spec+ filteredSpec = specToEvalForest config spec+ seed = (fromJust . configQuickCheckSeed) config+ qcArgs = configQuickCheckArgs config+ !numberOfItems = countEvalItems filteredSpec + when (configFailOnEmpty config && numberOfItems == 0) $ do+ when (countSpecItems spec /= 0) $ do+ die "all spec items have been filtered; failing due to --fail-on=empty"+ concurrentJobs <- case configConcurrentJobs config of Nothing -> getDefaultConcurrentJobs Just n -> return n@@ -335,7 +340,7 @@ (reportProgress, useColor) <- colorOutputSupported (configColorMode config) (hSupportsANSI stdout) outputUnicode <- unicodeOutputSupported (configUnicodeMode config) stdout - results <- fmap (toSpecResult failOnEmpty) . withHiddenCursor reportProgress stdout $ do+ results <- fmap toSpecResult . withHiddenCursor reportProgress stdout $ do let formatConfig = FormatConfig { formatConfigUseColor = useColor@@ -363,7 +368,7 @@ , evalConfigConcurrentJobs = concurrentJobs , evalConfigFailFast = configFailFast config }- runFormatter evalConfig spec+ runFormatter evalConfig filteredSpec let failures :: [Path]@@ -392,8 +397,13 @@ >>> randomize >>> pruneForest where+ seed :: Integer seed = (fromJust . configQuickCheckSeed) config++ params :: Params params = Params (configQuickCheckArgs config) (configSmallCheckDepth config)++ randomize :: [Tree c a] -> [Tree c a] randomize | configRandomize config = randomizeForest seed | otherwise = id@@ -415,7 +425,12 @@ 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)+ toEvalItem (Item requirement loc isParallelizable _isFocused e) = EvalItem {+ evalItemDescription = requirement+ , evalItemLocation = loc+ , evalItemConcurrency = if isParallelizable == Just True then Concurrent else Sequential+ , evalItemAction = \ progress -> measure $ e params withUnit progress+ } withUnit :: ActionWith () -> IO () withUnit action = action ()@@ -434,8 +449,8 @@ doNotLeakCommandLineArgumentsToExamples = withArgs [] withHiddenCursor :: Bool -> Handle -> IO a -> IO a-withHiddenCursor useColor h- | useColor = bracket_ (hHideCursor h) (hShowCursor h)+withHiddenCursor reportProgress h+ | reportProgress = bracket_ (hHideCursor h) (hShowCursor h) | otherwise = id colorOutputSupported :: ColorMode -> IO Bool -> IO (Bool, Bool)@@ -497,5 +512,8 @@ ref <- newSTRef (mkStdGen $ fromIntegral seed) shuffleForest ref t -countSpecItems :: [Eval.Tree c a] -> Int+countEvalItems :: [Eval.Tree c a] -> Int+countEvalItems = getSum . foldMap (foldMap . const $ Sum 1)++countSpecItems :: [Tree c a] -> Int countSpecItems = getSum . foldMap (foldMap . const $ Sum 1)
src/Test/Hspec/Core/Runner/Eval.hs view
@@ -2,38 +2,24 @@ {-# LANGUAGE DeriveFunctor #-} {-# LANGUAGE DeriveFoldable #-} {-# LANGUAGE DeriveTraversable #-}-{-# 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.*-{-# OPTIONS_GHC -fno-warn-deprecations #-}-#endif- module Test.Hspec.Core.Runner.Eval ( EvalConfig(..) , EvalTree , Tree(..) , EvalItem(..)+, Concurrency(..) , runFormatter #ifdef TEST , mergeResults-, runSequentially #endif ) where import Prelude () import Test.Hspec.Core.Compat hiding (Monad)-import qualified Test.Hspec.Core.Compat as M -import Control.Concurrent-import Control.Concurrent.Async hiding (cancel)- import Control.Monad.IO.Class (liftIO)-import qualified Control.Monad.IO.Class as M- import Control.Monad.Trans.Reader import Control.Monad.Trans.Class @@ -44,21 +30,19 @@ import qualified Test.Hspec.Core.Format as Format import Test.Hspec.Core.Clock import Test.Hspec.Core.Example.Location-import Test.Hspec.Core.Example (safeEvaluateResultStatus)+import Test.Hspec.Core.Example (safeEvaluateResultStatus, exceptionToResultStatus) import qualified NonEmpty import NonEmpty (NonEmpty(..)) +import Test.Hspec.Core.Runner.JobQueue+ 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)- data EvalConfig = EvalConfig { evalConfigFormat :: Format , evalConfigConcurrentJobs :: Int@@ -67,6 +51,7 @@ data Env = Env { envConfig :: EvalConfig+, envFailed :: IORef Bool , envResults :: IORef [(Path, Format.Item)] } @@ -77,14 +62,21 @@ type EvalM = ReaderT Env IO +setFailed :: EvalM ()+setFailed = do+ ref <- asks envFailed+ liftIO $ writeIORef ref True++hasFailed :: EvalM Bool+hasFailed = do+ ref <- asks envFailed+ liftIO $ readIORef ref+ addResult :: Path -> Format.Item -> EvalM () addResult path item = do ref <- asks envResults liftIO $ modifyIORef ref ((path, item) :) -getResults :: EvalM [(Path, Format.Item)]-getResults = reverse <$> (asks envResults >>= liftIO . readIORef)- reportItem :: Path -> Maybe Location -> EvalM (Seconds, Result) -> EvalM () reportItem path loc action = do reportItemStarted path@@ -95,6 +87,12 @@ reportItemDone :: Path -> Format.Item -> EvalM () reportItemDone path item = do+ let+ isFailure = case Format.itemResult item of+ Format.Success{} -> False+ Format.Pending{} -> False+ Format.Failure{} -> True+ when isFailure setFailed addResult path item formatEvent $ Format.ItemDone path item @@ -116,8 +114,8 @@ data EvalItem = EvalItem { evalItemDescription :: String , evalItemLocation :: Maybe Location-, evalItemParallelize :: Bool-, evalItemAction :: ProgressCallback -> IO Result+, evalItemConcurrency :: Concurrency+, evalItemAction :: ProgressCallback -> IO (Seconds, Result) } type EvalTree = Tree (IO ()) EvalItem@@ -125,46 +123,55 @@ -- | Evaluate all examples of a given spec and produce a report. runFormatter :: EvalConfig -> [EvalTree] -> IO ([(Path, Format.Item)]) runFormatter config specs = do- ref <- newIORef []+ withJobQueue (evalConfigConcurrentJobs config) $ \ queue -> do+ withTimer 0.05 $ \ timer -> do+ env <- mkEnv+ runningSpecs_ <- enqueueItems queue specs - let- start = parallelizeTree (evalConfigConcurrentJobs config) specs- cancel = cancelMany . concatMap toList . map (fmap fst)+ let+ applyReportProgress :: RunningItem_ IO -> RunningItem+ applyReportProgress item = fmap (. reportProgress timer) item - bracket start cancel $ \ runningSpecs -> do- withTimer 0.05 $ \ timer -> do+ runningSpecs :: [RunningTree ()]+ runningSpecs = applyCleanup $ map (fmap applyReportProgress) runningSpecs_ - format Format.Started- runReaderT (run . applyCleanup $ map (fmap (fmap (. reportProgress timer) . snd)) runningSpecs) (Env config ref) `finally` do- results <- reverse <$> readIORef ref- format (Format.Done results)+ getResults :: IO [(Path, Format.Item)]+ getResults = reverse <$> readIORef (envResults env) - results <- reverse <$> readIORef ref- return results+ formatItems :: IO ()+ formatItems = runReaderT (eval runningSpecs) env++ formatDone :: IO ()+ formatDone = getResults >>= format . Format.Done++ format Format.Started+ formatItems `finally` formatDone+ getResults where+ mkEnv :: IO Env+ mkEnv = Env config <$> newIORef False <*> newIORef []++ format :: Format format = evalConfigFormat config + reportProgress :: IO Bool -> Path -> Progress -> IO () reportProgress timer path progress = do r <- timer when r $ do format (Format.Progress path progress) -cancelMany :: [Async a] -> IO ()-cancelMany asyncs = do- mapM_ (killThread . asyncThreadId) asyncs- mapM_ waitCatch asyncs- data Item a = Item {- _itemDescription :: String-, _itemLocation :: Maybe Location+ itemDescription :: String+, itemLocation :: Maybe Location , itemAction :: a } deriving Functor -type Job m progress a = (progress -> m ()) -> m a- type RunningItem = Item (Path -> IO (Seconds, Result)) type RunningTree c = Tree c RunningItem +type RunningItem_ m = Item (Job m Progress (Seconds, Result))+type RunningTree_ m = Tree (IO ()) (RunningItem_ m)+ applyCleanup :: [RunningTree (IO ())] -> [RunningTree ()] applyCleanup = map go where@@ -211,63 +218,23 @@ 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)--data Semaphore = Semaphore {- semaphoreWait :: IO ()-, semaphoreSignal :: IO ()-}--parallelizeTree :: MonadIO m => Int -> [EvalTree] -> IO [RunningTree_ m]-parallelizeTree n specs = do- sem <- newQSem n- mapM (traverse $ parallelizeItem sem) specs--parallelizeItem :: MonadIO m => QSem -> EvalItem -> IO (RunningItem_ m)-parallelizeItem sem EvalItem{..} = do- (asyncAction, evalAction) <- parallelize (Semaphore (waitQSem sem) (signalQSem sem)) evalItemParallelize (interruptible . evalItemAction)- return (asyncAction, Item evalItemDescription evalItemLocation evalAction)--parallelize :: MonadIO m => Semaphore -> Bool -> Job IO progress a -> IO (Async (), Job m progress (Seconds, a))-parallelize sem isParallelizable- | isParallelizable = runParallel sem- | otherwise = runSequentially--runSequentially :: MonadIO m => Job IO progress a -> IO (Async (), Job m progress (Seconds, a))-runSequentially action = do- mvar <- newEmptyMVar- (asyncAction, evalAction) <- runParallel (Semaphore (takeMVar mvar) pass) action- return (asyncAction, \ notifyPartial -> liftIO (putMVar mvar ()) >> evalAction notifyPartial)--data Parallel progress a = Partial progress | Return a+enqueueItems :: MonadIO m => JobQueue -> [EvalTree] -> IO [RunningTree_ m]+enqueueItems queue = mapM (traverse $ enqueueItem queue) -runParallel :: forall m progress a. MonadIO m => Semaphore -> Job IO progress a -> IO (Async (), Job m progress (Seconds, a))-runParallel Semaphore{..} action = do- mvar <- newEmptyMVar- asyncAction <- async $ bracket_ semaphoreWait semaphoreSignal (worker mvar)- return (asyncAction, eval mvar)+enqueueItem :: MonadIO m => JobQueue -> EvalItem -> IO (RunningItem_ m)+enqueueItem queue EvalItem{..} = do+ job <- enqueueJob queue evalItemConcurrency evalItemAction+ return Item {+ itemDescription = evalItemDescription+ , itemLocation = evalItemLocation+ , itemAction = job >=> liftIO . either exceptionToResult return+ } where- worker :: MVar (Parallel progress (Seconds, a)) -> IO ()- worker mvar = do- let partialCallback = replaceMVar mvar . Partial- result <- measure $ action partialCallback- replaceMVar mvar (Return result)-- eval :: MVar (Parallel progress (Seconds, a)) -> (progress -> m ()) -> m (Seconds, a)- eval mvar notifyPartial = do- r <- liftIO (takeMVar mvar)- case r of- Partial p -> do- notifyPartial p- eval mvar notifyPartial- Return result -> return result--replaceMVar :: MVar a -> a -> IO ()-replaceMVar mvar p = tryTakeMVar mvar >> putMVar mvar p+ exceptionToResult :: SomeException -> IO (Seconds, Result)+ exceptionToResult err = (,) 0 . Result "" <$> exceptionToResultStatus err -run :: [RunningTree ()] -> EvalM ()-run specs = do+eval :: [RunningTree ()] -> EvalM ()+eval specs = do failFast <- asks (evalConfigFailFast . envConfig) sequenceActions failFast (concatMap foldSpec specs) where@@ -320,13 +287,5 @@ action stopNow <- case failFast of False -> return False- True -> any itemIsFailure <$> getResults+ True -> hasFailed unless stopNow (go actions)-- 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
+ src/Test/Hspec/Core/Runner/JobQueue.hs view
@@ -0,0 +1,121 @@+{-# LANGUAGE CPP #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE ConstraintKinds #-}++#if MIN_VERSION_base(4,6,0) && !MIN_VERSION_base(4,7,0)+-- Control.Concurrent.QSem is deprecated in base-4.6.0.*+{-# OPTIONS_GHC -fno-warn-deprecations #-}+#endif++module Test.Hspec.Core.Runner.JobQueue (+ MonadIO+, Job+, Concurrency(..)+, JobQueue+, withJobQueue+, enqueueJob+) where++import Prelude ()+import Test.Hspec.Core.Compat hiding (Monad)+import qualified Test.Hspec.Core.Compat as M++import Control.Concurrent+import Control.Concurrent.Async (Async, AsyncCancelled(..), async, waitCatch, asyncThreadId)++import Control.Monad.IO.Class (liftIO)+import qualified Control.Monad.IO.Class as M++-- 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)++type Job m progress a = (progress -> m ()) -> m a++data Concurrency = Sequential | Concurrent++data JobQueue = JobQueue {+ _semaphore :: Semaphore+, _cancelQueue :: CancelQueue+}++data Semaphore = Semaphore {+ _wait :: IO ()+, _signal :: IO ()+}++type CancelQueue = IORef [Async ()]++withJobQueue :: Int -> (JobQueue -> IO a) -> IO a+withJobQueue concurrency = bracket new cancelAll+ where+ new :: IO JobQueue+ new = JobQueue <$> newSemaphore concurrency <*> newIORef []++ cancelAll :: JobQueue -> IO ()+ cancelAll (JobQueue _ cancelQueue) = readIORef cancelQueue >>= cancelMany++ cancelMany :: [Async a] -> IO ()+ cancelMany jobs = do+ mapM_ notifyCancel jobs+ mapM_ waitCatch jobs++ notifyCancel :: Async a -> IO ()+ notifyCancel = flip throwTo AsyncCancelled . asyncThreadId++newSemaphore :: Int -> IO Semaphore+newSemaphore capacity = do+ sem <- newQSem capacity+ return $ Semaphore (waitQSem sem) (signalQSem sem)++enqueueJob :: MonadIO m => JobQueue -> Concurrency -> Job IO progress a -> IO (Job m progress (Either SomeException a))+enqueueJob (JobQueue sem cancelQueue) concurrency = case concurrency of+ Sequential -> runSequentially cancelQueue+ Concurrent -> runConcurrently sem cancelQueue++runSequentially :: forall m progress a. MonadIO m => CancelQueue -> Job IO progress a -> IO (Job m progress (Either SomeException a))+runSequentially cancelQueue action = do+ barrier <- newEmptyMVar+ let+ wait :: IO ()+ wait = takeMVar barrier++ signal :: m ()+ signal = liftIO $ putMVar barrier ()++ job <- runConcurrently (Semaphore wait pass) cancelQueue action+ return $ \ notifyPartial -> signal >> job notifyPartial++data Partial progress a = Partial progress | Done++runConcurrently :: forall m progress a. MonadIO m => Semaphore -> CancelQueue -> Job IO progress a -> IO (Job m progress (Either SomeException a))+runConcurrently (Semaphore wait signal) cancelQueue action = do+ result :: MVar (Partial progress a) <- newEmptyMVar+ let+ worker :: IO a+ worker = bracket_ wait signal $ do+ interruptible (action partialResult) `finally` done+ where+ partialResult :: progress -> IO ()+ partialResult = replaceMVar result . Partial++ done :: IO ()+ done = replaceMVar result Done++ pushOnCancelQueue :: Async a -> IO ()+ pushOnCancelQueue = (modifyIORef cancelQueue . (:) . void)++ job <- bracket (async worker) pushOnCancelQueue return++ let+ waitForResult :: (progress -> m ()) -> m (Either SomeException a)+ waitForResult notifyPartial = do+ r <- liftIO (takeMVar result)+ case r of+ Partial progress -> notifyPartial progress >> waitForResult notifyPartial+ Done -> liftIO $ waitCatch job++ return waitForResult++replaceMVar :: MVar a -> a -> IO ()+replaceMVar mvar p = tryTakeMVar mvar >> putMVar mvar p
src/Test/Hspec/Core/Runner/Result.hs view
@@ -59,11 +59,11 @@ | ResultItemFailure deriving (Eq, Show) -toSpecResult :: Bool -> [(Path, Format.Item)] -> SpecResult-toSpecResult failOnEmpty results = SpecResult items success+toSpecResult :: [(Path, Format.Item)] -> SpecResult+toSpecResult results = SpecResult items success where items = map toResultItem results- success = not (failOnEmpty && null results) && all (not . resultItemIsFailure) items+ success = all (not . resultItemIsFailure) items toResultItem :: (Path, Format.Item) -> ResultItem toResultItem (path, item) = ResultItem path status
src/Test/Hspec/Core/Spec.hs view
@@ -73,6 +73,10 @@ , Test.Hspec.Core.Tree.location , focusForest++-- * Re-exports+, HasCallStack+, Expectation ) where import Prelude ()
src/Test/Hspec/Core/Tree.hs view
@@ -29,7 +29,6 @@ import Data.CallStack (SrcLoc(..)) import qualified Data.CallStack as CallStack-import Data.Maybe import Test.Hspec.Core.Example @@ -126,8 +125,14 @@ | otherwise = s -- | The @specItem@ function creates a spec item.-specItem :: (HasCallStack, Example a) => String -> a -> SpecTree (Arg a)-specItem s e = Leaf $ Item requirement location Nothing False (safeEvaluateExample e)+specItem :: (HasCallStack, Example e) => String -> e -> SpecTree (Arg e)+specItem s e = Leaf Item {+ itemRequirement = requirement+ , itemLocation = location+ , itemIsParallelizable = Nothing+ , itemIsFocused = False+ , itemExample = safeEvaluateExample e+ } where requirement :: HasCallStack => String requirement
test/Helper.hs view
@@ -8,6 +8,7 @@ , module Test.Hspec.Core.Compat , module Test.QuickCheck , module System.IO.Silently+, Seconds(..) , sleep , timeout , defaultParams
test/Test/Hspec/Core/Formatters/PrettySpec.hs view
@@ -19,23 +19,53 @@ it "does not recover unicode" $ do pretty2 False (show "foo\955bar") (show "foo-bar") `shouldBe` ("\"foo\\955bar\"", "\"foo-bar\"") + context "when expected and actual would be equal after pretty-printing" $ do+ it "returns the original values unmodified" $ do+ pretty2 True (show "foo") (show "foo" <> " ") `shouldBe` (show "foo", show "foo" <> " ")+ describe "recoverString" $ do- it "parses back multi-line string literals" $ do- recoverString True (show "foo\nbar\nbaz\n") `shouldBe` Just "foo\nbar\nbaz\n"+ it "recovers a string" $ do+ recoverString (show "foo") `shouldBe` Just "foo" - it "does not parse back string literals that contain control characters" $ do- recoverString True (show "foo\n\tbar\nbaz\n") `shouldBe` Nothing+ it "recovers the empty string" $ do+ recoverString (show "") `shouldBe` Just "" - it "does not parse back string literals that span a single line" $ do- recoverString True (show "foo\n") `shouldBe` Nothing+ it "does not recover a string with leading space" $ do+ recoverString (" " <> show "foo") `shouldBe` Nothing + it "does not recover a string with trailing space" $ do+ recoverString (show "foo" <> " ") `shouldBe` Nothing++ it "does not recover an empty list" $ do+ recoverString "[]" `shouldBe` Nothing++ describe "recoverMultiLineString" $ do+ let+ multiLineString :: String+ multiLineString = "foo\nbar\nbaz\n"++ it "recovers multi-line string literals" $ do+ recoverMultiLineString True (show multiLineString) `shouldBe` Just multiLineString++ it "does not recover string literals that contain control characters" $ do+ recoverMultiLineString True (show "foo\n\tbar\nbaz\n") `shouldBe` Nothing++ it "does not recover string literals that span a single line" $ do+ recoverMultiLineString True (show "foo\n") `shouldBe` Nothing++ it "does not recover a string with trailing space" $ do+ recoverMultiLineString True (" " <> show multiLineString) `shouldBe` Nothing++ it "does not recover a string with trailing space" $ do+ recoverMultiLineString True (show multiLineString <> " ") `shouldBe` Nothing+ context "when unicode is True" $ do- it "parses back string literals that contain unicode" $ do- recoverString True (show "foo\n\955\nbaz\n") `shouldBe` Just "foo\n\955\nbaz\n"+ it "recovers string literals that contain unicode" $ do+ recoverMultiLineString True (show "foo\n\955\nbaz\n") `shouldBe` Just "foo\n\955\nbaz\n" context "when unicode is False" $ do- it "does not parse back string literals that contain unicode" $ do- recoverString False (show "foo\n\955\nbaz\n") `shouldBe` Nothing+ it "does not recover string literals that contain unicode" $ do+ recoverMultiLineString False (show "foo\n\955\nbaz\n") `shouldBe` Nothing describe "pretty" $ do let person = Person "Joe" 23
test/Test/Hspec/Core/Formatters/V2Spec.hs view
@@ -100,7 +100,7 @@ formatter = checks config = H.defaultConfig { H.configFormat = Just $ formatterToFormat formatter } - it "" $ do+ it "prints unicode check marks" $ do r <- captureLines . H.hspecWithResult config $ do H.it "foo" True normalizeSummary r `shouldBe` [@@ -111,7 +111,7 @@ , "1 example, 0 failures" ] - it "" $ do+ it "uses ASCII as a fallback" $ do r <- captureLines . H.hspecWithResult config { H.configUnicodeMode = H.UnicodeNever } $ do H.it "foo" True normalizeSummary r `shouldBe` [
test/Test/Hspec/Core/Runner/EvalSpec.hs view
@@ -73,13 +73,3 @@ ref <- newIORef [] traverse_ (modifyIORef ref . (:) ) tree reverse <$> readIORef ref `shouldReturn` [1 .. 4]-- describe "runSequentially" $ do- it "runs actions sequentially" $ do- ref <- newIORef []- (_, actionA) <- runSequentially $ \ _ -> modifyIORef ref (23 :)- (_, actionB) <- runSequentially $ \ _ -> modifyIORef ref (42 :)- (_, ()) <- actionB (\_ -> pass)- readIORef ref `shouldReturn` [42 :: Int]- (_, ()) <- actionA (\_ -> pass)- readIORef ref `shouldReturn` [23, 42]
+ test/Test/Hspec/Core/Runner/JobQueueSpec.hs view
@@ -0,0 +1,44 @@+module Test.Hspec.Core.Runner.JobQueueSpec (spec) where++import Prelude ()+import Helper++import Control.Concurrent++import Test.Hspec.Core.Runner.JobQueue++spec :: Spec+spec = do+ describe "enqueueJob" $ do+ let+ waitFor job = job (\ _ -> pass) >>= either throwIO return++ context "with Sequential" $ do+ it "runs actions sequentially" $ do+ withJobQueue 10 $ \ queue -> do+ ref <- newIORef []+ jobA <- enqueueJob queue Sequential $ \ _ -> modifyIORef ref (23 :)+ jobB <- enqueueJob queue Sequential $ \ _ -> modifyIORef ref (42 :)+ waitFor jobB+ readIORef ref `shouldReturn` [42 :: Int]+ waitFor jobA+ readIORef ref `shouldReturn` [23, 42]++ context "with Concurrent" $ do+ it "runs actions concurrently" $ do+ withJobQueue 10 $ \ queue -> do+ barrierA <- newEmptyMVar+ barrierB <- newEmptyMVar++ jobA <- enqueueJob queue Concurrent $ \ _ -> do+ putMVar barrierB ()+ takeMVar barrierA++ jobB <- enqueueJob queue Concurrent $ \ _ -> do+ putMVar barrierA ()+ takeMVar barrierB++ timeout (0.1 :: Seconds) $ do+ waitFor jobA+ waitFor jobB+ `shouldReturn` Just ()
test/Test/Hspec/Core/Runner/ResultSpec.hs view
@@ -16,21 +16,12 @@ context "when all spec items passed" $ do it "returns True" $ do- specResultSuccess (toSpecResult False [item Success]) `shouldBe` True+ specResultSuccess (toSpecResult [item Success]) `shouldBe` True context "with a failed spec item" $ do it "returns False" $ do- specResultSuccess (toSpecResult False [item Success, item failure]) `shouldBe` False+ specResultSuccess (toSpecResult [item Success, item failure]) `shouldBe` False context "with an empty result list" $ do it "returns True" $ do- specResultSuccess (toSpecResult False []) `shouldBe` True-- context "when configFailOnEmpty is True" $ do- context "when all spec items passed" $ do- it "returns True" $ do- specResultSuccess (toSpecResult True [item Success]) `shouldBe` True-- context "with an empty result list" $ do- it "returns False" $ do- specResultSuccess (toSpecResult True []) `shouldBe` False+ specResultSuccess (toSpecResult []) `shouldBe` True
test/Test/Hspec/Core/RunnerSpec.hs view
@@ -270,15 +270,12 @@ context "with --fail-on=empty" $ do it "fails if no spec items have been run" $ do- (out, r) <- capture . try . withArgs ["--skip=", "--fail-on=empty"] . H.hspec $ do+ (out, r) <- hCapture [stdout, stderr] . try . withProgName "spec" . withArgs ["--skip=", "--fail-on=empty"] . H.hspec $ do H.it "foo" True H.it "bar" True H.it "baz" True unlines (normalizeSummary (lines out)) `shouldBe` unlines [- ""- , ""- , "Finished in 0.0000 seconds"- , "0 examples, 0 failures"+ "spec: all spec items have been filtered; failing due to --fail-on=empty" ] r `shouldBe` Left (ExitFailure 1) @@ -697,6 +694,16 @@ it "treats uncaught exceptions as failure" $ do hspecResult_ $ do H.it "foobar" throwException_+ `shouldReturn` H.Summary 1 1++ it "handles unguarded exceptions in runner" $ do+ let+ throwExceptionThatIsNotGuardedBy_safeTry :: H.Item () -> H.Item ()+ throwExceptionThatIsNotGuardedBy_safeTry item = item {+ H.itemExample = \ _params _hook _progress -> throwIO DivideByZero+ }+ hspecResult_ $ H.mapSpecItem_ throwExceptionThatIsNotGuardedBy_safeTry $ do+ H.it "foo" True `shouldReturn` H.Summary 1 1 it "uses the specdoc formatter by default" $ do
test/Test/Hspec/Core/SpecSpec.hs view
@@ -18,8 +18,13 @@ spec :: Spec spec = do- let runSpecM = fmap snd . H.runSpecM+ let+ runSpecM :: H.SpecWith a -> IO [H.SpecTree a]+ runSpecM = fmap snd . H.runSpecM + runItem :: Item () -> IO Result+ runItem item = itemExample item defaultParams ($ ()) noOpProgressCallback+ describe "getSpecDescriptionPath" $ do it "returns the spec path" $ do let descriptionPathShouldBe xs =@@ -51,8 +56,7 @@ describe "xdescribe" $ do it "creates a tree of pending spec items" $ do [Node _ [Leaf item]] <- runSpecM (H.xdescribe "" $ H.it "whatever" True)- r <- itemExample item defaultParams ($ ()) noOpProgressCallback- r `shouldBe` Result "" (Pending Nothing Nothing)+ runItem item `shouldReturn` Result "" (Pending Nothing Nothing) describe "it" $ do it "takes a description of a desired behavior" $ do@@ -61,8 +65,7 @@ it "takes an example of that behavior" $ do [Leaf item] <- runSpecM (H.it "whatever" True)- r <- itemExample item defaultParams ($ ()) noOpProgressCallback- r `shouldBe` Result "" Success+ runItem item `shouldReturn` Result "" Success it "adds source locations" $ do [Leaf item] <- runSpecM (H.it "foo" True)@@ -81,8 +84,7 @@ describe "xit" $ do it "creates a pending spec item" $ do [Leaf item] <- runSpecM (H.xit "whatever" True)- r <- itemExample item defaultParams ($ ()) noOpProgressCallback- r `shouldBe` Result "" (Pending Nothing Nothing)+ runItem item `shouldReturn` Result "" (Pending Nothing Nothing) describe "pending" $ do it "specifies a pending example" $ do
version.yaml view
@@ -1,1 +1,1 @@-&version 2.10.8+&version 2.10.9