packages feed

hspec-meta 2.4.6 → 2.5.6

raw patch · 29 files changed

+2295/−857 lines, 29 filesdep +clockdep +stmdep −asyncdep ~QuickCheckPVP ok

version bump matches the API change (PVP)

Dependencies added: clock, stm

Dependencies removed: async

Dependency ranges changed: QuickCheck

API changes (from Hackage documentation)

- Test.Hspec.Meta: type Arg e = ();
- Test.Hspec.Meta: type family Arg e;
- Test.Hspec.Meta: }
- Test.Hspec.Meta: class Example e where {
+ Test.Hspec.Meta: class Example e
- Test.Hspec.Meta: context :: String -> SpecWith a -> SpecWith a
+ Test.Hspec.Meta: context :: HasCallStack => String -> SpecWith a -> SpecWith a
- Test.Hspec.Meta: describe :: String -> SpecWith a -> SpecWith a
+ Test.Hspec.Meta: describe :: HasCallStack => String -> SpecWith a -> SpecWith a
- Test.Hspec.Meta: pending :: Expectation
+ Test.Hspec.Meta: pending :: HasCallStack => Expectation
- Test.Hspec.Meta: pendingWith :: String -> Expectation
+ Test.Hspec.Meta: pendingWith :: HasCallStack => String -> Expectation
- Test.Hspec.Meta: xcontext :: String -> SpecWith a -> SpecWith a
+ Test.Hspec.Meta: xcontext :: HasCallStack => String -> SpecWith a -> SpecWith a
- Test.Hspec.Meta: xdescribe :: String -> SpecWith a -> SpecWith a
+ Test.Hspec.Meta: xdescribe :: HasCallStack => String -> SpecWith a -> SpecWith a

Files

CHANGES.markdown view
@@ -1,3 +1,52 @@+## Changes in 2.5.5+  - Use `module[line:column]` instead of `module:line:column` as default label+    for `describe`/`it` (fixes #366)++## Changes in 2.5.4+  - Show how to rerun individual spec items on test failures (see #205)++## Changes in 2.5.3+  - Treat character escapes like `\NUL` as single tokens on `--diff` (see #351)+  - Allow a `/` at the beginning and at the end of an absolute path that is+    passed to `--match` or `--skip`++## Changes in 2.5.2+  - Use module:line:column as default label for describe/it (see #250)+  - Warn if user is affected by https://ghc.haskell.org/trac/ghc/ticket/13285 (see #329)++## Changes in 2.5.1+  - Disable tests for Test.Hspec.Core.Timer (see #352)++## Changes in 2.5.0+  - Add `sequential` (see #311)+  - Add support for `--diff` when `shouldBe` is uesd with+    `QuickCheck`-properties+  - Add source locations when `shouldBe` is uesd with `QuickCheck` properties+  - Print `QuickCheck` labels on success (see #297)+  - Retain output of `verbose`, `label`, `collect`, `classify`, etc. for+    `QuickCheck` properties (see #257)+  - Extract source location from error / undefined (see #316)+  - Parse source locations from pattern match failures+  - Include source column when formatting source locations+  - Colorize whitespaces with background color instead of foreground color with+    `--diff`+  - Run `Test.Hspec.Core.Formatters.exampleProgress` in `FormatM` instead of+    `IO`+  - Make sure that progress output is always cleared (fixes #301)+  - Add location information to `pending` (not used by any formatter yet)+  - Include duration for each spec item in new formatter API (see #315) (not yet exposed)+  - Removed deprecated module `Test.Hspec.HUnit`, use+    `Test.Hspec.Contrib.HUnit` instead+  - Deprecate `--out`+  - Remove `BestEffort` source locations++## Changes in 2.4.8+  - compatibility with GHC 8.4.1-alpha3++## Changes in 2.4.7+  - compatibility with `QuickCheck-2.11.3` and up (note that `QuickCheck`+    versions `2.11` to `2.11.2` are not fully supported)+ ## Changes in 2.4.6   - compatibility with the upcoming version `4.11.0.0` of `base` 
LICENSE view
@@ -1,4 +1,4 @@-Copyright (c) 2011-2017 Simon Hengel <sol@typeful.net>+Copyright (c) 2011-2018 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/Test/Hspec/Core/Clock.hs view
@@ -0,0 +1,33 @@+{-# LANGUAGE GeneralizedNewtypeDeriving #-}+module Test.Hspec.Core.Clock (+  Seconds(..)+, toMicroseconds+, getMonotonicTime+, measure+, sleep+) where++import           Text.Printf+import           System.Clock+import           Control.Concurrent++newtype Seconds = Seconds Double+  deriving (Eq, Show, Num, Fractional, PrintfArg)++toMicroseconds :: Seconds -> Int+toMicroseconds (Seconds s) = floor (s * 1000000)++getMonotonicTime :: IO Seconds+getMonotonicTime = do+  t <- getTime Monotonic+  return $ Seconds ((fromIntegral . toNanoSecs $ t) / 1000000000)++measure :: IO a -> IO (Seconds, a)+measure action = do+  t0 <- getMonotonicTime+  a <- action+  t1 <- getMonotonicTime+  return (t1 - t0, a)++sleep :: Seconds -> IO ()+sleep = threadDelay . toMicroseconds
hspec-core/src/Test/Hspec/Core/Compat.hs view
@@ -9,19 +9,33 @@  , module Prelude , module Control.Applicative+, module Control.Monad , module Data.Foldable , module Data.Traversable , module Data.Monoid+, module Data.List  #if !MIN_VERSION_base(4,6,0) , modifyIORef'+, atomicWriteIORef #endif+, interruptible ) where  import           Control.Applicative+import           Control.Monad hiding (+    mapM+  , mapM_+  , forM+  , forM_+  , msum+  , sequence+  , sequence_+  ) import           Data.Foldable import           Data.Traversable import           Data.Monoid+import           Data.List (intercalate)  import           Prelude hiding (     all@@ -54,6 +68,12 @@ import           Data.Typeable (tyConModule, tyConName) import           Control.Concurrent +#if MIN_VERSION_base(4,9,0)+import           Control.Exception (interruptible)+#else+import           GHC.IO+#endif+ #if !MIN_VERSION_base(4,6,0) import qualified Text.ParserCombinators.ReadP as P @@ -64,6 +84,11 @@     let x' = f x     x' `seq` writeIORef ref x' +atomicWriteIORef :: IORef a -> a -> IO ()+atomicWriteIORef ref a = do+    x <- atomicModifyIORef ref (\_ -> (a, ()))+    x `seq` return ()+ -- | Parse a string using the 'Read' instance. -- Succeeds if there is exactly one valid result. -- A 'Left' value indicates a parse error.@@ -104,3 +129,13 @@  getDefaultConcurrentJobs :: IO Int getDefaultConcurrentJobs = getNumCapabilities++#if !MIN_VERSION_base(4,9,0)+interruptible :: IO a -> IO a+interruptible act = do+  st <- getMaskingState+  case st of+    Unmasked              -> act+    MaskedInterruptible   -> unsafeUnmask act+    MaskedUninterruptible -> act+#endif
hspec-core/src/Test/Hspec/Core/Config.hs view
@@ -12,9 +12,9 @@ ) where  import           Prelude ()+import           Test.Hspec.Core.Compat  import           Control.Exception-import           Control.Monad import           Data.Maybe import           System.IO import           System.IO.Error@@ -24,8 +24,7 @@ import qualified Test.QuickCheck as QC  import           Test.Hspec.Core.Util-import           Test.Hspec.Core.Compat-import           Test.Hspec.Core.Options+import           Test.Hspec.Core.Config.Options import           Test.Hspec.Core.FailureReport import           Test.Hspec.Core.QuickCheckUtil (mkGen) import           Test.Hspec.Core.Example (Params(..), defaultParams)
+ hspec-core/src/Test/Hspec/Core/Config/Options.hs view
@@ -0,0 +1,311 @@+module Test.Hspec.Core.Config.Options (+  Config(..)+, ColorMode (..)+, defaultConfig+, filterOr+, parseOptions+, ConfigFile+, ignoreConfigFile+, envVarName+) where++import           Prelude ()+import           Test.Hspec.Core.Compat++import           System.IO+import           System.Exit+import           System.Console.GetOpt++import           Test.Hspec.Core.Formatters+import           Test.Hspec.Core.Config.Util+import           Test.Hspec.Core.Util+import           Test.Hspec.Core.Example (Params(..), defaultParams)+import           Data.Functor.Identity+import           Data.Maybe++type ConfigFile = (FilePath, [String])++type EnvVar = [String]++envVarName :: String+envVarName = "HSPEC_OPTIONS"++data Config = Config {+  configIgnoreConfigFile :: Bool+, configDryRun :: Bool+, configPrintCpuTime :: Bool+, configFastFail :: Bool+, configFailureReport :: Maybe FilePath+, configRerun :: Bool+, configRerunAllOnSuccess :: Bool++-- |+-- A predicate that is used to filter the spec before it is run.  Only examples+-- that satisfy the predicate are run.+, configFilterPredicate :: Maybe (Path -> Bool)+, configSkipPredicate :: Maybe (Path -> Bool)+, configQuickCheckSeed :: Maybe Integer+, configQuickCheckMaxSuccess :: Maybe Int+, configQuickCheckMaxDiscardRatio :: Maybe Int+, configQuickCheckMaxSize :: Maybe Int+, configSmallCheckDepth :: Int+, configColorMode :: ColorMode+, configDiff :: Bool+, configFormatter :: Maybe Formatter+, configHtmlOutput :: Bool+, configOutputFile :: Either Handle FilePath+, configConcurrentJobs :: Maybe Int+}++defaultConfig :: Config+defaultConfig = Config {+  configIgnoreConfigFile = False+, configDryRun = False+, configPrintCpuTime = False+, configFastFail = False+, configFailureReport = Nothing+, configRerun = False+, configRerunAllOnSuccess = False+, configFilterPredicate = Nothing+, configSkipPredicate = Nothing+, configQuickCheckSeed = Nothing+, configQuickCheckMaxSuccess = Nothing+, configQuickCheckMaxDiscardRatio = Nothing+, configQuickCheckMaxSize = Nothing+, configSmallCheckDepth = paramsSmallCheckDepth defaultParams+, configColorMode = ColorAuto+, configDiff = True+, configFormatter = Nothing+, configHtmlOutput = False+, configOutputFile = Left stdout+, configConcurrentJobs = Nothing+}++filterOr :: Maybe (Path -> Bool) -> Maybe (Path -> Bool) -> Maybe (Path -> Bool)+filterOr p1_ p2_ = case (p1_, p2_) of+  (Just p1, Just p2) -> Just $ \path -> p1 path || p2 path+  _ -> p1_ <|> p2_++addMatch :: String -> Config -> Config+addMatch s c = c {configFilterPredicate = Just (filterPredicate s) `filterOr` configFilterPredicate c}++addSkip :: String -> Config -> Config+addSkip s c = c {configSkipPredicate = Just (filterPredicate s) `filterOr` configSkipPredicate c}++setDepth :: Int -> Config -> Config+setDepth n c = c {configSmallCheckDepth = n}++setMaxSuccess :: Int -> Config -> Config+setMaxSuccess n c = c {configQuickCheckMaxSuccess = Just n}++setMaxSize :: Int -> Config -> Config+setMaxSize n c = c {configQuickCheckMaxSize = Just n}++setMaxDiscardRatio :: Int -> Config -> Config+setMaxDiscardRatio n c = c {configQuickCheckMaxDiscardRatio = Just n}++setSeed :: Integer -> Config -> Config+setSeed n c = c {configQuickCheckSeed = Just n}++data ColorMode = ColorAuto | ColorNever | ColorAlways+  deriving (Eq, Show)++type Result m = Either InvalidArgument (m Config)++data InvalidArgument = InvalidArgument String String++data Arg a = Arg {+  _argumentName   :: String+, _argumentParser :: String -> Maybe a+, _argumentSetter :: a -> Config -> Config+}++mkOption :: Monad m => [Char] -> String -> Arg a -> String -> OptDescr (Result m -> Result m)+mkOption shortcut name (Arg argName parser setter) help = Option shortcut [name] (ReqArg arg argName) help+  where+    arg input x = x >>= \c -> case parser input of+      Just n -> Right (setter n `liftM` c)+      Nothing -> Left (InvalidArgument name input)++mkFlag :: Monad m => String -> (Bool -> Config -> Config) -> String -> [OptDescr (Result m -> Result m)]+mkFlag name setter help = [+    Option [] [name] (NoArg $ set $ setter True) help+  , Option [] ["no-" ++ name] (NoArg $ set $ setter False) ("do not " ++ help)+  ]++commandLineOptions :: [OptDescr (Result Maybe -> Result Maybe)]+commandLineOptions = [+    Option [] ["help"] (NoArg (const $ Right Nothing)) "display this help and exit"+  , Option [] ["ignore-dot-hspec"] (NoArg setIgnoreConfigFile) "do not read options from ~/.hspec and .hspec"+  , mkOption "m" "match" (Arg "PATTERN" return addMatch) "only run examples that match given PATTERN"+  , mkOption [] "skip" (Arg "PATTERN" return addSkip) "skip examples that match given PATTERN"+  ]+  where+    setIgnoreConfigFile = set $ \config -> config {configIgnoreConfigFile = True}++formatterOptions :: Monad m => [OptDescr (Result m -> Result m)]+formatterOptions = concat [+    [mkOption "f" "format" (Arg "FORMATTER" readFormatter setFormatter) helpForFormat]+  , mkFlag "color" setColor "colorize the output"+  , mkFlag "diff" setDiff "show colorized diffs"+  , [Option [] ["print-cpu-time"] (NoArg setPrintCpuTime) "include used CPU time in summary"]+  ]+  where+    formatters :: [(String, Formatter)]+    formatters = [+        ("specdoc", specdoc)+      , ("progress", progress)+      , ("failed-examples", failed_examples)+      , ("silent", silent)+      ]++    helpForFormat :: String+    helpForFormat = "use a custom formatter; this can be one of " ++ (formatOrList $ map fst formatters)++    readFormatter :: String -> Maybe Formatter+    readFormatter = (`lookup` formatters)++    setFormatter :: Formatter -> Config -> Config+    setFormatter f c = c {configFormatter = Just f}++    setColor :: Bool -> Config -> Config+    setColor v config = config {configColorMode = if v then ColorAlways else ColorNever}++    setDiff :: Bool -> Config -> Config+    setDiff v config = config {configDiff = v}++    setPrintCpuTime = set $ \config -> config {configPrintCpuTime = True}++smallCheckOptions :: Monad m => [OptDescr (Result m -> Result m)]+smallCheckOptions = [+    mkOption [] "depth" (Arg "N" readMaybe setDepth) "maximum depth of generated test values for SmallCheck properties"+  ]++quickCheckOptions :: Monad m => [OptDescr (Result m -> Result m)]+quickCheckOptions = [+    mkOption "a" "qc-max-success" (Arg "N" readMaybe setMaxSuccess) "maximum number of successful tests before a QuickCheck property succeeds"+  , mkOption "" "qc-max-size" (Arg "N" readMaybe setMaxSize) "size to use for the biggest test cases"+  , mkOption "" "qc-max-discard" (Arg "N" readMaybe setMaxDiscardRatio) "maximum number of discarded tests per successful test before giving up"+  , mkOption [] "seed" (Arg "N" readMaybe setSeed) "used seed for QuickCheck properties"+  ]++runnerOptions :: Monad m => [OptDescr (Result m -> Result m)]+runnerOptions = [+    Option [] ["dry-run"] (NoArg setDryRun) "pretend that everything passed; don't verify anything"+  , Option [] ["fail-fast"] (NoArg setFastFail) "abort on first failure"+  , Option "r" ["rerun"] (NoArg  setRerun) "rerun all examples that failed in the previous test run (only works in combination with --failure-report or in GHCi)"+  , mkOption [] "failure-report" (Arg "FILE" return setFailureReport) "read/write a failure report for use with --rerun"+  , Option [] ["rerun-all-on-success"] (NoArg setRerunAllOnSuccess) "run the whole test suite after a previously failing rerun succeeds for the first time (only works in combination with --rerun)"+  , mkOption "j" "jobs" (Arg "N" readMaxJobs setMaxJobs) "run at most N parallelizable tests simultaneously (default: number of available processors)"+  ]+  where+    readMaxJobs :: String -> Maybe Int+    readMaxJobs s = do+      n <- readMaybe s+      guard $ n > 0+      return n++    setFailureReport :: String -> Config -> Config+    setFailureReport file c = c {configFailureReport = Just file}++    setMaxJobs :: Int -> Config -> Config+    setMaxJobs n c = c {configConcurrentJobs = Just n}++    setDryRun       = set $ \config -> config {configDryRun = True}+    setFastFail     = set $ \config -> config {configFastFail = True}+    setRerun        = set $ \config -> config {configRerun = True}+    setRerunAllOnSuccess = set $ \config -> config {configRerunAllOnSuccess = True}++documentedConfigFileOptions :: Monad m => [(String, [OptDescr (Result m -> Result m)])]+documentedConfigFileOptions = [+    ("RUNNER OPTIONS", runnerOptions)+  , ("FORMATTER OPTIONS", formatterOptions)+  , ("OPTIONS FOR QUICKCHECK", quickCheckOptions)+  , ("OPTIONS FOR SMALLCHECK", smallCheckOptions)+  ]++documentedOptions :: [(String, [OptDescr (Result Maybe -> Result Maybe)])]+documentedOptions = ("OPTIONS", commandLineOptions) : documentedConfigFileOptions++configFileOptions :: Monad m => [OptDescr (Result m -> Result m)]+configFileOptions = (concat . map snd) documentedConfigFileOptions++set :: Monad m => (Config -> Config) -> Either a (m Config) -> Either a (m Config)+set = liftM . liftM++undocumentedOptions :: Monad m => [OptDescr (Result m -> Result m)]+undocumentedOptions = [+    -- for compatibility with test-framework+    mkOption [] "maximum-generated-tests" (Arg "NUMBER" readMaybe setMaxSuccess) "how many automated tests something like QuickCheck should try, by default"++    -- undocumented for now, as we probably want to change this to produce a+    -- standalone HTML report in the future+  , Option []  ["html"]                    (NoArg setHtml)                    "produce HTML output"++  , mkOption "o"  "out"                    (Arg "FILE" return setOutputFile)  "write output to a file instead of STDOUT"++    -- now a noop+  , Option "v" ["verbose"]                 (NoArg id)                         "do not suppress output to stdout when evaluating examples"+  ]+  where+    setHtml = set $ \config -> config {configHtmlOutput = True}++    setOutputFile :: String -> Config -> Config+    setOutputFile file c = c {configOutputFile = Right file}++recognizedOptions :: [OptDescr (Result Maybe -> Result Maybe)]+recognizedOptions = commandLineOptions ++ configFileOptions ++ undocumentedOptions++parseOptions :: Config -> String -> [ConfigFile] -> Maybe EnvVar -> [String] -> Either (ExitCode, String) Config+parseOptions config prog configFiles envVar args = do+      foldM (parseFileOptions prog) config configFiles+  >>= parseEnvVarOptions prog envVar+  >>= parseCommandLineOptions prog args++parseCommandLineOptions :: String -> [String] -> Config -> Either (ExitCode, String) Config+parseCommandLineOptions prog args config = case parse recognizedOptions config args of+  Right Nothing -> Left (ExitSuccess, usage)+  Right (Just c) -> Right c+  Left err -> failure err+  where+    failure err = Left (ExitFailure 1, prog ++ ": " ++ err ++ "\nTry `" ++ prog ++ " --help' for more information.\n")++    usage :: String+    usage = "Usage: " ++ prog ++ " [OPTION]...\n\n"+      ++ (intercalate "\n" $ map (uncurry mkUsageInfo) documentedOptions)++parseFileOptions :: String -> Config -> ConfigFile -> Either (ExitCode, String) Config+parseFileOptions prog config (name, args) =+  parseOtherOptions prog ("in config file " ++ name) args config++parseEnvVarOptions :: String -> (Maybe EnvVar) -> Config -> Either (ExitCode, String) Config+parseEnvVarOptions prog args =+  parseOtherOptions prog ("from environment variable " ++ envVarName) (fromMaybe [] args)++parseOtherOptions :: String -> String -> [String] -> Config -> Either (ExitCode, String) Config+parseOtherOptions prog source args config = case parse configFileOptions config args of+  Right (Identity c) -> Right c+  Left err -> failure err+  where+    failure err = Left (ExitFailure 1, prog ++ ": " ++ message)+      where+        message = unlines $ case lines err of+          [x] -> [x ++ " " ++ source]+          xs -> xs ++ [source]++parse :: Monad m => [OptDescr (Result m -> Result m)] -> Config -> [String] -> Either String (m Config)+parse options config args = case getOpt Permute options args of+  (opts, [], []) -> case foldl' (flip id) (Right $ return config) opts of+    Left (InvalidArgument name value) -> Left ("invalid argument `" ++ value ++ "' for `--" ++ name ++ "'")+    Right x -> Right x+  (_, _, err:_) -> Left (init err)+  (_, arg:_, _) -> Left ("unexpected argument `" ++ arg ++ "'")++ignoreConfigFile :: Config -> [String] -> IO Bool+ignoreConfigFile config args = do+  ignore <- lookupEnv "IGNORE_DOT_HSPEC"+  case ignore of+    Just _ -> return True+    Nothing -> case parse recognizedOptions config args of+      Right (Just c) -> return (configIgnoreConfigFile c)+      _ -> return False
+ hspec-core/src/Test/Hspec/Core/Config/Util.hs view
@@ -0,0 +1,37 @@+module Test.Hspec.Core.Config.Util where++import           System.Console.GetOpt++import           Test.Hspec.Core.Util++modifyHelp :: (String -> String) -> OptDescr a -> OptDescr a+modifyHelp modify (Option s n a help) = Option s n a (modify help)++mkUsageInfo :: String -> [OptDescr a] -> String+mkUsageInfo title = usageInfo title . addLineBreaksForHelp . condenseNoOptions++addLineBreaksForHelp :: [OptDescr a] -> [OptDescr a]+addLineBreaksForHelp options = map (modifyHelp addLineBreaks) options+  where+    withoutHelpWidth = maxLength . usageInfo "" . map removeHelp+    helpWidth = 80 - withoutHelpWidth options++    addLineBreaks = unlines . lineBreaksAt helpWidth++    maxLength = maximum . map length . lines+    removeHelp = modifyHelp (const "")++condenseNoOptions :: [OptDescr a] -> [OptDescr a]+condenseNoOptions options = case options of+  Option "" [optionA] arg help : Option "" [optionB] _ _ : ys | optionB == ("no-" ++ optionA) ->+    Option "" ["[no-]" ++ optionA] arg help : condenseNoOptions ys+  x : xs -> x : condenseNoOptions xs+  [] -> []++formatOrList :: [String] -> String+formatOrList xs = case xs of+  [] -> ""+  x : ys -> (case ys of+    [] -> x+    _ : [] -> x ++ " or "+    _ : _ : _ -> x ++ ", ") ++ formatOrList ys
hspec-core/src/Test/Hspec/Core/Example.hs view
@@ -1,4 +1,9 @@-{-# LANGUAGE CPP, TypeFamilies, FlexibleInstances, TypeSynonymInstances, DeriveDataTypeable #-}+{-# LANGUAGE CPP #-}+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE DeriveDataTypeable #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE TypeSynonymInstances #-} module Test.Hspec.Core.Example (   Example (..) , Params (..)@@ -6,33 +11,30 @@ , ActionWith , Progress , ProgressCallback-, Result (..)+, Result(..)+, ResultStatus (..) , Location (..)-, LocationAccuracy (..) , FailureReason (..) , safeEvaluateExample ) where -import           Data.Maybe (fromMaybe)-import           Data.List (isPrefixOf) import qualified Test.HUnit.Lang as HUnit -#if MIN_VERSION_HUnit(1,4,0) import           Data.CallStack-#endif -import qualified Control.Exception as E+import           Control.Exception import           Control.DeepSeq import           Data.Typeable (Typeable) import qualified Test.QuickCheck as QC import           Test.Hspec.Expectations (Expectation) -import qualified Test.QuickCheck.State as QC+import qualified Test.QuickCheck.State as QC (numSuccessTests, maxSuccessTests) import qualified Test.QuickCheck.Property as QCP  import           Test.Hspec.Core.QuickCheckUtil import           Test.Hspec.Core.Util import           Test.Hspec.Core.Compat+import           Test.Hspec.Core.Example.Location  -- | A type class for examples class Example e where@@ -58,49 +60,49 @@ type ActionWith a = a -> IO ()  -- | The result of running an example-data Result = Success | Pending (Maybe String) | Failure (Maybe Location) FailureReason-  deriving (Eq, Show, Read, Typeable)+data Result = Result {+  resultInfo :: String+, resultStatus :: ResultStatus+} deriving (Show, Typeable) -data FailureReason = NoReason | Reason String | ExpectedButGot (Maybe String) String String-    deriving (Eq, Show, Read, Typeable)+data ResultStatus =+    Success+  | Pending (Maybe Location) (Maybe String)+  | Failure (Maybe Location) FailureReason+  deriving (Show, Typeable) +data FailureReason =+    NoReason+  | Reason String+  | ExpectedButGot (Maybe String) String String+  | Error (Maybe String) SomeException+  deriving (Show, Typeable)+ instance NFData FailureReason where   rnf reason = case reason of     NoReason -> ()     Reason r -> r `deepseq` ()     ExpectedButGot p e a  -> p `deepseq` e `deepseq` a `deepseq` ()--instance E.Exception Result---- | @Location@ is used to represent source locations.-data Location = Location {-  locationFile :: FilePath-, locationLine :: Int-, locationColumn :: Int-, locationAccuracy :: LocationAccuracy-} deriving (Eq, Show, Read)+    Error m e -> m `deepseq` e `seq` () --- | A marker for source locations-data LocationAccuracy =-  -- | The source location is accurate-  ExactLocation |-  -- | The source location was determined on a best-effort basis and my be-  -- wrong or inaccurate-  BestEffort-  deriving (Eq, Show, Read)+instance Exception ResultStatus -safeEvaluateExample :: Example e => e -> Params -> (ActionWith (Arg e) -> IO ()) -> ProgressCallback -> IO (Either E.SomeException Result)+safeEvaluateExample :: Example e => e -> Params -> (ActionWith (Arg e) -> IO ()) -> ProgressCallback -> IO Result safeEvaluateExample example params around progress = do   r <- safeTry $ forceResult <$> evaluateExample example params around progress   return $ case r of-    Left e | Just result <- E.fromException e -> Right result-    Left e | Just hunit <- E.fromException e -> Right (hunitFailureToResult hunit)-    _ -> r+    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+    Right result -> result   where     forceResult :: Result -> Result-    forceResult r = case r of+    forceResult r@(Result info status) = info `deepseq` (forceResultStatus status) `seq` r++    forceResultStatus :: ResultStatus -> ResultStatus+    forceResultStatus r = case r of       Success -> r-      Pending m -> m `deepseq` r+      Pending _ m -> m `deepseq` r       Failure _ m -> m `deepseq` r  instance Example Result where@@ -110,8 +112,8 @@ instance Example (a -> Result) where   type Arg (a -> Result) = a   evaluateExample example _params action _callback = do-    ref <- newIORef Success-    action (writeIORef ref . example)+    ref <- newIORef (Result "" Success)+    action (evaluate . example >=> writeIORef ref)     readIORef ref  instance Example Bool where@@ -121,44 +123,42 @@ instance Example (a -> Bool) where   type Arg (a -> Bool) = a   evaluateExample p _params action _callback = do-    ref <- newIORef Success-    action $ \a -> example a >>= writeIORef ref+    ref <- newIORef (Result "" Success)+    action (evaluate . example >=> writeIORef ref)     readIORef ref     where       example a-        | p a = return Success-        | otherwise = return (Failure Nothing NoReason)+        | p a = Result "" Success+        | otherwise = Result "" $ Failure Nothing NoReason  instance Example Expectation where   type Arg Expectation = ()   evaluateExample e = evaluateExample (\() -> e) -hunitFailureToResult :: HUnit.HUnitFailure -> Result-hunitFailureToResult e = case e of-#if MIN_VERSION_HUnit(1,3,0)+hunitFailureToResult :: Maybe String -> HUnit.HUnitFailure -> ResultStatus+hunitFailureToResult pre e = case e of   HUnit.HUnitFailure mLoc err ->-#if MIN_VERSION_HUnit(1,5,0)       case err of-        HUnit.Reason reason -> Failure location (Reason reason)-        HUnit.ExpectedButGot preface expected actual -> Failure location (ExpectedButGot preface expected actual)-#else-      Failure location (Reason err)-#endif+        HUnit.Reason reason -> Failure location (Reason $ addPre reason)+        HUnit.ExpectedButGot preface expected actual -> Failure location (ExpectedButGot (addPreMaybe preface) expected actual)+          where+            addPreMaybe :: Maybe String -> Maybe String+            addPreMaybe xs = case (pre, xs) of+              (Just x, Just y) -> Just (x ++ "\n" ++ y)+              _ -> pre <|> xs     where       location = case mLoc of         Nothing -> Nothing-#if MIN_VERSION_HUnit(1,4,0)-        Just loc -> Just $ Location (srcLocFile loc) (srcLocStartLine loc) (srcLocStartCol loc) ExactLocation-#else-        Just loc -> Just $ Location (HUnit.locationFile loc) (HUnit.locationLine loc) (HUnit.locationColumn loc) ExactLocation-#endif-#else-  HUnit.HUnitFailure err -> Failure Nothing (Reason err)-#endif+        Just loc -> Just $ Location (srcLocFile loc) (srcLocStartLine loc) (srcLocStartCol loc)+  where+    addPre :: String -> String+    addPre xs = case pre of+      Just x -> x ++ "\n" ++ xs+      Nothing -> xs  instance Example (a -> Expectation) where   type Arg (a -> Expectation) = a-  evaluateExample e _ action _ = action e >> return Success+  evaluateExample e _ action _ = action e >> return (Result "" Success)  instance Example QC.Property where   type Arg QC.Property = ()@@ -168,51 +168,41 @@   type Arg (a -> QC.Property) = a   evaluateExample p c action progressCallback = do     r <- QC.quickCheckWithResult (paramsQuickCheckArgs c) {QC.chatty = False} (QCP.callback qcProgressCallback $ aroundProperty action p)-    return $-      case r of-        QC.Success {}               -> Success-        QC.Failure {QC.output = m}  -> fromMaybe (Failure Nothing . Reason $ sanitizeFailureMessage r) (parsePending m)-        QC.GaveUp {QC.numTests = n} -> Failure Nothing (Reason $ "Gave up after " ++ pluralize n "test" )-        QC.NoExpectedFailure {}     -> Failure Nothing (Reason $ "No expected failure")-#if MIN_VERSION_QuickCheck(2,8,0)-        QC.InsufficientCoverage {}  -> Failure Nothing (Reason $ "Insufficient coverage")-#endif+    return $ fromQuickCheckResult r     where       qcProgressCallback = QCP.PostTest QCP.NotCounterexample $         \st _ -> progressCallback (QC.numSuccessTests st, QC.maxSuccessTests st) -      sanitizeFailureMessage :: QC.Result -> String-      sanitizeFailureMessage r = let m = QC.output r in strip $-#if MIN_VERSION_QuickCheck(2,7,0)-        case QC.theException r of-          Just e -> case E.fromException e :: Maybe (HUnit.HUnitFailure) of-            Just _ -> (addFalsifiable . stripFailed) m-            Nothing -> let numbers = formatNumbers r in-              "uncaught exception: " ++ formatException e ++ " " ++ numbers ++ "\n" ++ case lines m of-                x:xs | x == (exceptionPrefix ++ show e ++ "' " ++ numbers ++ ": ") -> unlines xs-                _ -> m-          Nothing ->-#endif-            (addFalsifiable . stripFailed) m+fromQuickCheckResult :: QC.Result -> Result+fromQuickCheckResult r = case parseQuickCheckResult r of+  QuickCheckResult _ info (QuickCheckOtherFailure err) -> Result info $ Failure Nothing (Reason err)+  QuickCheckResult _ info QuickCheckSuccess -> Result info Success+  QuickCheckResult n info (QuickCheckFailure QCFailure{..}) -> case quickCheckFailureException of+    Just e | Just result <- fromException e -> Result info result+    Just e | Just hunit <- fromException e -> Result info $ hunitFailureToResult (Just hunitAssertion) hunit+    Just e -> failure (uncaughtException e)+    Nothing -> failure falsifiable+    where+      failure = Result info . Failure Nothing . Reason -      addFalsifiable :: String -> String-      addFalsifiable m-        | "(after " `isPrefixOf` m = "Falsifiable " ++ m-        | otherwise = m+      numbers = formatNumbers n quickCheckFailureNumShrinks -      stripFailed :: String -> String-      stripFailed m-        | prefix `isPrefixOf` m = drop n m-        | otherwise = m-        where-          prefix = "*** Failed! "-          n = length prefix+      hunitAssertion :: String+      hunitAssertion = intercalate "\n" [+          "Falsifiable " ++ numbers ++ ":"+        , indent (unlines quickCheckFailureCounterexample)+        ] -      parsePending :: String -> Maybe Result-      parsePending m-        | exceptionPrefix `isPrefixOf` m = (readMaybe . takeWhile (/= '\'') . drop n) m-        | otherwise = Nothing-        where-          n = length exceptionPrefix+      uncaughtException e = intercalate "\n" [+          "uncaught exception: " ++ formatException e+        , numbers+        , indent (unlines quickCheckFailureCounterexample)+        ] -      exceptionPrefix = "*** Failed! Exception: '"+      falsifiable = intercalate "\n" [+          quickCheckFailureReason ++ " " ++ numbers ++ ":"+        , indent (unlines quickCheckFailureCounterexample)+        ]++indent :: String -> String+indent = intercalate "\n" . map ("  " ++) . lines
+ hspec-core/src/Test/Hspec/Core/Example/Location.hs view
@@ -0,0 +1,88 @@+{-# LANGUAGE CPP #-}+module Test.Hspec.Core.Example.Location (+  Location(..)+, extractLocation++-- for testing+, parseCallStack+, parseLocation+, parseSourceSpan+) where++import           Prelude ()+import           Test.Hspec.Core.Compat++import           Control.Exception+import           Data.List+import           Data.Char+import           Data.Maybe+import           GHC.IO.Exception++-- | @Location@ is used to represent source locations.+data Location = Location {+  locationFile :: FilePath+, locationLine :: Int+, locationColumn :: Int+} deriving (Eq, Show, Read)++extractLocation :: SomeException -> Maybe Location+extractLocation e = locationFromErrorCall e <|> locationFromPatternMatchFail e <|> locationFromIOException e++locationFromErrorCall :: SomeException -> Maybe Location+locationFromErrorCall e = case fromException e of+#if MIN_VERSION_base(4,9,0)+  Just (ErrorCallWithLocation err loc) ->+    parseCallStack loc <|>+#else+  Just (ErrorCall err) ->+#endif+    fromPatternMatchFailureInDoExpression err+  Nothing -> Nothing++locationFromPatternMatchFail :: SomeException -> Maybe Location+locationFromPatternMatchFail e = case fromException e of+  Just (PatternMatchFail s) -> listToMaybe (words s) >>= parseSourceSpan+  Nothing -> Nothing++locationFromIOException :: SomeException -> Maybe Location+locationFromIOException e = case fromException e of+  Just (IOError {ioe_type = UserError, ioe_description = xs}) -> fromPatternMatchFailureInDoExpression xs+  Just _ -> Nothing+  Nothing -> Nothing++fromPatternMatchFailureInDoExpression :: String -> Maybe Location+fromPatternMatchFailureInDoExpression input =+  stripPrefix "Pattern match failure in do expression at " input >>= parseSourceSpan++parseCallStack :: String -> Maybe Location+parseCallStack input = case reverse (lines input) of+  [] -> Nothing+  line : _ -> findLocation line+  where+    findLocation xs = case xs of+      [] -> Nothing+      _ : ys -> case stripPrefix prefix xs of+        Just zs -> parseLocation (takeWhile (not . isSpace) zs)+        Nothing -> findLocation ys+    prefix = ", called at "++parseLocation :: String -> Maybe Location+parseLocation input = case fmap breakColon (breakColon input) of+  (file, (line, column)) -> Location file <$> readMaybe line <*> readMaybe column++parseSourceSpan :: String -> Maybe Location+parseSourceSpan input = case breakColon input of+  (file, xs) -> (uncurry $ Location file) <$> (tuple <|> colonSeparated)+    where+      lineAndColumn :: String+      lineAndColumn = takeWhile (/= '-') xs++      tuple :: Maybe (Int, Int)+      tuple = readMaybe lineAndColumn++      colonSeparated :: Maybe (Int, Int)+      colonSeparated = case breakColon lineAndColumn of+        (l, c) -> (,) <$> readMaybe l <*> readMaybe c++breakColon :: String -> (String, String)+breakColon = fmap (drop 1) . break (== ':')
hspec-core/src/Test/Hspec/Core/FailureReport.hs view
@@ -5,16 +5,17 @@ , readFailureReport ) where +import           Prelude ()+import           Test.Hspec.Core.Compat+ #ifndef __GHCJS__ import           System.SetEnv import           Test.Hspec.Core.Util (safeTry) #endif-import           Control.Monad import           System.IO import           System.Directory-import           Test.Hspec.Core.Compat import           Test.Hspec.Core.Util (Path)-import           Test.Hspec.Core.Options (Config(..))+import           Test.Hspec.Core.Config.Options (Config(..))  data FailureReport = FailureReport {   failureReportSeed :: Integer
+ hspec-core/src/Test/Hspec/Core/Format.hs view
@@ -0,0 +1,36 @@+{-# LANGUAGE RankNTypes #-}+module Test.Hspec.Core.Format (+  Format(..)+, Progress+, Path+, Location(..)+, Seconds(..)+, Item(..)+, Result(..)+, FailureReason(..)+) where++import           Test.Hspec.Core.Spec (Progress, Location(..))+import           Test.Hspec.Core.Example (FailureReason(..))+import           Test.Hspec.Core.Util (Path)+import           Test.Hspec.Core.Clock++data Item = Item {+  itemLocation :: Maybe Location+, itemDuration :: Seconds+, itemInfo :: String+, itemResult :: Result+}++data Result =+    Success+  | Pending (Maybe String)+  | Failure FailureReason++data Format m = Format {+  formatRun :: forall a. m a -> IO a+, formatGroupStarted :: Path -> m ()+, formatGroupDone :: Path -> m ()+, formatProgress :: Path -> Progress -> m ()+, formatItem :: Path -> Item -> m ()+}
hspec-core/src/Test/Hspec/Core/Formatters.hs view
@@ -1,3 +1,4 @@+{-# LANGUAGE CPP #-} -- | -- Stability: experimental --@@ -32,12 +33,14 @@ , getFailMessages , usedSeed +, Seconds(..) , getCPUTime , getRealTime --- ** Appending to the gerenated report+-- ** Appending to the generated report , write , writeLine+, writeTransient  -- ** Dealing with colors , withInfoColor@@ -57,10 +60,8 @@  import           Data.Maybe import           Test.Hspec.Core.Util-import           Test.Hspec.Core.Spec (Location(..), LocationAccuracy(..))+import           Test.Hspec.Core.Spec (Location(..)) import           Text.Printf-import           Control.Monad (when, unless)-import           System.IO (hPutStr, hFlush)  -- We use an explicit import list for "Test.Hspec.Formatters.Internal", to make -- sure, that we only use the public API to implement formatters.@@ -86,6 +87,7 @@    , write   , writeLine+  , writeTransient    , withInfoColor   , withSuccessColor@@ -96,6 +98,8 @@   , missingChunk   ) +import           Test.Hspec.Core.Clock (Seconds(..))+ import           Test.Hspec.Core.Formatters.Diff  silent :: Formatter@@ -103,10 +107,10 @@   headerFormatter     = return () , exampleGroupStarted = \_ _ -> return () , exampleGroupDone    = return ()-, exampleProgress     = \_ _ _ -> return ()-, exampleSucceeded    = \_ -> return ()-, exampleFailed       = \_ _ -> return ()-, examplePending      = \_ _  -> return ()+, exampleProgress     = \_ _ -> return ()+, exampleSucceeded    = \ _ _ -> return ()+, exampleFailed       = \_ _ _ -> return ()+, examplePending      = \_ _ _ -> return () , failedFormatter     = return () , footerFormatter     = return () }@@ -120,19 +124,25 @@ , exampleGroupStarted = \nesting name -> do     writeLine (indentationFor nesting ++ name) -, exampleProgress = \h _ p -> do-    hPutStr h (formatProgress p)-    hFlush h+, exampleProgress = \_ p -> do+    writeTransient (formatProgress p) -, exampleSucceeded = \(nesting, requirement) -> withSuccessColor $ do+, exampleSucceeded = \(nesting, requirement) info -> withSuccessColor $ do     writeLine $ indentationFor nesting ++ requirement+    forM_ (lines info) $ \ s ->+      writeLine $ indentationFor ("" : nesting) ++ s -, exampleFailed = \(nesting, requirement) _ -> withFailColor $ do+, exampleFailed = \(nesting, requirement) info _ -> withFailColor $ do     n <- getFailCount     writeLine $ indentationFor nesting ++ requirement ++ " FAILED [" ++ show n ++ "]"+    forM_ (lines info) $ \ s ->+      writeLine $ indentationFor ("" : nesting) ++ s -, examplePending = \(nesting, requirement) reason -> withPendingColor $ do-    writeLine $ indentationFor nesting ++ requirement ++ "\n     # PENDING: " ++ fromMaybe "No reason given" reason+, examplePending = \(nesting, requirement) info reason -> withPendingColor $ do+    writeLine $ indentationFor nesting ++ requirement+    forM_ (lines info) $ \ s ->+      writeLine $ indentationFor ("" : nesting) ++ s+    writeLine $ indentationFor ("" : nesting) ++ "# PENDING: " ++ fromMaybe "No reason given" reason  , failedFormatter = defaultFailedFormatter @@ -140,15 +150,15 @@ } where     indentationFor nesting = replicate (length nesting * 2) ' '     formatProgress (current, total)-      | total == 0 = show current ++ "\r"-      | otherwise  = show current ++ "/" ++ show total ++ "\r"+      | total == 0 = show current+      | otherwise  = show current ++ "/" ++ show total   progress :: Formatter progress = silent {-  exampleSucceeded = \_   -> withSuccessColor $ write "."-, exampleFailed    = \_ _ -> withFailColor    $ write "F"-, examplePending   = \_ _ -> withPendingColor $ write "."+  exampleSucceeded = \_ _ -> withSuccessColor $ write "."+, exampleFailed    = \_ _ _ -> withFailColor    $ write "F"+, examplePending   = \_ _ _ -> withPendingColor $ write "." , failedFormatter  = defaultFailedFormatter , footerFormatter  = defaultFooter }@@ -174,19 +184,19 @@       formatFailure x       writeLine "" -    when (hasBestEffortLocations failures) $ do-      withInfoColor $ writeLine "Source locations marked with \"best-effort\" are calculated heuristically and may be incorrect."+#if __GLASGOW_HASKELL__ == 800+    withFailColor $ do+      writeLine "WARNING:"+      writeLine "  Your version of GHC is affected by https://ghc.haskell.org/trac/ghc/ticket/13285."+      writeLine "  Source locations may not work as expected."       writeLine ""+      writeLine "  Please consider upgrading GHC!"+      writeLine ""+#endif      write "Randomized with seed " >> usedSeed >>= writeLine . show     writeLine ""   where-    hasBestEffortLocations :: [FailureRecord] -> Bool-    hasBestEffortLocations = any p-      where-        p :: FailureRecord -> Bool-        p failure = (locationAccuracy <$> failureRecordLocation failure) == Just BestEffort-     formatFailure :: (Int, FailureRecord) -> FormatM ()     formatFailure (n, FailureRecord mLoc path reason) = do       forM_ mLoc $ \loc -> do@@ -194,10 +204,9 @@       write ("  " ++ show n ++ ") ")       writeLine (formatRequirement path)       case reason of-        Left e -> withFailColor . indent $ (("uncaught exception: " ++) . formatException) e-        Right NoReason -> return ()-        Right (Reason err) -> withFailColor $ indent err-        Right (ExpectedButGot preface expected actual) -> do+        NoReason -> return ()+        Reason err -> withFailColor $ indent err+        ExpectedButGot preface expected actual -> do           mapM_ indent preface            let chunks = diff expected actual@@ -219,19 +228,16 @@             indented output text = case break (== '\n') text of               (xs, "") -> output xs               (xs, _ : ys) -> output (xs ++ "\n") >> write (indentation ++ "          ") >> indented output ys+        Error _ e -> withFailColor . indent $ (("uncaught exception: " ++) . formatException) e++      writeLine ""+      writeLine ("  To rerun use: --match " ++ show (joinPath path))       where         indentation = "       "         indent message = do           forM_ (lines message) $ \line -> do             writeLine (indentation ++ line)-        formatLoc (Location file line _column accuracy) = "  " ++ file ++ ":" ++ show line ++ ":" ++ message-          where-            message = case accuracy of-              ExactLocation -> " " -- NOTE: Vim's default 'errorformat'-                                   -- requires a non-empty message.  This is-                                   -- why we use a single space as message-                                   -- here.-              BestEffort -> " (best-effort)"+        formatLoc (Location file line column) = "  " ++ file ++ ":" ++ show line ++ ":" ++ show column ++ ": "  defaultFooter :: FormatM () defaultFooter = do
hspec-core/src/Test/Hspec/Core/Formatters/Diff.hs view
@@ -1,4 +1,5 @@ {-# LANGUAGE CPP #-}+{-# LANGUAGE ViewPatterns #-} module Test.Hspec.Core.Formatters.Diff (   Diff (..) , diff@@ -8,17 +9,21 @@ #endif ) where +import           Prelude ()+import           Test.Hspec.Core.Compat+ import           Data.Char+import           Data.List (stripPrefix) import           Data.Algorithm.Diff  diff :: String -> String -> [Diff String] diff expected actual = map (fmap concat) $ getGroupedDiff (partition expected) (partition actual)  partition :: String -> [String]-partition = mergeBackslashes . breakList isAlphaNum+partition = filter (not . null) . mergeBackslashes . breakList isAlphaNum   where     mergeBackslashes xs = case xs of-      ['\\'] : (y : ys) : zs -> ['\\', y] : ys : mergeBackslashes zs+      ['\\'] : (splitEscape -> Just (escape, ys)) : zs -> ("\\" ++ escape) : ys : mergeBackslashes zs       z : zs -> z : mergeBackslashes zs       [] -> [] @@ -31,3 +36,55 @@     cons x       | null x = id       | otherwise = (x :)++splitEscape :: String -> Maybe (String, String)+splitEscape xs = splitNumericEscape xs <|> (msum $ map split escapes)+  where+    split :: String -> Maybe (String, String)+    split escape = (,) escape <$> stripPrefix escape xs++splitNumericEscape :: String -> Maybe (String, String)+splitNumericEscape xs = case span isDigit xs of+  ("", _) -> Nothing+  r -> Just r++escapes :: [String]+escapes = [+    "ACK"+  , "CAN"+  , "DC1"+  , "DC2"+  , "DC3"+  , "DC4"+  , "DEL"+  , "DLE"+  , "ENQ"+  , "EOT"+  , "ESC"+  , "ETB"+  , "ETX"+  , "NAK"+  , "NUL"+  , "SOH"+  , "STX"+  , "SUB"+  , "SYN"+  , "EM"+  , "FS"+  , "GS"+  , "RS"+  , "SI"+  , "SO"+  , "US"+  , "a"+  , "b"+  , "f"+  , "n"+  , "r"+  , "t"+  , "v"+  , "&"+  , "'"+  , "\""+  , "\\"+  ]
hspec-core/src/Test/Hspec/Core/Formatters/Internal.hs view
@@ -1,13 +1,14 @@ {-# LANGUAGE GeneralizedNewtypeDeriving #-} module Test.Hspec.Core.Formatters.Internal (   FormatM+, FormatConfig(..) , runFormatM , interpret , increaseSuccessCount , increasePendingCount-, increaseFailCount , addFailMessage , finally_+, formatterToFormat ) where  import           Prelude ()@@ -15,31 +16,54 @@  import qualified System.IO as IO import           System.IO (Handle)-import           Control.Monad-import           Control.Exception (SomeException, AsyncException(..), bracket_, try, throwIO)+import           Control.Exception (AsyncException(..), bracket_, try, throwIO) import           System.Console.ANSI-import           Control.Monad.Trans.State hiding (gets, modify)+import           Control.Monad.Trans.State hiding (state, gets, modify) import           Control.Monad.IO.Class+import           Data.Char (isSpace) import qualified System.CPUTime as CPUTime-import           Data.Time.Clock.POSIX (POSIXTime, getPOSIXTime) -import           Test.Hspec.Core.Util (Path)-import           Test.Hspec.Core.Spec (Location)-import           Test.Hspec.Core.Example (FailureReason(..))- import qualified Test.Hspec.Core.Formatters.Monad as M import           Test.Hspec.Core.Formatters.Monad (Environment(..), interpretWith, FailureRecord(..))+import           Test.Hspec.Core.Format+import           Test.Hspec.Core.Clock +formatterToFormat :: M.Formatter -> FormatConfig -> Format FormatM+formatterToFormat formatter config = Format {+  formatRun = \action -> runFormatM config $ do+    interpret (M.headerFormatter formatter)+    a <- action `finally_` interpret (M.failedFormatter formatter)+    interpret (M.footerFormatter formatter)+    return a+, formatGroupStarted = \ (nesting, name) -> interpret $ M.exampleGroupStarted formatter nesting name+, formatGroupDone = \ _ -> interpret (M.exampleGroupDone formatter)+, formatProgress = \ path progress -> when useColor $ do+    interpret $ M.exampleProgress formatter path progress+, formatItem = \ path (Item loc _duration info result) -> do+    clearTransientOutput+    case result of+      Success -> do+        increaseSuccessCount+        interpret $ M.exampleSucceeded formatter path info+      Pending reason -> do+        increasePendingCount+        interpret $ M.examplePending formatter path info reason+      Failure err -> do+        addFailMessage loc path err+        interpret $ M.exampleFailed formatter path info err+} where+    useColor = formatConfigUseColor config+ interpret :: M.FormatM a -> FormatM a interpret = interpretWith Environment {   environmentGetSuccessCount = getSuccessCount , environmentGetPendingCount = getPendingCount-, environmentGetFailCount = getFailCount , environmentGetFailMessages = getFailMessages , environmentUsedSeed = usedSeed , environmentGetCPUTime = getCPUTime , environmentGetRealTime = getRealTime , environmentWrite = write+, environmentWriteTransient = writeTransient , environmentWithFailColor = withFailColor , environmentWithSuccessColor = withSuccessColor , environmentWithPendingColor = withPendingColor@@ -59,72 +83,89 @@ modify f = FormatM $ do   get >>= liftIO . (`modifyIORef'` f) +data FormatConfig = FormatConfig {+  formatConfigHandle :: Handle+, formatConfigUseColor :: Bool+, formatConfigUseDiff :: Bool+, formatConfigHtmlOutput :: Bool+, formatConfigPrintCpuTime :: Bool+, formatConfigUsedSeed :: Integer+} deriving (Eq, Show)+ data FormatterState = FormatterState {-  stateHandle     :: Handle-, stateUseColor   :: Bool-, stateUseDiff    :: Bool-, produceHTML     :: Bool-, successCount    :: Int-, pendingCount    :: Int-, failCount       :: Int-, failMessages    :: [FailureRecord]-, stateUsedSeed   :: Integer-, cpuStartTime    :: Maybe Integer-, startTime       :: POSIXTime+  stateSuccessCount    :: Int+, statePendingCount    :: Int+, stateFailMessages    :: [FailureRecord]+, stateCpuStartTime    :: Maybe Integer+, stateStartTime       :: Seconds+, stateTransientOutput :: String+, stateConfig :: FormatConfig } +getConfig :: (FormatConfig -> a) -> FormatM a+getConfig f = gets (f . stateConfig)++getHandle :: FormatM Handle+getHandle = getConfig formatConfigHandle+ -- | The random seed that is used for QuickCheck. usedSeed :: FormatM Integer-usedSeed = gets stateUsedSeed+usedSeed = getConfig formatConfigUsedSeed  -- NOTE: We use an IORef here, so that the state persists when UserInterrupt is -- thrown. newtype FormatM a = FormatM (StateT (IORef FormatterState) IO a)   deriving (Functor, Applicative, Monad, MonadIO) -runFormatM :: Bool -> Bool -> Bool -> Bool -> Integer -> Handle -> FormatM a -> IO a-runFormatM useColor useDiff produceHTML_ printCpuTime seed handle (FormatM action) = do-  time <- getPOSIXTime-  cpuTime <- if printCpuTime then Just <$> CPUTime.getCPUTime else pure Nothing-  st <- newIORef (FormatterState handle useColor useDiff produceHTML_ 0 0 0 [] seed cpuTime time)+runFormatM :: FormatConfig -> FormatM a -> IO a+runFormatM config (FormatM action) = 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  -- | Increase the counter for successful examples increaseSuccessCount :: FormatM ()-increaseSuccessCount = modify $ \s -> s {successCount = succ $ successCount s}+increaseSuccessCount = modify $ \s -> s {stateSuccessCount = succ $ stateSuccessCount s}  -- | Increase the counter for pending examples increasePendingCount :: FormatM ()-increasePendingCount = modify $ \s -> s {pendingCount = succ $ pendingCount s}---- | Increase the counter for failed examples-increaseFailCount :: FormatM ()-increaseFailCount = modify $ \s -> s {failCount = succ $ failCount s}+increasePendingCount = modify $ \s -> s {statePendingCount = succ $ statePendingCount s}  -- | Get the number of successful examples encountered so far. getSuccessCount :: FormatM Int-getSuccessCount = gets successCount+getSuccessCount = gets stateSuccessCount  -- | Get the number of pending examples encountered so far. getPendingCount :: FormatM Int-getPendingCount = gets pendingCount---- | Get the number of failed examples encountered so far.-getFailCount :: FormatM Int-getFailCount = gets failCount+getPendingCount = gets statePendingCount  -- | Append to the list of accumulated failure messages.-addFailMessage :: Maybe Location -> Path -> Either SomeException FailureReason -> FormatM ()-addFailMessage loc p m = modify $ \s -> s {failMessages = FailureRecord loc p m : failMessages s}+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 failMessages+getFailMessages = reverse `fmap` gets stateFailMessages +writeTransient :: String -> FormatM ()+writeTransient s = do+  write ("\r" ++ s)+  modify $ \ state -> state {stateTransientOutput = s}+  h <- getHandle+  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 = ""}+ -- | Append some output to the report. write :: String -> FormatM () write s = do-  h <- gets stateHandle+  h <- getHandle   liftIO $ IO.hPutStr h s  -- | Set output color to red, run given action, and finally restore the default@@ -150,16 +191,16 @@ -- | Set a color, run an action, and finally reset colors. withColor :: SGR -> String -> FormatM a -> FormatM a withColor color cls action = do-  r <- gets produceHTML-  (if r then htmlSpan cls else withColor_ color) action+  produceHTML <- getConfig formatConfigHtmlOutput+  (if produceHTML then htmlSpan cls else withColor_ color) action  htmlSpan :: String -> FormatM a -> FormatM a htmlSpan cls action = write ("<span class=\"" ++ cls ++ "\">") *> action <* write "</span>"  withColor_ :: SGR -> FormatM a -> FormatM a withColor_ color (FormatM action) = do-  useColor <- gets stateUseColor-  h        <- gets stateHandle+  useColor <- getConfig formatConfigUseColor+  h <- getHandle    FormatM . StateT $ \st -> do     bracket_@@ -176,45 +217,61 @@ -- | Output given chunk in red. extraChunk :: String -> FormatM () extraChunk s = do-  useDiff <- gets stateUseDiff+  useDiff <- getConfig formatConfigUseDiff   case useDiff of-    True -> withFailColor $ write s+    True -> extra s     False -> write s+  where+    extra :: String -> FormatM ()+    extra = diffColorize Red "hspec-failure"  -- | Output given chunk in green. missingChunk :: String -> FormatM () missingChunk s = do-  useDiff <- gets stateUseDiff+  useDiff <- getConfig formatConfigUseDiff   case useDiff of-    True -> withSuccessColor $ write s+    True -> missing s     False -> write s+  where+    missing :: String-> FormatM ()+    missing = diffColorize Green "hspec-success" +diffColorize :: Color -> String -> String-> FormatM ()+diffColorize color cls s = withColor (SetColor layer Dull color) cls $ do+  write s+  where+    layer+      | all isSpace s = Background+      | otherwise = Foreground+ -- | -- @finally_ actionA actionB@ runs @actionA@ and then @actionB@.  @actionB@ is -- run even when a `UserInterrupt` occurs during @actionA@.-finally_ :: FormatM () -> FormatM () -> FormatM ()+finally_ :: FormatM a -> FormatM () -> FormatM a finally_ (FormatM actionA) (FormatM actionB) = FormatM . StateT $ \st -> do-  r <- try (execStateT actionA st)+  r <- try (runStateT actionA st)   case r of     Left e -> do       when (e == UserInterrupt) $         runStateT actionB st >> return ()       throwIO e-    Right st_ -> do-      runStateT actionB st_+    Right (a, st_) -> do+      runStateT actionB st_ >>= return . replaceValue a+  where+    replaceValue a (_, st) = (a, st)  -- | Get the used CPU time since the test run has been started.-getCPUTime :: FormatM (Maybe Double)+getCPUTime :: FormatM (Maybe Seconds) getCPUTime = do   t1  <- liftIO CPUTime.getCPUTime-  mt0 <- gets cpuStartTime+  mt0 <- gets stateCpuStartTime   return $ toSeconds <$> ((-) <$> pure t1 <*> mt0)   where-    toSeconds x = fromIntegral x / (10.0 ^ (12 :: Integer))+    toSeconds x = Seconds (fromIntegral x / (10.0 ^ (12 :: Integer)))  -- | Get the passed real time since the test run has been started.-getRealTime :: FormatM Double+getRealTime :: FormatM Seconds getRealTime = do-  t1 <- liftIO getPOSIXTime-  t0 <- gets startTime-  return (realToFrac $ t1 - t0)+  t1 <- liftIO getMonotonicTime+  t0 <- gets stateStartTime+  return (t1 - t0)
hspec-core/src/Test/Hspec/Core/Formatters/Monad.hs view
@@ -23,6 +23,7 @@  , write , writeLine+, writeTransient  , withInfoColor , withSuccessColor@@ -39,63 +40,59 @@ import           Prelude () import           Test.Hspec.Core.Compat -import           System.IO (Handle)-import           Control.Exception import           Control.Monad.IO.Class  import           Test.Hspec.Core.Formatters.Free- import           Test.Hspec.Core.Example (FailureReason(..)) import           Test.Hspec.Core.Util (Path) import           Test.Hspec.Core.Spec (Progress, Location)+import           Test.Hspec.Core.Clock  data Formatter = Formatter {    headerFormatter :: FormatM ()  -- | evaluated before each test group------ The given number indicates the position within the parent group. , exampleGroupStarted :: [String] -> String -> FormatM () -, exampleGroupDone    :: FormatM ()+, exampleGroupDone :: FormatM ()  -- | used to notify the progress of the currently evaluated example -- -- /Note/: This is only called when interactive/color mode.-, exampleProgress     :: Handle -> Path -> Progress -> IO ()+, exampleProgress :: Path -> Progress -> FormatM ()  -- | evaluated after each successful example-, exampleSucceeded    :: Path -> FormatM ()+, exampleSucceeded :: Path -> String -> FormatM ()  -- | evaluated after each failed example-, exampleFailed       :: Path -> Either SomeException FailureReason -> FormatM ()+, exampleFailed :: Path -> String -> FailureReason -> FormatM ()  -- | evaluated after each pending example-, examplePending      :: Path -> Maybe String -> FormatM ()+, examplePending :: Path -> String -> Maybe String -> FormatM ()  -- | evaluated after a test run-, failedFormatter     :: FormatM ()+, failedFormatter :: FormatM ()  -- | evaluated after `failuresFormatter`-, footerFormatter     :: FormatM ()+, footerFormatter :: FormatM () }  data FailureRecord = FailureRecord {   failureRecordLocation :: Maybe Location , failureRecordPath     :: Path-, failureRecordMessage  :: Either SomeException FailureReason+, failureRecordMessage  :: FailureReason }  data FormatF next =     GetSuccessCount (Int -> next)   | GetPendingCount (Int -> next)-  | GetFailCount (Int -> next)   | GetFailMessages ([FailureRecord] -> next)   | UsedSeed (Integer -> next)-  | GetCPUTime (Maybe Double -> next)-  | GetRealTime (Double -> next)+  | GetCPUTime (Maybe Seconds -> next)+  | GetRealTime (Seconds -> next)   | Write String next+  | WriteTransient String next   | forall a. WithFailColor (FormatM a) (a -> next)   | forall a. WithSuccessColor (FormatM a) (a -> next)   | forall a. WithPendingColor (FormatM a) (a -> next)@@ -108,12 +105,12 @@   fmap f x = case x of     GetSuccessCount next -> GetSuccessCount (fmap f next)     GetPendingCount next -> GetPendingCount (fmap f next)-    GetFailCount next -> GetFailCount (fmap f next)     GetFailMessages next -> GetFailMessages (fmap f next)     UsedSeed next -> UsedSeed (fmap f next)     GetCPUTime next -> GetCPUTime (fmap f next)     GetRealTime next -> GetRealTime (fmap f next)     Write s next -> Write s (f next)+    WriteTransient s next -> WriteTransient s (f next)     WithFailColor action next -> WithFailColor action (fmap f next)     WithSuccessColor action next -> WithSuccessColor action (fmap f next)     WithPendingColor action next -> WithPendingColor action (fmap f next)@@ -130,12 +127,12 @@ data Environment m = Environment {   environmentGetSuccessCount :: m Int , environmentGetPendingCount :: m Int-, environmentGetFailCount :: m Int , environmentGetFailMessages :: m [FailureRecord] , environmentUsedSeed :: m Integer-, environmentGetCPUTime :: m (Maybe Double)-, environmentGetRealTime :: m Double+, environmentGetCPUTime :: m (Maybe Seconds)+, environmentGetRealTime :: m Seconds , environmentWrite :: String -> m ()+, environmentWriteTransient :: String -> m () , environmentWithFailColor :: forall a. m a -> m a , environmentWithSuccessColor :: forall a. m a -> m a , environmentWithPendingColor :: forall a. m a -> m a@@ -154,12 +151,12 @@       Free action -> case action of         GetSuccessCount next -> environmentGetSuccessCount >>= go . next         GetPendingCount next -> environmentGetPendingCount >>= go . next-        GetFailCount next -> environmentGetFailCount >>= go . next         GetFailMessages next -> environmentGetFailMessages >>= go . next         UsedSeed next -> environmentUsedSeed >>= go . next         GetCPUTime next -> environmentGetCPUTime >>= go . next         GetRealTime next -> environmentGetRealTime >>= go . next         Write s next -> environmentWrite s >> go next+        WriteTransient s next -> environmentWriteTransient s >> go next         WithFailColor inner next -> environmentWithFailColor (go inner) >>= go . next         WithSuccessColor inner next -> environmentWithSuccessColor (go inner) >>= go . next         WithPendingColor inner next -> environmentWithPendingColor (go inner) >>= go . next@@ -178,7 +175,7 @@  -- | Get the number of failed examples encountered so far. getFailCount :: FormatM Int-getFailCount = liftF (GetFailCount id)+getFailCount = length <$> getFailMessages  -- | Get the total number of examples encountered so far. getTotalCount :: FormatM Int@@ -193,11 +190,11 @@ usedSeed = liftF (UsedSeed id)  -- | Get the used CPU time since the test run has been started.-getCPUTime :: FormatM (Maybe Double)+getCPUTime :: FormatM (Maybe Seconds) getCPUTime = liftF (GetCPUTime id)  -- | Get the passed real time since the test run has been started.-getRealTime :: FormatM Double+getRealTime :: FormatM Seconds getRealTime = liftF (GetRealTime id)  -- | Append some output to the report.@@ -207,6 +204,9 @@ -- | The same as `write`, but adds a newline character. writeLine :: String -> FormatM () writeLine s = write s >> write "\n"++writeTransient :: String -> FormatM ()+writeTransient s = liftF (WriteTransient s ())  -- | Set output color to red, run given action, and finally restore the default -- color.
hspec-core/src/Test/Hspec/Core/Hooks.hs view
@@ -57,7 +57,7 @@       a <- try action       return (either Failed Memoized a, a)     Memoized a -> return (ma, Right a)-    Failed _ -> throwIO (Pending (Just "exception in beforeAll-hook (see previous failure)"))+    Failed _ -> throwIO (Pending Nothing (Just "exception in beforeAll-hook (see previous failure)"))   either throwIO return result  -- | Run a custom action after every spec item.
− hspec-core/src/Test/Hspec/Core/Options.hs
@@ -1,283 +0,0 @@-module Test.Hspec.Core.Options (-  Config(..)-, ColorMode (..)-, defaultConfig-, filterOr-, parseOptions-, ConfigFile-, ignoreConfigFile-, envVarName-) where--import           Prelude ()-import           Control.Monad-import           Test.Hspec.Core.Compat--import           System.IO-import           System.Exit-import           System.Console.GetOpt--import           Test.Hspec.Core.Formatters-import           Test.Hspec.Core.Util-import           Test.Hspec.Core.Example (Params(..), defaultParams)-import           Data.Functor.Identity-import           Data.Maybe--type ConfigFile = (FilePath, [String])--type EnvVar = [String]--envVarName :: String-envVarName = "HSPEC_OPTIONS"--data Config = Config {-  configIgnoreConfigFile :: Bool-, configDryRun :: Bool-, configPrintCpuTime :: Bool-, configFastFail :: Bool-, configFailureReport :: Maybe FilePath-, configRerun :: Bool-, configRerunAllOnSuccess :: Bool---- |--- A predicate that is used to filter the spec before it is run.  Only examples--- that satisfy the predicate are run.-, configFilterPredicate :: Maybe (Path -> Bool)-, configSkipPredicate :: Maybe (Path -> Bool)-, configQuickCheckSeed :: Maybe Integer-, configQuickCheckMaxSuccess :: Maybe Int-, configQuickCheckMaxDiscardRatio :: Maybe Int-, configQuickCheckMaxSize :: Maybe Int-, configSmallCheckDepth :: Int-, configColorMode :: ColorMode-, configDiff :: Bool-, configFormatter :: Maybe Formatter-, configHtmlOutput :: Bool-, configOutputFile :: Either Handle FilePath-, configConcurrentJobs :: Maybe Int-}--defaultConfig :: Config-defaultConfig = Config {-  configIgnoreConfigFile = False-, configDryRun = False-, configPrintCpuTime = False-, configFastFail = False-, configFailureReport = Nothing-, configRerun = False-, configRerunAllOnSuccess = False-, configFilterPredicate = Nothing-, configSkipPredicate = Nothing-, configQuickCheckSeed = Nothing-, configQuickCheckMaxSuccess = Nothing-, configQuickCheckMaxDiscardRatio = Nothing-, configQuickCheckMaxSize = Nothing-, configSmallCheckDepth = paramsSmallCheckDepth defaultParams-, configColorMode = ColorAuto-, configDiff = True-, configFormatter = Nothing-, configHtmlOutput = False-, configOutputFile = Left stdout-, configConcurrentJobs = Nothing-}--filterOr :: Maybe (Path -> Bool) -> Maybe (Path -> Bool) -> Maybe (Path -> Bool)-filterOr p1_ p2_ = case (p1_, p2_) of-  (Just p1, Just p2) -> Just $ \path -> p1 path || p2 path-  _ -> p1_ <|> p2_--addMatch :: String -> Config -> Config-addMatch s c = c {configFilterPredicate = Just (filterPredicate s) `filterOr` configFilterPredicate c}--addSkip :: String -> Config -> Config-addSkip s c = c {configSkipPredicate = Just (filterPredicate s) `filterOr` configSkipPredicate c}--setDepth :: Int -> Config -> Config-setDepth n c = c {configSmallCheckDepth = n}--setMaxSuccess :: Int -> Config -> Config-setMaxSuccess n c = c {configQuickCheckMaxSuccess = Just n}--setMaxSize :: Int -> Config -> Config-setMaxSize n c = c {configQuickCheckMaxSize = Just n}--setMaxDiscardRatio :: Int -> Config -> Config-setMaxDiscardRatio n c = c {configQuickCheckMaxDiscardRatio = Just n}--setSeed :: Integer -> Config -> Config-setSeed n c = c {configQuickCheckSeed = Just n}--data ColorMode = ColorAuto | ColorNever | ColorAlways-  deriving (Eq, Show)--formatters :: [(String, Formatter)]-formatters = [-    ("specdoc", specdoc)-  , ("progress", progress)-  , ("failed-examples", failed_examples)-  , ("silent", silent)-  ]--formatHelp :: String-formatHelp = unlines (addLineBreaks "use a custom formatter; this can be one of:" ++ map (("   " ++) . fst) formatters)--type Result m = Either InvalidArgument (m Config)--data InvalidArgument = InvalidArgument String String--data Arg a = Arg {-  _argumentName   :: String-, _argumentParser :: String -> Maybe a-, _argumentSetter :: a -> Config -> Config-}--mkOption :: Monad m => [Char] -> String -> Arg a -> String -> OptDescr (Result m -> Result m)-mkOption shortcut name (Arg argName parser setter) help = Option shortcut [name] (ReqArg arg argName) help-  where-    arg input x = x >>= \c -> case parser input of-      Just n -> Right (setter n `liftM` c)-      Nothing -> Left (InvalidArgument name input)--addLineBreaks :: String -> [String]-addLineBreaks = lineBreaksAt 40--h :: String -> String-h = unlines . addLineBreaks--commandLineOptions :: [OptDescr (Result Maybe -> Result Maybe)]-commandLineOptions = [-    Option   []  ["help"]             (NoArg (const $ Right Nothing))     (h "display this help and exit")-  , Option   []  ["ignore-dot-hspec"] (NoArg setIgnoreConfigFile)         (h "do not read options from ~/.hspec and .hspec")-  , mkOption "m"  "match"             (Arg "PATTERN" return addMatch)     (h "only run examples that match given PATTERN")-  , mkOption []   "skip"              (Arg "PATTERN" return addSkip)      (h "skip examples that match given PATTERN")-  ]-  where-    setIgnoreConfigFile = set $ \config -> config {configIgnoreConfigFile = True}--configFileOptions :: Monad m => [OptDescr (Result m -> Result m)]-configFileOptions = [-    Option   []  ["color"]            (NoArg setColor)                    (h "colorize the output")-  , Option   []  ["no-color"]         (NoArg setNoColor)                  (h "do not colorize the output")-  , Option   []  ["diff"]             (NoArg setDiff)                     (h "show colorized diffs")-  , Option   []  ["no-diff"]          (NoArg setNoDiff)                   (h "do not show colorized diffs")-  , mkOption "f"  "format"            (Arg "FORMATTER" readFormatter setFormatter) formatHelp-  , mkOption "o"  "out"               (Arg "FILE" return setOutputFile)   (h "write output to a file instead of STDOUT")-  , mkOption []   "depth"             (Arg "N" readMaybe setDepth)        (h "maximum depth of generated test values for SmallCheck properties")-  , mkOption "a"  "qc-max-success"    (Arg "N" readMaybe setMaxSuccess)   (h "maximum number of successful tests before a QuickCheck property succeeds")-  , mkOption ""   "qc-max-size"       (Arg "N" readMaybe setMaxSize)      (h "size to use for the biggest test cases")-  , mkOption ""   "qc-max-discard"    (Arg "N" readMaybe setMaxDiscardRatio) (h "maximum number of discarded tests per successful test before giving up")-  , mkOption []   "seed"              (Arg "N" readMaybe setSeed)         (h "used seed for QuickCheck properties")-  , Option   []  ["print-cpu-time"]   (NoArg setPrintCpuTime)             (h "include used CPU time in summary")-  , Option   []  ["dry-run"]          (NoArg setDryRun)                   (h "pretend that everything passed; don't verify anything")-  , Option   []  ["fail-fast"]        (NoArg setFastFail)                 (h "abort on first failure")-  , Option   "r" ["rerun"]            (NoArg  setRerun)                   (h "rerun all examples that failed in the previous test run (only works in combination with --failure-report or in GHCi)")-  , mkOption []   "failure-report"    (Arg "FILE" return setFailureReport)(h "read/write a failure report for use with --rerun")-  , Option   []  ["rerun-all-on-success"] (NoArg setRerunAllOnSuccess)    (h "run the whole test suite after a previously failing rerun succeeds for the first time (only works in combination with --rerun)")-  , mkOption "j"  "jobs"              (Arg "N" readMaxJobs setMaxJobs)    (h "run at most N parallelizable tests simultaneously (default: number of available processors)")-  ]-  where-    readFormatter :: String -> Maybe Formatter-    readFormatter = (`lookup` formatters)--    readMaxJobs :: String -> Maybe Int-    readMaxJobs s = do-      n <- readMaybe s-      guard $ n > 0-      return n--    setFormatter :: Formatter -> Config -> Config-    setFormatter f c = c {configFormatter = Just f}--    setOutputFile :: String -> Config -> Config-    setOutputFile file c = c {configOutputFile = Right file}--    setFailureReport :: String -> Config -> Config-    setFailureReport file c = c {configFailureReport = Just file}--    setMaxJobs :: Int -> Config -> Config-    setMaxJobs n c = c {configConcurrentJobs = Just n}--    setPrintCpuTime = set $ \config -> config {configPrintCpuTime = True}-    setDryRun       = set $ \config -> config {configDryRun = True}-    setFastFail     = set $ \config -> config {configFastFail = True}-    setRerun        = set $ \config -> config {configRerun = True}-    setRerunAllOnSuccess = set $ \config -> config {configRerunAllOnSuccess = True}-    setColor        = set $ \config -> config {configColorMode = ColorAlways}-    setNoColor      = set $ \config -> config {configColorMode = ColorNever}-    setDiff         = set $ \config -> config {configDiff = True}-    setNoDiff       = set $ \config -> config {configDiff = False}--set :: Monad m => (Config -> Config) -> Either a (m Config) -> Either a (m Config)-set = liftM . liftM--documentedOptions :: [OptDescr (Result Maybe -> Result Maybe)]-documentedOptions = commandLineOptions ++ configFileOptions--undocumentedOptions :: [OptDescr (Result Maybe -> Result Maybe)]-undocumentedOptions = [-    -- for compatibility with test-framework-    mkOption [] "maximum-generated-tests" (Arg "NUMBER" readMaybe setMaxSuccess) "how many automated tests something like QuickCheck should try, by default"--    -- undocumented for now, as we probably want to change this to produce a-    -- standalone HTML report in the future-  , Option []  ["html"]                    (NoArg setHtml)                    "produce HTML output"--    -- now a noop-  , Option "v" ["verbose"]                 (NoArg id)                         "do not suppress output to stdout when evaluating examples"-  ]-  where-    setHtml :: Result Maybe -> Result Maybe-    setHtml = set $ \config -> config {configHtmlOutput = True}--recognizedOptions :: [OptDescr (Result Maybe -> Result Maybe)]-recognizedOptions = documentedOptions ++ undocumentedOptions--parseOptions :: Config -> String -> [ConfigFile] -> Maybe EnvVar -> [String] -> Either (ExitCode, String) Config-parseOptions config prog configFiles envVar args = do-      foldM (parseFileOptions prog) config configFiles-  >>= parseEnvVarOptions prog envVar-  >>= parseCommandLineOptions prog args--parseCommandLineOptions :: String -> [String] -> Config -> Either (ExitCode, String) Config-parseCommandLineOptions prog args config = case parse recognizedOptions config args of-  Right Nothing -> Left (ExitSuccess, usageInfo ("Usage: " ++ prog ++ " [OPTION]...\n\nOPTIONS") documentedOptions)-  Right (Just c) -> Right c-  Left err -> failure err-  where-    failure err = Left (ExitFailure 1, prog ++ ": " ++ err ++ "\nTry `" ++ prog ++ " --help' for more information.\n")--parseFileOptions :: String -> Config -> ConfigFile -> Either (ExitCode, String) Config-parseFileOptions prog config (name, args) =-  parseOtherOptions prog ("in config file " ++ name) args config--parseEnvVarOptions :: String -> (Maybe EnvVar) -> Config -> Either (ExitCode, String) Config-parseEnvVarOptions prog args =-  parseOtherOptions prog ("from environment variable " ++ envVarName) (fromMaybe [] args)--parseOtherOptions :: String -> String -> [String] -> Config -> Either (ExitCode, String) Config-parseOtherOptions prog source args config = case parse configFileOptions config args of-  Right (Identity c) -> Right c-  Left err -> failure err-  where-    failure err = Left (ExitFailure 1, prog ++ ": " ++ message)-      where-        message = unlines $ case lines err of-          [x] -> [x ++ " " ++ source]-          xs -> xs ++ [source]--parse :: Monad m => [OptDescr (Result m -> Result m)] -> Config -> [String] -> Either String (m Config)-parse options config args = case getOpt Permute options args of-  (opts, [], []) -> case foldl' (flip id) (Right $ return config) opts of-    Left (InvalidArgument name value) -> Left ("invalid argument `" ++ value ++ "' for `--" ++ name ++ "'")-    Right x -> Right x-  (_, _, err:_) -> Left (init err)-  (_, arg:_, _) -> Left ("unexpected argument `" ++ arg ++ "'")--ignoreConfigFile :: Config -> [String] -> IO Bool-ignoreConfigFile config args = do-  ignore <- lookupEnv "IGNORE_DOT_HSPEC"-  case ignore of-    Just _ -> return True-    Nothing -> case parse recognizedOptions config args of-      Right (Just c) -> return (configIgnoreConfigFile c)-      _ -> return False
hspec-core/src/Test/Hspec/Core/QuickCheckUtil.hs view
@@ -1,32 +1,29 @@-{-# LANGUAGE CPP #-}+{-# LANGUAGE RecordWildCards #-} module Test.Hspec.Core.QuickCheckUtil where  import           Prelude () import           Test.Hspec.Core.Compat +import           Control.Exception+import           Data.List+import           Data.Maybe import           Data.Int-import           Test.QuickCheck hiding (Result(..))-import           Test.QuickCheck as QC+import           System.Random++import           Test.QuickCheck+import           Test.QuickCheck.Text (isOneLine)+import qualified Test.QuickCheck.Property as QCP import           Test.QuickCheck.Property hiding (Result(..)) import           Test.QuickCheck.Gen-import qualified Test.QuickCheck.Property as QCP import           Test.QuickCheck.IO ()---#if MIN_VERSION_QuickCheck(2,7,0) import           Test.QuickCheck.Random-#endif--import           System.Random+import qualified Test.QuickCheck.Test as QC (showTestCount)+import           Test.QuickCheck.State (State(..))  import           Test.Hspec.Core.Util  aroundProperty :: ((a -> IO ()) -> IO ()) -> (a -> Property) -> Property-#if MIN_VERSION_QuickCheck(2,7,0) aroundProperty action p = MkProperty . MkGen $ \r n -> aroundProp action $ \a -> (unGen . unProperty $ p a) r n-#else-aroundProperty action p = MkGen $ \r n -> aroundProp action $ \a -> (unGen $ p a) r n-#endif  aroundProp :: ((a -> IO ()) -> IO ()) -> (a -> Prop) -> Prop aroundProp action p = MkProp $ aroundRose action (\a -> unProp $ p a)@@ -37,25 +34,115 @@   action $ \a -> reduceRose (r a) >>= writeIORef ref   readIORef ref -formatNumbers :: Result -> String-formatNumbers r = "(after " ++ pluralize (numTests r) "test" ++ shrinks ++ ")"-  where-    shrinks-      | 0 < numShrinks r = " and " ++ pluralize (numShrinks r) "shrink"-      | otherwise = ""- newSeed :: IO Int newSeed = fst . randomR (0, fromIntegral (maxBound :: Int32)) <$>-#if MIN_VERSION_QuickCheck(2,7,0)   newQCGen-#else-  newStdGen-#endif -#if MIN_VERSION_QuickCheck(2,7,0) mkGen :: Int -> QCGen mkGen = mkQCGen-#else-mkGen :: Int -> StdGen-mkGen = mkStdGen-#endif++formatNumbers :: Int -> Int -> String+formatNumbers n shrinks = "(after " ++ pluralize n "test" ++ shrinks_ ++ ")"+  where+    shrinks_+      | shrinks > 0 = " and " ++ pluralize shrinks "shrink"+      | otherwise = ""++data QuickCheckResult = QuickCheckResult {+  quickCheckResultNumTests :: Int+, quickCheckResultInfo :: String+, quickCheckResultStatus :: Status+} deriving Show++data Status =+    QuickCheckSuccess+  | QuickCheckFailure QuickCheckFailure+  | QuickCheckOtherFailure String+  deriving Show++data QuickCheckFailure = QCFailure {+  quickCheckFailureNumShrinks :: Int+, quickCheckFailureException :: Maybe SomeException+, quickCheckFailureReason :: String+, quickCheckFailureCounterexample :: [String]+} deriving Show++parseQuickCheckResult :: Result -> QuickCheckResult+parseQuickCheckResult r = case r of+  Success {..} -> result output QuickCheckSuccess++  Failure {..} ->+    case stripSuffix outputWithoutVerbose output of+      Just xs -> result verboseOutput (QuickCheckFailure $ QCFailure numShrinks theException reason failingTestCase)+        where+          verboseOutput+            | xs == "*** Failed! " = ""+            | otherwise = maybeStripSuffix "*** Failed!" (strip xs)+      Nothing -> couldNotParse output+    where+      outputWithoutVerbose = reasonAndNumbers ++ unlines failingTestCase+      reasonAndNumbers+        | isOneLine reason = reason ++ " " ++ numbers ++ colonNewline+        | otherwise = numbers ++ colonNewline ++ ensureTrailingNewline reason+      numbers = formatNumbers numTests numShrinks+      colonNewline = ":\n"++  GaveUp {..} ->+    case stripSuffix outputWithoutVerbose output of+      Just info -> otherFailure info ("Gave up after " ++ numbers ++ "!")+      Nothing -> couldNotParse output+    where+      numbers = showTestCount numTests numDiscarded+      outputWithoutVerbose = "*** Gave up! Passed only " ++ numbers ++ " tests.\n"++  NoExpectedFailure {..} -> case splitBy "*** Failed! " output of+    Just (info, err) -> otherFailure info err+    Nothing -> couldNotParse output++  where+    result = QuickCheckResult (numTests r) . strip+    otherFailure info err = result info (QuickCheckOtherFailure $ strip err)+    couldNotParse = result "" . QuickCheckOtherFailure++showTestCount :: Int -> Int -> String+showTestCount success discarded = QC.showTestCount state+  where+    state = MkState {+      terminal                  = undefined+    , maxSuccessTests           = undefined+    , maxDiscardedRatio         = undefined+    , coverageConfidence        = undefined+    , computeSize               = undefined+    , numTotMaxShrinks          = 0+    , numSuccessTests           = success+    , numDiscardedTests         = discarded+    , numRecentlyDiscardedTests = 0+    , labels                    = mempty+    , classes                   = mempty+    , tables                    = mempty+    , requiredCoverage          = mempty+    , expected                  = True+    , randomSeed                = mkGen 0+    , numSuccessShrinks         = 0+    , numTryShrinks             = 0+    , numTotTryShrinks          = 0+    }++ensureTrailingNewline :: String -> String+ensureTrailingNewline = unlines . lines++maybeStripPrefix :: String -> String -> String+maybeStripPrefix prefix m = fromMaybe m (stripPrefix prefix m)++maybeStripSuffix :: String -> String -> String+maybeStripSuffix suffix = reverse . maybeStripPrefix (reverse suffix) . reverse++stripSuffix :: Eq a => [a] -> [a] -> Maybe [a]+stripSuffix suffix = fmap reverse . stripPrefix (reverse suffix) . reverse++splitBy :: String -> String -> Maybe (String, String)+splitBy sep xs = listToMaybe [+    (x, y) | (x, Just y) <- zip (inits xs) (map stripSep $ tails xs)+  ]+  where+    stripSep = stripPrefix sep
hspec-core/src/Test/Hspec/Core/Runner.hs view
@@ -1,10 +1,5 @@ {-# LANGUAGE CPP #-} -#if MIN_VERSION_base(4,6,0)--- Control.Concurrent.QSem is deprecated in base-4.6.0.*-{-# OPTIONS_GHC -fno-warn-deprecations #-}-#endif- -- | -- Stability: provisional module Test.Hspec.Core.Runner (@@ -30,24 +25,20 @@ import           Prelude () import           Test.Hspec.Core.Compat -import           Control.Monad import           Data.Maybe import           System.IO import           System.Environment (getProgName, getArgs, withArgs) import           System.Exit import qualified Control.Exception as E-import           Control.Concurrent  import           System.Console.ANSI (hHideCursor, hShowCursor) import qualified Test.QuickCheck as QC-import           Control.Monad.IO.Class (liftIO)  import           Test.Hspec.Core.Util (Path) import           Test.Hspec.Core.Spec import           Test.Hspec.Core.Config import           Test.Hspec.Core.Formatters import           Test.Hspec.Core.Formatters.Internal-import qualified Test.Hspec.Core.Formatters.Internal as Formatter import           Test.Hspec.Core.FailureReport import           Test.Hspec.Core.QuickCheckUtil @@ -83,7 +74,7 @@   | otherwise = id   where     markSuccess :: Item () -> Item ()-    markSuccess item = item {itemExample = safeEvaluateExample Success}+    markSuccess item = item {itemExample = safeEvaluateExample (Result "" Success)}      removeCleanup :: SpecTree () -> SpecTree ()     removeCleanup spec = case spec of@@ -160,25 +151,43 @@         seed = (fromJust . configQuickCheckSeed) config         qcArgs = configQuickCheckArgs config -    jobsSem <- newQSem =<< case configConcurrentJobs config of+    concurrentJobs <- case configConcurrentJobs config of       Nothing -> getDefaultConcurrentJobs-      Just maxJobs -> return maxJobs+      Just n -> return n      useColor <- doesUseColor h config -    filteredSpec <- filterSpecs config . applyDryRun config <$> runSpecM spec+    let params = Params (configQuickCheckArgs config) (configSmallCheckDepth config) -    withHiddenCursor useColor h $-      runFormatM useColor (configDiff config) (configHtmlOutput config) (configPrintCpuTime config) seed h $ do-        runFormatter jobsSem useColor h config formatter filteredSpec `finally_` do-          Formatter.interpret $ failedFormatter formatter+    filteredSpec <- map (toEvalTree params) . filterSpecs config . applyDryRun config <$> runSpecM spec -        Formatter.interpret $ footerFormatter formatter+    (total, failures) <- withHiddenCursor useColor h $ do+      let+        formatConfig = FormatConfig {+          formatConfigHandle = h+        , formatConfigUseColor = useColor+        , formatConfigUseDiff = configDiff config+        , formatConfigHtmlOutput = configHtmlOutput config+        , formatConfigPrintCpuTime = configPrintCpuTime config+        , formatConfigUsedSeed =  seed+        }+        evalConfig = EvalConfig {+          evalConfigFormat = formatterToFormat formatter formatConfig+        , evalConfigConcurrentJobs = concurrentJobs+        , evalConfigFastFail = configFastFail config+        }+      runFormatter evalConfig filteredSpec -        xs <- map failureRecordPath <$> Formatter.interpret getFailMessages-        liftIO $ dumpFailureReport config seed qcArgs xs+    dumpFailureReport config seed qcArgs failures+    return (Summary total (length failures)) -        Summary <$> Formatter.interpret getTotalCount <*> Formatter.interpret getFailCount+toEvalTree :: Params -> SpecTree () -> EvalTree+toEvalTree params = go+  where+    go t = case t of+      Node s xs -> Node s (map go xs)+      NodeWithCleanup c xs -> NodeWithCleanup (c ()) (map go xs)+      Leaf (Item requirement loc isParallelizable e)  -> Leaf (EvalItem requirement loc (fromMaybe False isParallelizable) (e params $ ($ ())))  dumpFailureReport :: Config -> Integer -> QC.Args -> [Path] -> IO () dumpFailureReport config seed qcArgs xs = do
hspec-core/src/Test/Hspec/Core/Runner/Eval.hs view
@@ -1,157 +1,268 @@ {-# LANGUAGE CPP #-}+{-# LANGUAGE DeriveFunctor #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE ConstraintKinds #-} -#if MIN_VERSION_base(4,6,0)+#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 (runFormatter) where+module Test.Hspec.Core.Runner.Eval (+  EvalConfig(..)+, EvalTree+, EvalItem(..)+, runFormatter+#ifdef TEST+, runSequentially+#endif+) where  import           Prelude ()-import           Test.Hspec.Core.Compat+import           Test.Hspec.Core.Compat hiding (Monad)+import qualified Test.Hspec.Core.Compat as M -import           Control.Monad (unless, when) import qualified Control.Exception as E import           Control.Concurrent-import           System.IO (Handle)+import           Control.Concurrent.Async hiding (cancel)  import           Control.Monad.IO.Class (liftIO)-import           Data.Time.Clock.POSIX+import qualified Control.Monad.IO.Class as M +import           Control.Monad.Trans.State hiding (State, state)+import           Control.Monad.Trans.Class+ import           Test.Hspec.Core.Util-import           Test.Hspec.Core.Spec-import           Test.Hspec.Core.Config-import           Test.Hspec.Core.Formatters hiding (FormatM)-import           Test.Hspec.Core.Formatters.Internal-import qualified Test.Hspec.Core.Formatters.Internal as Formatter+import           Test.Hspec.Core.Spec (Tree(..), 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 -type EvalTree = Tree (ActionWith ()) (String, Maybe Location, ProgressCallback -> FormatResult -> IO (FormatM ()))+-- 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 m = EvalConfig {+  evalConfigFormat :: Format m+, evalConfigConcurrentJobs :: Int+, evalConfigFastFail :: Bool+}++data State m = State {+  stateConfig :: EvalConfig m+, stateSuccessCount :: Int+, statePendingCount :: Int+, stateFailures :: [Path]+}++type EvalM m = StateT (State m) m++increaseSuccessCount :: Monad m => EvalM m ()+increaseSuccessCount = modify $ \state -> state {stateSuccessCount = stateSuccessCount state + 1}++increasePendingCount :: Monad m => EvalM m ()+increasePendingCount = modify $ \state -> state {statePendingCount = statePendingCount state + 1}++addFailure :: Monad m => Path -> EvalM m ()+addFailure path = modify $ \state -> state {stateFailures = path : stateFailures state}++getFormat :: Monad m => (Format m -> a) -> EvalM m a+getFormat format = gets (format . evalConfigFormat . stateConfig)++reportItem :: Monad m => Path -> Format.Item -> EvalM m ()+reportItem path item = do+  case Format.itemResult item of+    Format.Success {} -> increaseSuccessCount+    Format.Pending {} -> increasePendingCount+    Format.Failure {} -> addFailure path+  format <- getFormat formatItem+  lift (format path item)++failureItem :: Maybe Location -> Seconds -> String -> FailureReason -> Format.Item+failureItem loc duration info err = Format.Item loc duration info (Format.Failure err)++reportResult :: Monad m => Path -> Maybe Location -> (Seconds, Result) -> EvalM m ()+reportResult path loc (duration, result) = do+  case result of+    Result info status -> case status of+      Success -> reportItem path (Format.Item loc duration info Format.Success)+      Pending loc_ reason -> reportItem path (Format.Item (loc_ <|> loc) duration info $ Format.Pending reason)+      Failure loc_ err@(Error _ e) -> reportItem path (failureItem (loc_ <|> extractLocation e <|> loc) duration info err)+      Failure loc_ err -> reportItem path (failureItem (loc_ <|> loc) duration info err)++groupStarted :: Monad m => Path -> EvalM m ()+groupStarted path = do+  format <- getFormat formatGroupStarted+  lift $ format path++groupDone :: Monad m => Path -> EvalM m ()+groupDone path = do+  format <- getFormat formatGroupDone+  lift $ format path++data EvalItem = EvalItem {+  evalItemDescription :: String+, evalItemLocation :: Maybe Location+, evalItemParallelize :: Bool+, evalItemAction :: ProgressCallback -> IO Result+}++type EvalTree = Tree (IO ()) EvalItem++runEvalM :: Monad m => EvalConfig m -> EvalM m () -> m (State m)+runEvalM config action = execStateT action (State config 0 0 [])+ -- | Evaluate all examples of a given spec and produce a report.-runFormatter :: QSem -> Bool -> Handle -> Config -> Formatter -> [SpecTree ()] -> FormatM ()-runFormatter jobsSem useColor h c formatter specs = do-  Formatter.interpret $ headerFormatter formatter-  chan <- liftIO newChan-  reportProgress <- liftIO mkReportProgress-  run chan reportProgress c formatter (toEvalTree specs)+runFormatter :: forall m. MonadIO m => EvalConfig m -> [EvalTree] -> IO (Int, [Path])+runFormatter config specs = do+  let+    start = parallelizeTree (evalConfigConcurrentJobs config) specs+    cancel = cancelMany . concatMap toList . map (fmap fst)+  E.bracket start cancel $ \ runningSpecs -> do+    withTimer 0.05 $ \ timer -> do+      state <- formatRun format $ do+        runEvalM config $+          run $ map (fmap (fmap (. reportProgress timer) . snd)) runningSpecs+      let+        failures = stateFailures state+        total = stateSuccessCount state + statePendingCount state + length failures+      return (total, reverse failures)   where-    mkReportProgress :: IO (Path -> Progress -> IO ())-    mkReportProgress-      | useColor = every 0.05 $ exampleProgress formatter h-      | otherwise = return $ \_ _ -> return ()+    format = evalConfigFormat config -    toEvalTree :: [SpecTree ()] -> [EvalTree]-    toEvalTree = map (fmap f)-      where-        f :: Item () -> (String, Maybe Location, ProgressCallback -> FormatResult -> IO (FormatM ()))-        f (Item requirement loc isParallelizable e) = (requirement, loc, parallelize jobsSem isParallelizable $ e params ($ ()))+    reportProgress :: IO Bool -> Path -> Progress -> m ()+    reportProgress timer path progress = do+      r <- liftIO timer+      when r (formatProgress format path progress) -    params :: Params-    params = Params (configQuickCheckArgs c) (configSmallCheckDepth c)+cancelMany :: [Async a] -> IO ()+cancelMany asyncs = do+  mapM_ (killThread . asyncThreadId) asyncs+  mapM_ waitCatch asyncs --- | Execute given action at most every specified number of seconds.-every :: POSIXTime -> (a -> b -> IO ()) -> IO (a -> b -> IO ())-every seconds action = do-  timer <- newTimer seconds-  return $ \a b -> do-    r <- timer-    when r (action a b)+data Item a = Item {+  _itemDescription :: String+, _itemLocation :: Maybe Location+, _itemAction :: a+} deriving Functor -type FormatResult = Either E.SomeException Result -> FormatM ()+type Job m p a = (p -> m ()) -> m a -parallelize :: QSem -> Bool -> (ProgressCallback -> IO (Either E.SomeException Result)) -> ProgressCallback -> FormatResult -> IO (FormatM ())-parallelize jobsSem isParallelizable e-  | isParallelizable = runParallel jobsSem e-  | otherwise = runSequentially e+type RunningItem m = Item (Path -> m (Seconds, Result))+type RunningTree m = Tree (IO ()) (RunningItem m) -runSequentially :: (ProgressCallback -> IO (Either E.SomeException Result)) -> ProgressCallback -> FormatResult -> IO (FormatM ())-runSequentially e reportProgress formatResult = return $ do-  result <- liftIO $ e reportProgress-  formatResult result+type RunningItem_ m = (Async (), Item (Job m Progress (Seconds, Result)))+type RunningTree_ m = Tree (IO ()) (RunningItem_ m) -data Report = ReportProgress Progress | ReportResult (Either E.SomeException Result)+data Semaphore = Semaphore {+  semaphoreWait :: IO ()+, semaphoreSignal :: IO ()+} -runParallel :: QSem -> (ProgressCallback -> IO (Either E.SomeException Result)) -> ProgressCallback -> FormatResult -> IO (FormatM ())-runParallel jobsSem e reportProgress formatResult = do+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 p a -> IO (Async (), Job m p (Seconds, a))+parallelize sem isParallelizable+  | isParallelizable = runParallel sem+  | otherwise = runSequentially++runSequentially :: MonadIO m => Job IO p a -> IO (Async (), Job m p (Seconds, a))+runSequentially action = do   mvar <- newEmptyMVar-  _ <- forkIO $ E.bracket_ (waitQSem jobsSem) (signalQSem jobsSem) $ do-    let progressCallback = replaceMVar mvar . ReportProgress-    result <- e progressCallback-    replaceMVar mvar (ReportResult result)-  return $ evalReport mvar+  (asyncAction, evalAction) <- runParallel (Semaphore (takeMVar mvar) (return ())) action+  return (asyncAction, \ notifyPartial -> liftIO (putMVar mvar ()) >> evalAction notifyPartial)++data Parallel p a = Partial p | Return a++runParallel :: forall m p a. MonadIO m => Semaphore -> Job IO p a -> IO (Async (), Job m p (Seconds, a))+runParallel Semaphore{..} action = do+  mvar <- newEmptyMVar+  asyncAction <- async $ E.bracket_ semaphoreWait semaphoreSignal (worker mvar)+  return (asyncAction, eval mvar)   where-    evalReport :: MVar Report -> FormatM ()-    evalReport mvar = do+    worker mvar = do+      let partialCallback = replaceMVar mvar . Partial+      result <- measure $ action partialCallback+      replaceMVar mvar (Return result)++    eval :: MVar (Parallel p (Seconds, a)) -> (p -> m ()) -> m (Seconds, a)+    eval mvar notifyPartial = do       r <- liftIO (takeMVar mvar)       case r of-        ReportProgress p -> do-          liftIO $ reportProgress p-          evalReport mvar-        ReportResult result -> formatResult result--    replaceMVar :: MVar a -> a -> IO ()-    replaceMVar mvar p = tryTakeMVar mvar >> putMVar mvar p+        Partial p -> do+          notifyPartial p+          eval mvar notifyPartial+        Return result -> return result -data Message = Done | Run (FormatM ())+replaceMVar :: MVar a -> a -> IO ()+replaceMVar mvar p = tryTakeMVar mvar >> putMVar mvar p -run :: Chan Message -> (Path -> ProgressCallback) -> Config -> Formatter -> [EvalTree] -> FormatM ()-run chan reportProgress_ c formatter specs = do-  liftIO $ do-    forM_ specs (queueSpec [])-    writeChan chan Done-  processMessages (readChan chan) (configFastFail c)+run :: forall m. MonadIO m => [RunningTree m] -> EvalM m ()+run specs = do+  fastFail <- gets (evalConfigFastFail . stateConfig)+  sequenceActions fastFail (concatMap foldSpec specs)   where-    defer :: FormatM () -> IO ()-    defer = writeChan chan . Run--    runCleanup :: IO () -> Path -> FormatM ()-    runCleanup action path = do-      r <- liftIO $ safeTry action-      either (failed Nothing path . Left) return r+    foldSpec :: RunningTree m -> [EvalM m ()]+    foldSpec = foldTree FoldTree {+      onGroupStarted = groupStarted+    , onGroupDone = groupDone+    , onCleanup = runCleanup+    , onLeafe = evalItem+    } -    queueSpec :: [String] -> EvalTree -> IO ()-    queueSpec rGroups (Node group xs) = do-      defer (Formatter.interpret $ exampleGroupStarted formatter (reverse rGroups) group)-      forM_ xs (queueSpec (group : rGroups))-      defer (Formatter.interpret $ exampleGroupDone formatter)-    queueSpec rGroups (NodeWithCleanup action xs) = do-      forM_ xs (queueSpec rGroups)-      defer (runCleanup (action ()) (reverse rGroups, "afterAll-hook"))-    queueSpec rGroups (Leaf e) =-      queueExample (reverse rGroups) e+    runCleanup :: [String] -> IO () -> EvalM m ()+    runCleanup groups action = do+      (dt, r) <- liftIO $ measure $ safeTry action+      either (\ e -> reportItem path . failureItem (extractLocation e) dt "" . Error Nothing $ e) return r+      where+        path = (groups, "afterAll-hook") -    queueExample :: [String] -> (String, Maybe Location, ProgressCallback -> FormatResult -> IO (FormatM ())) -> IO ()-    queueExample groups (requirement, loc, e) = e reportProgress formatResult >>= defer+    evalItem :: [String] -> RunningItem m -> EvalM m ()+    evalItem groups (Item requirement loc action) = do+      lift (action path) >>= reportResult path loc       where         path :: Path         path = (groups, requirement) -        reportProgress = reportProgress_ path--        formatResult :: FormatResult-        formatResult result = do-          case result of-            Right Success -> do-              increaseSuccessCount-              Formatter.interpret $ exampleSucceeded formatter path-            Right (Pending reason) -> do-              increasePendingCount-              Formatter.interpret $ examplePending formatter path reason-            Right (Failure loc_ err) -> failed (loc_ <|> loc) path (Right err)-            Left err         -> failed loc path (Left  err)+data FoldTree c a r = FoldTree {+  onGroupStarted :: Path -> r+, onGroupDone :: Path -> r+, onCleanup :: [String] -> c -> r+, onLeafe :: [String] -> a -> r+} -    failed loc path err = do-      increaseFailCount-      addFailMessage loc path err-      Formatter.interpret $ exampleFailed formatter path err+foldTree :: FoldTree c a r -> Tree c a -> [r]+foldTree FoldTree{..} = go []+  where+    go rGroups (Node group xs) = start : children ++ [done]+      where+        path = (reverse rGroups, group)+        start = onGroupStarted path+        children = concatMap (go (group : rGroups)) xs+        done =  onGroupDone path+    go rGroups (NodeWithCleanup action xs) = children ++ [cleanup]+      where+        children = concatMap (go rGroups) xs+        cleanup = onCleanup (reverse rGroups) action+    go rGroups (Leaf a) = [onLeafe (reverse rGroups) a] -processMessages :: IO Message -> Bool -> FormatM ()-processMessages getMessage fastFail = go+sequenceActions :: Monad m => Bool -> [EvalM m ()] -> EvalM m ()+sequenceActions fastFail = go   where-    go = liftIO getMessage >>= \m -> case m of-      Run action -> do-        action-        fails <- Formatter.interpret getFailCount-        unless (fastFail && fails /= 0) go-      Done -> return ()+    go [] = return ()+    go (action : actions) = do+      () <- action+      hasFailures <- (not . null) <$> gets stateFailures+      let stopNow = fastFail && hasFailures+      unless stopNow (go actions)
hspec-core/src/Test/Hspec/Core/Spec.hs view
@@ -19,6 +19,7 @@ , xdescribe , xcontext , parallel+, sequential  -- * The @SpecM@ monad , module Test.Hspec.Core.Spec.Monad@@ -30,6 +31,9 @@ , module Test.Hspec.Core.Tree ) where +import           Prelude ()+import           Test.Hspec.Core.Compat+ import qualified Control.Exception as E import           Data.CallStack @@ -41,22 +45,22 @@ import           Test.Hspec.Core.Spec.Monad  -- | The @describe@ function combines a list of specs into a larger spec.-describe :: String -> SpecWith a -> SpecWith a+describe :: HasCallStack => String -> SpecWith a -> SpecWith a describe label spec = runIO (runSpecM spec) >>= fromSpecList . return . specGroup label  -- | @context@ is an alias for `describe`.-context :: String -> SpecWith a -> SpecWith a+context :: HasCallStack => String -> SpecWith a -> SpecWith a context = describe  -- | -- Changing `describe` to `xdescribe` marks all spec items of the corresponding subtree as pending. -- -- This can be used to temporarily disable spec items.-xdescribe :: String -> SpecWith a -> SpecWith a-xdescribe label spec = before_ pending $ describe label spec+xdescribe :: HasCallStack => String -> SpecWith a -> SpecWith a+xdescribe label spec = before_ pending_ $ describe label spec  -- | @xcontext@ is an alias for `xdescribe`.-xcontext :: String -> SpecWith a -> SpecWith a+xcontext :: HasCallStack => String -> SpecWith a -> SpecWith a xcontext = xdescribe  -- | The @it@ function creates a spec item.@@ -82,7 +86,7 @@ -- -- This can be used to temporarily disable a spec item. xit :: (HasCallStack, Example a) => String -> a -> SpecWith (Arg a)-xit label action = before_ pending $ it label action+xit label action = before_ pending_ $ it label action  -- | @xspecify@ is an alias for `xit`. xspecify :: (HasCallStack, Example a) => String -> a -> SpecWith (Arg a)@@ -91,8 +95,15 @@ -- | `parallel` marks all spec items of the given spec to be safe for parallel -- evaluation. parallel :: SpecWith a -> SpecWith a-parallel = mapSpecItem_ $ \item -> item {itemIsParallelizable = True}+parallel = mapSpecItem_ (setParallelizable True) +-- | `sequential` marks all spec items of the given spec to be evaluated sequentially.+sequential :: SpecWith a -> SpecWith a+sequential = mapSpecItem_ (setParallelizable False)++setParallelizable :: Bool -> Item a -> Item a+setParallelizable value item = item {itemIsParallelizable = itemIsParallelizable item <|> Just value}+ -- | `pending` can be used to mark a spec item as pending. -- -- If you want to textually specify a behavior but do not have an example yet,@@ -101,11 +112,14 @@ -- > describe "fancyFormatter" $ do -- >   it "can format text in a way that everyone likes" $ -- >     pending-pending :: Expectation-pending = E.throwIO (Pending Nothing)+pending :: HasCallStack => Expectation+pending = E.throwIO (Pending location Nothing) +pending_ :: Expectation+pending_ = (E.throwIO (Pending Nothing Nothing))+ -- | -- `pendingWith` is similar to `pending`, but it takes an additional string -- argument that can be used to specify the reason for why the spec item is pending.-pendingWith :: String -> Expectation-pendingWith = E.throwIO . Pending . Just+pendingWith :: HasCallStack => String -> Expectation+pendingWith = E.throwIO . Pending location . Just
hspec-core/src/Test/Hspec/Core/Spec/Monad.hs view
@@ -7,7 +7,6 @@ , fromSpecList , runIO -, mapSpecTree , mapSpecItem , mapSpecItem_ , modifyParams@@ -16,6 +15,7 @@ import           Prelude () import           Test.Hspec.Core.Compat +import           Control.Arrow import           Control.Monad.Trans.Writer import           Control.Monad.IO.Class (liftIO) @@ -49,8 +49,8 @@ runIO :: IO r -> SpecM a r runIO = SpecM . liftIO -mapSpecTree :: (SpecTree a -> SpecTree b) -> SpecWith a -> SpecWith b-mapSpecTree f spec = runIO (runSpecM spec) >>= fromSpecList . map f+mapSpecTree :: (SpecTree a -> SpecTree b) -> SpecM a r -> SpecM b r+mapSpecTree f (SpecM specs) = SpecM (mapWriterT (fmap (second (map f))) specs)  mapSpecItem :: (ActionWith a -> ActionWith b) -> (Item a -> Item b) -> SpecWith a -> SpecWith b mapSpecItem g f = mapSpecTree go
hspec-core/src/Test/Hspec/Core/Timer.hs view
@@ -1,14 +1,21 @@-module Test.Hspec.Core.Timer where+module Test.Hspec.Core.Timer (withTimer) where -import           Data.IORef-import           Data.Time.Clock.POSIX+import           Prelude ()+import           Test.Hspec.Core.Compat -newTimer :: POSIXTime -> IO (IO Bool)-newTimer delay = do-  ref <- getPOSIXTime >>= newIORef-  return $ do-    t0 <- readIORef ref-    t1 <- getPOSIXTime-    if delay < t1 - t0-      then writeIORef ref t1 >> return True-      else return False+import           Control.Exception+import           Control.Concurrent.Async++import           Test.Hspec.Core.Clock++withTimer :: Seconds -> (IO Bool -> IO a) -> IO a+withTimer delay action = do+  ref <- newIORef False+  bracket (async $ worker delay ref) cancel $ \_ -> do+    action $ atomicModifyIORef ref (\a -> (False, a))++worker :: Seconds -> IORef Bool -> IO ()+worker delay ref = do+  forever $ do+    sleep delay+    atomicWriteIORef ref True
hspec-core/src/Test/Hspec/Core/Tree.hs view
@@ -12,14 +12,15 @@ , Item (..) , specGroup , specItem+, location ) where -import           Data.CallStack-import           Control.Exception- import           Prelude () import           Test.Hspec.Core.Compat +import           Data.CallStack+import           Data.Maybe+ import           Test.Hspec.Core.Example  -- | Internal tree data structure@@ -50,28 +51,35 @@ , itemLocation :: Maybe Location   -- | A flag that indicates whether it is safe to evaluate this spec item in   -- parallel with other spec items-, itemIsParallelizable :: Bool+, itemIsParallelizable :: Maybe Bool   -- | Example for behavior-, itemExample :: Params -> (ActionWith a -> IO ()) -> ProgressCallback -> IO (Either SomeException Result)+, itemExample :: Params -> (ActionWith a -> IO ()) -> ProgressCallback -> IO Result }  -- | The @specGroup@ function combines a list of specs into a larger spec.-specGroup :: String -> [SpecTree a] -> SpecTree a+specGroup :: HasCallStack => String -> [SpecTree a] -> SpecTree a specGroup s = Node msg   where+    msg :: HasCallStack => String     msg-      | null s = "(no description given)"+      | null s = fromMaybe "(no description given)" defaultDescription       | 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 False (safeEvaluateExample e)+specItem s e = Leaf $ Item requirement location Nothing (safeEvaluateExample e)   where+    requirement :: HasCallStack => String     requirement-      | null s = "(unspecified behavior)"+      | null s = fromMaybe "(unspecified behavior)" defaultDescription       | otherwise = s -    location :: Maybe Location-    location = case reverse callStack of-      (_, loc) : _ -> Just (Location (srcLocFile loc) (srcLocStartLine loc) (srcLocStartCol loc) ExactLocation)-      _ -> Nothing+location :: HasCallStack => Maybe Location+location = case reverse callStack of+  (_, loc) : _ -> Just (Location (srcLocFile loc) (srcLocStartLine loc) (srcLocStartCol loc))+  _ -> Nothing++defaultDescription :: HasCallStack => Maybe String+defaultDescription = case reverse callStack of+  (_, loc) : _ -> Just (srcLocModule loc ++ "[" ++ show (srcLocStartLine loc) ++ ":" ++ show (srcLocStartCol loc) ++ "]")+  _ -> Nothing
hspec-core/src/Test/Hspec/Core/Util.hs view
@@ -7,6 +7,7 @@  -- * Working with paths , Path+, joinPath , formatRequirement , filterPredicate @@ -68,6 +69,12 @@ type Path = ([String], String)  -- |+-- Join a `Path` with slashes.  The result will have a leading and a trailing+-- slash.+joinPath :: Path -> String+joinPath (groups, requirement) = "/" ++ intercalate "/" (groups ++ [requirement]) ++ "/"++-- | -- Try to create a proper English sentence from a path by applying some -- heuristics. formatRequirement :: Path -> String@@ -83,11 +90,11 @@  -- | A predicate that can be used to filter a spec tree. filterPredicate :: String -> Path -> Bool-filterPredicate pattern path@(groups, requirement) =+filterPredicate pattern path =      pattern `isInfixOf` plain   || pattern `isInfixOf` formatted   where-    plain = intercalate "/" (groups ++ [requirement])+    plain = joinPath path     formatted = formatRequirement path  -- | The function `formatException` converts an exception to a string.@@ -100,8 +107,8 @@ -- For `IOException`s the `IOErrorType` is included, as well. formatException :: SomeException -> String formatException err@(SomeException e) = case fromException err of-  Just ioe -> showType ioe ++ " of type " ++ showIOErrorType ioe ++ " (" ++ show ioe ++ ")"-  Nothing  -> showType e ++ " (" ++ show e ++ ")"+  Just ioe -> showType ioe ++ " of type " ++ showIOErrorType ioe ++ "\n" ++ show ioe+  Nothing  -> showType e ++ "\n" ++ show e   where     showIOErrorType :: IOException -> String     showIOErrorType ioe = case ioe_type ioe of@@ -129,13 +136,4 @@ -- occurs, the exception is returned instead.  Unlike `try` it is agnostic to -- asynchronous exceptions. safeTry :: IO a -> IO (Either SomeException a)-safeTry action = bracket runAction cancelAction waitForAction-  where-    runAction = async ((action >>= evaluate))-    waitForAction = waitCatch-    cancelAction a = do-      cancel a-      -- It is important to wait here to make sure all finalizers in action have-      -- been run. Otherwise the main thread can exit before they have finished-      -- and the finalizers are only partially run.-      waitCatch a -- We use waitCatch to hide the ThreadKilled exception+safeTry action = withAsync (action >>= evaluate) waitCatch
+ hspec-core/vendor/Control/Concurrent/Async.hs view
@@ -0,0 +1,870 @@+{-# LANGUAGE CPP, MagicHash, UnboxedTuples, RankNTypes,+    ExistentialQuantification #-}+#if __GLASGOW_HASKELL__ >= 701+{-# LANGUAGE Trustworthy #-}+#endif+#if __GLASGOW_HASKELL__ < 710+{-# LANGUAGE DeriveDataTypeable #-}+#endif+{-# OPTIONS -Wall #-}++-----------------------------------------------------------------------------+-- |+-- Module      :  Control.Concurrent.Async+-- Copyright   :  (c) Simon Marlow 2012+-- License     :  BSD3 (see the file LICENSE)+--+-- Maintainer  :  Simon Marlow <marlowsd@gmail.com>+-- Stability   :  provisional+-- Portability :  non-portable (requires concurrency)+--+-- This module provides a set of operations for running IO operations+-- asynchronously and waiting for their results.  It is a thin layer+-- over the basic concurrency operations provided by+-- "Control.Concurrent".  The main additional functionality it+-- provides is the ability to wait for the return value of a thread,+-- but the interface also provides some additional safety and+-- robustness over using threads and @MVar@ directly.+--+-- The basic type is @'Async' a@, which represents an asynchronous+-- @IO@ action that will return a value of type @a@, or die with an+-- exception.  An @Async@ corresponds to a thread, and its 'ThreadId'+-- can be obtained with 'asyncThreadId', although that should rarely+-- be necessary.+--+-- For example, to fetch two web pages at the same time, we could do+-- this (assuming a suitable @getURL@ function):+--+-- >    do a1 <- async (getURL url1)+-- >       a2 <- async (getURL url2)+-- >       page1 <- wait a1+-- >       page2 <- wait a2+-- >       ...+--+-- where 'async' starts the operation in a separate thread, and+-- 'wait' waits for and returns the result.  If the operation+-- throws an exception, then that exception is re-thrown by+-- 'wait'.  This is one of the ways in which this library+-- provides some additional safety: it is harder to accidentally+-- forget about exceptions thrown in child threads.+--+-- A slight improvement over the previous example is this:+--+-- >       withAsync (getURL url1) $ \a1 -> do+-- >       withAsync (getURL url2) $ \a2 -> do+-- >       page1 <- wait a1+-- >       page2 <- wait a2+-- >       ...+--+-- 'withAsync' is like 'async', except that the 'Async' is+-- automatically killed (using 'uninterruptibleCancel') if the+-- enclosing IO operation returns before it has completed.  Consider+-- the case when the first 'wait' throws an exception; then the second+-- 'Async' will be automatically killed rather than being left to run+-- in the background, possibly indefinitely.  This is the second way+-- that the library provides additional safety: using 'withAsync'+-- means we can avoid accidentally leaving threads running.+-- Furthermore, 'withAsync' allows a tree of threads to be built, such+-- that children are automatically killed if their parents die for any+-- reason.+--+-- The pattern of performing two IO actions concurrently and waiting+-- for their results is packaged up in a combinator 'concurrently', so+-- we can further shorten the above example to:+--+-- >       (page1, page2) <- concurrently (getURL url1) (getURL url2)+-- >       ...+--+-- The 'Functor' instance can be used to change the result of an+-- 'Async'.  For example:+--+-- > ghci> a <- async (return 3)+-- > ghci> wait a+-- > 3+-- > ghci> wait (fmap (+1) a)+-- > 4++-----------------------------------------------------------------------------++module Control.Concurrent.Async (++    -- * Asynchronous actions+    Async,+    -- ** Spawning+    async, asyncBound, asyncOn, asyncWithUnmask, asyncOnWithUnmask,++    -- ** Spawning with automatic 'cancel'ation+    withAsync, withAsyncBound, withAsyncOn, withAsyncWithUnmask,+    withAsyncOnWithUnmask,++    -- ** Querying 'Async's+    wait, poll, waitCatch, asyncThreadId,+    cancel, uninterruptibleCancel, cancelWith, AsyncCancelled(..),++    -- ** STM operations+    waitSTM, pollSTM, waitCatchSTM,++    -- ** Waiting for multiple 'Async's+    waitAny, waitAnyCatch, waitAnyCancel, waitAnyCatchCancel,+    waitEither, waitEitherCatch, waitEitherCancel, waitEitherCatchCancel,+    waitEither_,+    waitBoth,++    -- ** Waiting for multiple 'Async's in STM+    waitAnySTM, waitAnyCatchSTM,+    waitEitherSTM, waitEitherCatchSTM,+    waitEitherSTM_,+    waitBothSTM,++    -- ** Linking+    link, link2, ExceptionInLinkedThread(..),++    -- * Convenient utilities+    race, race_,+    concurrently, concurrently_,+    mapConcurrently, forConcurrently,+    mapConcurrently_, forConcurrently_,+    replicateConcurrently, replicateConcurrently_,+    Concurrently(..),+    compareAsyncs,++  ) where++import Control.Concurrent.STM+import Control.Exception+import Control.Concurrent+import qualified Data.Foldable as F+#if !MIN_VERSION_base(4,6,0)+import Prelude hiding (catch)+#endif+import Control.Monad+import Control.Applicative+#if !MIN_VERSION_base(4,8,0)+import Data.Monoid (Monoid(mempty,mappend))+import Data.Traversable+#endif+#if __GLASGOW_HASKELL__ < 710+import Data.Typeable+#endif+#if MIN_VERSION_base(4,9,0)+import Data.Semigroup (Semigroup((<>)))+#endif++import Data.IORef++import GHC.Exts+import GHC.IO hiding (finally, onException)+import GHC.Conc++-- -----------------------------------------------------------------------------+-- STM Async API+++-- | An asynchronous action spawned by 'async' or 'withAsync'.+-- Asynchronous actions are executed in a separate thread, and+-- operations are provided for waiting for asynchronous actions to+-- complete and obtaining their results (see e.g. 'wait').+--+data Async a = Async+  { asyncThreadId :: {-# UNPACK #-} !ThreadId+                  -- ^ Returns the 'ThreadId' of the thread running+                  -- the given 'Async'.+  , _asyncWait    :: STM (Either SomeException a)+  }++instance Eq (Async a) where+  Async a _ == Async b _  =  a == b++instance Ord (Async a) where+  Async a _ `compare` Async b _  =  a `compare` b++instance Functor Async where+  fmap f (Async a w) = Async a (fmap (fmap f) w)++-- | Compare two 'Async's that may have different types+compareAsyncs :: Async a -> Async b -> Ordering+compareAsyncs (Async t1 _) (Async t2 _) = compare t1 t2++-- | Spawn an asynchronous action in a separate thread.+async :: IO a -> IO (Async a)+async = inline asyncUsing rawForkIO++-- | Like 'async' but using 'forkOS' internally.+asyncBound :: IO a -> IO (Async a)+asyncBound = asyncUsing forkOS++-- | Like 'async' but using 'forkOn' internally.+asyncOn :: Int -> IO a -> IO (Async a)+asyncOn = asyncUsing . rawForkOn++-- | Like 'async' but using 'forkIOWithUnmask' internally.  The child+-- thread is passed a function that can be used to unmask asynchronous+-- exceptions.+asyncWithUnmask :: ((forall b . IO b -> IO b) -> IO a) -> IO (Async a)+asyncWithUnmask actionWith = asyncUsing rawForkIO (actionWith unsafeUnmask)++-- | Like 'asyncOn' but using 'forkOnWithUnmask' internally.  The+-- child thread is passed a function that can be used to unmask+-- asynchronous exceptions.+asyncOnWithUnmask :: Int -> ((forall b . IO b -> IO b) -> IO a) -> IO (Async a)+asyncOnWithUnmask cpu actionWith =+  asyncUsing (rawForkOn cpu) (actionWith unsafeUnmask)++asyncUsing :: (IO () -> IO ThreadId)+           -> IO a -> IO (Async a)+asyncUsing doFork = \action -> do+   var <- newEmptyTMVarIO+   -- t <- forkFinally action (\r -> atomically $ putTMVar var r)+   -- slightly faster:+   t <- mask $ \restore ->+          doFork $ try (restore action) >>= atomically . putTMVar var+   return (Async t (readTMVar var))++-- | Spawn an asynchronous action in a separate thread, and pass its+-- @Async@ handle to the supplied function.  When the function returns+-- or throws an exception, 'uninterruptibleCancel' is called on the @Async@.+--+-- > withAsync action inner = mask $ \restore -> do+-- >   a <- async (restore action)+-- >   restore inner `finally` uninterruptibleCancel a+--+-- This is a useful variant of 'async' that ensures an @Async@ is+-- never left running unintentionally.+--+-- Note: a reference to the child thread is kept alive until the call+-- to `withAsync` returns, so nesting many `withAsync` calls requires+-- linear memory.+--+withAsync :: IO a -> (Async a -> IO b) -> IO b+withAsync = inline withAsyncUsing rawForkIO++-- | Like 'withAsync' but uses 'forkOS' internally.+withAsyncBound :: IO a -> (Async a -> IO b) -> IO b+withAsyncBound = withAsyncUsing forkOS++-- | Like 'withAsync' but uses 'forkOn' internally.+withAsyncOn :: Int -> IO a -> (Async a -> IO b) -> IO b+withAsyncOn = withAsyncUsing . rawForkOn++-- | Like 'withAsync' but uses 'forkIOWithUnmask' internally.  The+-- child thread is passed a function that can be used to unmask+-- asynchronous exceptions.+withAsyncWithUnmask+  :: ((forall c. IO c -> IO c) -> IO a) -> (Async a -> IO b) -> IO b+withAsyncWithUnmask actionWith =+  withAsyncUsing rawForkIO (actionWith unsafeUnmask)++-- | Like 'withAsyncOn' but uses 'forkOnWithUnmask' internally.  The+-- child thread is passed a function that can be used to unmask+-- asynchronous exceptions+withAsyncOnWithUnmask+  :: Int -> ((forall c. IO c -> IO c) -> IO a) -> (Async a -> IO b) -> IO b+withAsyncOnWithUnmask cpu actionWith =+  withAsyncUsing (rawForkOn cpu) (actionWith unsafeUnmask)++withAsyncUsing :: (IO () -> IO ThreadId)+               -> IO a -> (Async a -> IO b) -> IO b+-- The bracket version works, but is slow.  We can do better by+-- hand-coding it:+withAsyncUsing doFork = \action inner -> do+  var <- newEmptyTMVarIO+  mask $ \restore -> do+    t <- doFork $ try (restore action) >>= atomically . putTMVar var+    let a = Async t (readTMVar var)+    r <- restore (inner a) `catchAll` \e -> do+      uninterruptibleCancel a+      throwIO e+    uninterruptibleCancel a+    return r++-- | Wait for an asynchronous action to complete, and return its+-- value.  If the asynchronous action threw an exception, then the+-- exception is re-thrown by 'wait'.+--+-- > wait = atomically . waitSTM+--+{-# INLINE wait #-}+wait :: Async a -> IO a+wait = atomically . waitSTM++-- | Wait for an asynchronous action to complete, and return either+-- @Left e@ if the action raised an exception @e@, or @Right a@ if it+-- returned a value @a@.+--+-- > waitCatch = atomically . waitCatchSTM+--+{-# INLINE waitCatch #-}+waitCatch :: Async a -> IO (Either SomeException a)+waitCatch = tryAgain . atomically . waitCatchSTM+  where+    -- See: https://github.com/simonmar/async/issues/14+    tryAgain f = f `catch` \BlockedIndefinitelyOnSTM -> f++-- | Check whether an 'Async' has completed yet.  If it has not+-- completed yet, then the result is @Nothing@, otherwise the result+-- is @Just e@ where @e@ is @Left x@ if the @Async@ raised an+-- exception @x@, or @Right a@ if it returned a value @a@.+--+-- > poll = atomically . pollSTM+--+{-# INLINE poll #-}+poll :: Async a -> IO (Maybe (Either SomeException a))+poll = atomically . pollSTM++-- | A version of 'wait' that can be used inside an STM transaction.+--+waitSTM :: Async a -> STM a+waitSTM a = do+   r <- waitCatchSTM a+   either throwSTM return r++-- | A version of 'waitCatch' that can be used inside an STM transaction.+--+{-# INLINE waitCatchSTM #-}+waitCatchSTM :: Async a -> STM (Either SomeException a)+waitCatchSTM (Async _ w) = w++-- | A version of 'poll' that can be used inside an STM transaction.+--+{-# INLINE pollSTM #-}+pollSTM :: Async a -> STM (Maybe (Either SomeException a))+pollSTM (Async _ w) = (Just <$> w) `orElse` return Nothing++-- | Cancel an asynchronous action by throwing the @AsyncCancelled@+-- exception to it, and waiting for the `Async` thread to quit.+-- Has no effect if the 'Async' has already completed.+--+-- > cancel a = throwTo (asyncThreadId a) AsyncCancelled <* waitCatch a+--+-- Note that 'cancel' will not terminate until the thread the 'Async'+-- refers to has terminated. This means that 'cancel' will block for+-- as long said thread blocks when receiving an asynchronous exception.+--+-- For example, it could block if:+--+-- * It's executing a foreign call, and thus cannot receive the asynchronous+-- exception;+-- * It's executing some cleanup handler after having received the exception,+-- and the handler is blocking.+{-# INLINE cancel #-}+cancel :: Async a -> IO ()+cancel a@(Async t _) = throwTo t AsyncCancelled <* waitCatch a++-- | The exception thrown by `cancel` to terminate a thread.+data AsyncCancelled = AsyncCancelled+  deriving (Show, Eq+#if __GLASGOW_HASKELL__ < 710+    ,Typeable+#endif+    )++instance Exception AsyncCancelled where+#if __GLASGOW_HASKELL__ >= 708+  fromException = asyncExceptionFromException+  toException = asyncExceptionToException+#endif++-- | Cancel an asynchronous action+--+-- This is a variant of `cancel`, but it is not interruptible.+{-# INLINE uninterruptibleCancel #-}+uninterruptibleCancel :: Async a -> IO ()+uninterruptibleCancel = uninterruptibleMask_ . cancel++-- | Cancel an asynchronous action by throwing the supplied exception+-- to it.+--+-- > cancelWith a x = throwTo (asyncThreadId a) x+--+-- The notes about the synchronous nature of 'cancel' also apply to+-- 'cancelWith'.+cancelWith :: Exception e => Async a -> e -> IO ()+cancelWith a@(Async t _) e = throwTo t e <* waitCatch a++-- | Wait for any of the supplied asynchronous operations to complete.+-- The value returned is a pair of the 'Async' that completed, and the+-- result that would be returned by 'wait' on that 'Async'.+--+-- If multiple 'Async's complete or have completed, then the value+-- returned corresponds to the first completed 'Async' in the list.+--+{-# INLINE waitAnyCatch #-}+waitAnyCatch :: [Async a] -> IO (Async a, Either SomeException a)+waitAnyCatch = atomically . waitAnyCatchSTM++-- | A version of 'waitAnyCatch' that can be used inside an STM transaction.+--+-- @since 2.1.0+waitAnyCatchSTM :: [Async a] -> STM (Async a, Either SomeException a)+waitAnyCatchSTM asyncs =+    foldr orElse retry $+      map (\a -> do r <- waitCatchSTM a; return (a, r)) asyncs++-- | Like 'waitAnyCatch', but also cancels the other asynchronous+-- operations as soon as one has completed.+--+waitAnyCatchCancel :: [Async a] -> IO (Async a, Either SomeException a)+waitAnyCatchCancel asyncs =+  waitAnyCatch asyncs `finally` mapM_ cancel asyncs++-- | Wait for any of the supplied @Async@s to complete.  If the first+-- to complete throws an exception, then that exception is re-thrown+-- by 'waitAny'.+--+-- If multiple 'Async's complete or have completed, then the value+-- returned corresponds to the first completed 'Async' in the list.+--+{-# INLINE waitAny #-}+waitAny :: [Async a] -> IO (Async a, a)+waitAny = atomically . waitAnySTM++-- | A version of 'waitAny' that can be used inside an STM transaction.+--+-- @since 2.1.0+waitAnySTM :: [Async a] -> STM (Async a, a)+waitAnySTM asyncs =+    foldr orElse retry $+      map (\a -> do r <- waitSTM a; return (a, r)) asyncs++-- | Like 'waitAny', but also cancels the other asynchronous+-- operations as soon as one has completed.+--+waitAnyCancel :: [Async a] -> IO (Async a, a)+waitAnyCancel asyncs =+  waitAny asyncs `finally` mapM_ cancel asyncs++-- | Wait for the first of two @Async@s to finish.+{-# INLINE waitEitherCatch #-}+waitEitherCatch :: Async a -> Async b+                -> IO (Either (Either SomeException a)+                              (Either SomeException b))+waitEitherCatch left right =+  tryAgain $ atomically (waitEitherCatchSTM left right)+  where+    -- See: https://github.com/simonmar/async/issues/14+    tryAgain f = f `catch` \BlockedIndefinitelyOnSTM -> f++-- | A version of 'waitEitherCatch' that can be used inside an STM transaction.+--+-- @since 2.1.0+waitEitherCatchSTM :: Async a -> Async b+                -> STM (Either (Either SomeException a)+                               (Either SomeException b))+waitEitherCatchSTM left right =+    (Left  <$> waitCatchSTM left)+      `orElse`+    (Right <$> waitCatchSTM right)++-- | Like 'waitEitherCatch', but also 'cancel's both @Async@s before+-- returning.+--+waitEitherCatchCancel :: Async a -> Async b+                      -> IO (Either (Either SomeException a)+                                    (Either SomeException b))+waitEitherCatchCancel left right =+  waitEitherCatch left right `finally` (cancel left >> cancel right)++-- | Wait for the first of two @Async@s to finish.  If the @Async@+-- that finished first raised an exception, then the exception is+-- re-thrown by 'waitEither'.+--+{-# INLINE waitEither #-}+waitEither :: Async a -> Async b -> IO (Either a b)+waitEither left right = atomically (waitEitherSTM left right)++-- | A version of 'waitEither' that can be used inside an STM transaction.+--+-- @since 2.1.0+waitEitherSTM :: Async a -> Async b -> STM (Either a b)+waitEitherSTM left right =+    (Left  <$> waitSTM left)+      `orElse`+    (Right <$> waitSTM right)++-- | Like 'waitEither', but the result is ignored.+--+{-# INLINE waitEither_ #-}+waitEither_ :: Async a -> Async b -> IO ()+waitEither_ left right = atomically (waitEitherSTM_ left right)++-- | A version of 'waitEither_' that can be used inside an STM transaction.+--+-- @since 2.1.0+waitEitherSTM_:: Async a -> Async b -> STM ()+waitEitherSTM_ left right =+    (void $ waitSTM left)+      `orElse`+    (void $ waitSTM right)++-- | Like 'waitEither', but also 'cancel's both @Async@s before+-- returning.+--+waitEitherCancel :: Async a -> Async b -> IO (Either a b)+waitEitherCancel left right =+  waitEither left right `finally` (cancel left >> cancel right)++-- | Waits for both @Async@s to finish, but if either of them throws+-- an exception before they have both finished, then the exception is+-- re-thrown by 'waitBoth'.+--+{-# INLINE waitBoth #-}+waitBoth :: Async a -> Async b -> IO (a,b)+waitBoth left right = atomically (waitBothSTM left right)++-- | A version of 'waitBoth' that can be used inside an STM transaction.+--+-- @since 2.1.0+waitBothSTM :: Async a -> Async b -> STM (a,b)+waitBothSTM left right = do+    a <- waitSTM left+           `orElse`+         (waitSTM right >> retry)+    b <- waitSTM right+    return (a,b)+++-- -----------------------------------------------------------------------------+-- Linking threads++data ExceptionInLinkedThread =+  forall a . ExceptionInLinkedThread (Async a) SomeException+#if __GLASGOW_HASKELL__ < 710+  deriving Typeable+#endif++instance Show ExceptionInLinkedThread where+  show (ExceptionInLinkedThread (Async t _) e) =+    "ExceptionInLinkedThread " ++ show t ++ " " ++ show e++instance Exception ExceptionInLinkedThread where+#if __GLASGOW_HASKELL__ >= 708+  fromException = asyncExceptionFromException+  toException = asyncExceptionToException+#endif++-- | Link the given @Async@ to the current thread, such that if the+-- @Async@ raises an exception, that exception will be re-thrown in+-- the current thread, wrapped in 'ExceptionInLinkedThread'.+--+-- 'link' ignores 'AsyncCancelled' exceptions thrown in the other thread,+-- so that it's safe to 'cancel' a thread you're linked to.  If you want+-- different behaviour, use 'linkOnly'.+--+link :: Async a -> IO ()+link = linkOnly (not . isCancel)++-- | Link the given @Async@ to the current thread, such that if the+-- @Async@ raises an exception, that exception will be re-thrown in+-- the current thread.  The supplied predicate determines which+-- exceptions in the target thread should be propagated to the source+-- thread.+--+linkOnly+  :: (SomeException -> Bool)  -- ^ return 'True' if the exception+                              -- should be propagated, 'False'+                              -- otherwise.+  -> Async a+  -> IO ()+linkOnly shouldThrow a = do+  me <- myThreadId+  void $ forkRepeat $ do+    r <- waitCatch a+    case r of+      Left e | shouldThrow e -> throwTo me (ExceptionInLinkedThread a e)+      _otherwise -> return ()++-- | Link two @Async@s together, such that if either raises an+-- exception, the same exception is re-thrown in the other @Async@,+-- wrapped in 'ExceptionInLinkedThread'.+--+-- 'link2' ignores 'AsyncCancelled' exceptions, so that it's possible+-- to 'cancel' either thread without cancelling the other.  If you+-- want different behaviour, use 'link2Only'.+--+link2 :: Async a -> Async b -> IO ()+link2 = link2Only (not . isCancel)++link2Only :: (SomeException -> Bool) -> Async a -> Async b -> IO ()+link2Only shouldThrow left@(Async tl _)  right@(Async tr _) =+  void $ forkRepeat $ do+    r <- waitEitherCatch left right+    case r of+      Left  (Left e) | shouldThrow e ->+        throwTo tr (ExceptionInLinkedThread left e)+      Right (Left e) | shouldThrow e ->+        throwTo tl (ExceptionInLinkedThread right e)+      _ -> return ()++isCancel :: SomeException -> Bool+isCancel e+  | Just AsyncCancelled <- fromException e = True+  | otherwise = False+++-- -----------------------------------------------------------------------------++-- | Run two @IO@ actions concurrently, and return the first to+-- finish.  The loser of the race is 'cancel'led.+--+-- > race left right =+-- >   withAsync left $ \a ->+-- >   withAsync right $ \b ->+-- >   waitEither a b+--+race :: IO a -> IO b -> IO (Either a b)++-- | Like 'race', but the result is ignored.+--+race_ :: IO a -> IO b -> IO ()++-- | Run two @IO@ actions concurrently, and return both results.  If+-- either action throws an exception at any time, then the other+-- action is 'cancel'led, and the exception is re-thrown by+-- 'concurrently'.+--+-- > concurrently left right =+-- >   withAsync left $ \a ->+-- >   withAsync right $ \b ->+-- >   waitBoth a b+concurrently :: IO a -> IO b -> IO (a,b)++#define USE_ASYNC_VERSIONS 0++#if USE_ASYNC_VERSIONS++race left right =+  withAsync left $ \a ->+  withAsync right $ \b ->+  waitEither a b++race_ left right =+  withAsync left $ \a ->+  withAsync right $ \b ->+  waitEither_ a b++concurrently left right =+  withAsync left $ \a ->+  withAsync right $ \b ->+  waitBoth a b++#else++-- MVar versions of race/concurrently+-- More ugly than the Async versions, but quite a bit faster.++-- race :: IO a -> IO b -> IO (Either a b)+race left right = concurrently' left right collect+  where+    collect m = do+        e <- m+        case e of+            Left ex -> throwIO ex+            Right r -> return r++-- race_ :: IO a -> IO b -> IO ()+race_ left right = void $ race left right++-- concurrently :: IO a -> IO b -> IO (a,b)+concurrently left right = concurrently' left right (collect [])+  where+    collect [Left a, Right b] _ = return (a,b)+    collect [Right b, Left a] _ = return (a,b)+    collect xs m = do+        e <- m+        case e of+            Left ex -> throwIO ex+            Right r -> collect (r:xs) m++concurrently' :: IO a -> IO b+             -> (IO (Either SomeException (Either a b)) -> IO r)+             -> IO r+concurrently' left right collect = do+    done <- newEmptyMVar+    mask $ \restore -> do+        -- Note: uninterruptibleMask here is because we must not allow+        -- the putMVar in the exception handler to be interrupted,+        -- otherwise the parent thread will deadlock when it waits for+        -- the thread to terminate.+        lid <- forkIO $ uninterruptibleMask_ $+          restore (left >>= putMVar done . Right . Left)+            `catchAll` (putMVar done . Left)+        rid <- forkIO $ uninterruptibleMask_ $+          restore (right >>= putMVar done . Right . Right)+            `catchAll` (putMVar done . Left)++        count <- newIORef (2 :: Int)+        let takeDone = do+                r <- takeMVar done      -- interruptible+                -- Decrement the counter so we know how many takes are left.+                -- Since only the parent thread is calling this, we can+                -- use non-atomic modifications.+                -- NB. do this *after* takeMVar, because takeMVar might be+                -- interrupted.+                modifyIORef count (subtract 1)+                return r++        let tryAgain f = f `catch` \BlockedIndefinitelyOnMVar -> f++            stop = do+                -- kill right before left, to match the semantics of+                -- the version using withAsync. (#27)+                uninterruptibleMask_ $ do+                  count' <- readIORef count+                  -- we only need to use killThread if there are still+                  -- children alive.  Note: forkIO here is because the+                  -- child thread could be in an uninterruptible+                  -- putMVar.+                  when (count' > 0) $+                    void $ forkIO $ do+                      throwTo rid AsyncCancelled+                      throwTo lid AsyncCancelled+                  -- ensure the children are really dead+                  replicateM_ count' (tryAgain $ takeMVar done)++        r <- collect (tryAgain $ takeDone) `onException` stop+        stop+        return r++#endif++-- | maps an @IO@-performing function over any @Traversable@ data+-- type, performing all the @IO@ actions concurrently, and returning+-- the original data structure with the arguments replaced by the+-- results.+--+-- If any of the actions throw an exception, then all other actions are+-- cancelled and the exception is re-thrown.+--+-- For example, @mapConcurrently@ works with lists:+--+-- > pages <- mapConcurrently getURL ["url1", "url2", "url3"]+--+mapConcurrently :: Traversable t => (a -> IO b) -> t a -> IO (t b)+mapConcurrently f = runConcurrently . traverse (Concurrently . f)++-- | `forConcurrently` is `mapConcurrently` with its arguments flipped+--+-- > pages <- forConcurrently ["url1", "url2", "url3"] $ \url -> getURL url+--+-- @since 2.1.0+forConcurrently :: Traversable t => t a -> (a -> IO b) -> IO (t b)+forConcurrently = flip mapConcurrently++-- | `mapConcurrently_` is `mapConcurrently` with the return value discarded,+-- just like @mapM_+mapConcurrently_ :: F.Foldable f => (a -> IO b) -> f a -> IO ()+mapConcurrently_ f = runConcurrently . F.foldMap (Concurrently . void . f)++-- | `forConcurrently_` is `forConcurrently` with the return value discarded,+-- just like @forM_+forConcurrently_ :: F.Foldable f => f a -> (a -> IO b) -> IO ()+forConcurrently_ = flip mapConcurrently_++-- | 'concurrently', but ignore the result values+--+-- @since 2.1.1+concurrently_ :: IO a -> IO b -> IO ()+concurrently_ left right = concurrently' left right (collect 0)+  where+    collect 2 _ = return ()+    collect i m = do+        e <- m+        case e of+            Left ex -> throwIO ex+            Right _ -> collect (i + 1 :: Int) m++-- | Perform the action in the given number of threads.+--+-- @since 2.1.1+replicateConcurrently :: Int -> IO a -> IO [a]+replicateConcurrently cnt = runConcurrently . sequenceA . replicate cnt . Concurrently++-- | Same as 'replicateConcurrently', but ignore the results.+--+-- @since 2.1.1+replicateConcurrently_ :: Int -> IO a -> IO ()+replicateConcurrently_ cnt = runConcurrently . F.fold . replicate cnt . Concurrently . void++-- -----------------------------------------------------------------------------++-- | A value of type @Concurrently a@ is an @IO@ operation that can be+-- composed with other @Concurrently@ values, using the @Applicative@+-- and @Alternative@ instances.+--+-- Calling @runConcurrently@ on a value of type @Concurrently a@ will+-- execute the @IO@ operations it contains concurrently, before+-- delivering the result of type @a@.+--+-- For example+--+-- > (page1, page2, page3)+-- >     <- runConcurrently $ (,,)+-- >     <$> Concurrently (getURL "url1")+-- >     <*> Concurrently (getURL "url2")+-- >     <*> Concurrently (getURL "url3")+--+newtype Concurrently a = Concurrently { runConcurrently :: IO a }++instance Functor Concurrently where+  fmap f (Concurrently a) = Concurrently $ f <$> a++instance Applicative Concurrently where+  pure = Concurrently . return+  Concurrently fs <*> Concurrently as =+    Concurrently $ (\(f, a) -> f a) <$> concurrently fs as++instance Alternative Concurrently where+  empty = Concurrently $ forever (threadDelay maxBound)+  Concurrently as <|> Concurrently bs =+    Concurrently $ either id id <$> race as bs++#if MIN_VERSION_base(4,9,0)+-- | Only defined by @async@ for @base >= 4.9@+--+-- @since 2.1.0+instance Semigroup a => Semigroup (Concurrently a) where+  (<>) = liftA2 (<>)++-- | @since 2.1.0+instance (Semigroup a, Monoid a) => Monoid (Concurrently a) where+  mempty = pure mempty+  mappend = (<>)+#else+-- | @since 2.1.0+instance Monoid a => Monoid (Concurrently a) where+  mempty = pure mempty+  mappend = liftA2 mappend+#endif++-- ----------------------------------------------------------------------------++-- | Fork a thread that runs the supplied action, and if it raises an+-- exception, re-runs the action.  The thread terminates only when the+-- action runs to completion without raising an exception.+forkRepeat :: IO a -> IO ThreadId+forkRepeat action =+  mask $ \restore ->+    let go = do r <- tryAll (restore action)+                case r of+                  Left _ -> go+                  _      -> return ()+    in forkIO go++catchAll :: IO a -> (SomeException -> IO a) -> IO a+catchAll = catch++tryAll :: IO a -> IO (Either SomeException a)+tryAll = try++-- A version of forkIO that does not include the outer exception+-- handler: saves a bit of time when we will be installing our own+-- exception handler.+{-# INLINE rawForkIO #-}+rawForkIO :: IO () -> IO ThreadId+rawForkIO action = IO $ \ s ->+   case (fork# action s) of (# s1, tid #) -> (# s1, ThreadId tid #)++{-# INLINE rawForkOn #-}+rawForkOn :: Int -> IO () -> IO ThreadId+rawForkOn (I# cpu) action = IO $ \ s ->+   case (forkOn# cpu action s) of (# s1, tid #) -> (# s1, ThreadId tid #)
hspec-meta.cabal view
@@ -1,19 +1,20 @@--- This file has been generated from package.yaml by hpack version 0.21.2.+cabal-version: 1.12++-- This file has been generated from package.yaml by hpack version 0.30.0. -- -- see: https://github.com/sol/hpack ----- hash: a127653a989c7ff69649480ddd6226dd61f3e9eded4419b7214fa6deb0da8f7c+-- hash: b9089eec6ed9c224790a988a565a1089609beb176824d8eafe653098556dc71c  name:             hspec-meta-version:          2.4.6+version:          2.5.6 license:          MIT license-file:     LICENSE-copyright:        (c) 2011-2017 Simon Hengel,+copyright:        (c) 2011-2018 Simon Hengel,                   (c) 2011-2012 Trystan Spangler,                   (c) 2011 Greg Weber maintainer:       Simon Hengel <sol@typeful.net> build-type:       Simple-cabal-version:    >= 1.10 category:         Testing stability:        experimental bug-reports:      https://github.com/hspec/hspec/issues@@ -21,7 +22,6 @@ 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.- extra-source-files:     CHANGES.markdown @@ -30,28 +30,6 @@   location: https://github.com/hspec/hspec  library-  ghc-options: -Wall-  hs-source-dirs:-      src-      hspec-core/src-      hspec-core/vendor/-  build-depends:-      HUnit-    , QuickCheck >=2.5.1-    , ansi-terminal-    , array-    , async-    , base ==4.*-    , call-stack-    , deepseq-    , directory-    , filepath-    , hspec-expectations >=0.8.0-    , quickcheck-io-    , random-    , setenv-    , time-    , transformers >=0.2.2.0   exposed-modules:       Test.Hspec.Meta   other-modules:@@ -59,20 +37,23 @@       Test.Hspec.Core       Test.Hspec.Discover       Test.Hspec.Formatters-      Test.Hspec.HUnit       Test.Hspec.QuickCheck       Test.Hspec.Runner+      Test.Hspec.Core.Clock       Test.Hspec.Core.Compat       Test.Hspec.Core.Config+      Test.Hspec.Core.Config.Options+      Test.Hspec.Core.Config.Util       Test.Hspec.Core.Example+      Test.Hspec.Core.Example.Location       Test.Hspec.Core.FailureReport+      Test.Hspec.Core.Format       Test.Hspec.Core.Formatters       Test.Hspec.Core.Formatters.Diff       Test.Hspec.Core.Formatters.Free       Test.Hspec.Core.Formatters.Internal       Test.Hspec.Core.Formatters.Monad       Test.Hspec.Core.Hooks-      Test.Hspec.Core.Options       Test.Hspec.Core.QuickCheck       Test.Hspec.Core.QuickCheckUtil       Test.Hspec.Core.Runner@@ -82,8 +63,32 @@       Test.Hspec.Core.Timer       Test.Hspec.Core.Tree       Test.Hspec.Core.Util+      Control.Concurrent.Async       Data.Algorithm.Diff       Paths_hspec_meta+  ghc-options: -Wall+  hs-source-dirs:+      src+      hspec-core/src+      hspec-core/vendor+  build-depends:+      HUnit+    , QuickCheck >=2.5.1+    , ansi-terminal+    , array+    , base ==4.*+    , call-stack+    , clock+    , deepseq+    , directory+    , filepath+    , hspec-expectations >=0.8.0+    , quickcheck-io+    , random+    , setenv+    , stm >=2.2+    , time+    , transformers >=0.2.2.0   default-language: Haskell2010  executable hspec-meta-discover@@ -97,9 +102,9 @@     , QuickCheck >=2.5.1     , ansi-terminal     , array-    , async     , base ==4.*     , call-stack+    , clock     , deepseq     , directory     , filepath@@ -107,6 +112,7 @@     , quickcheck-io     , random     , setenv+    , stm >=2.2     , time     , transformers >=0.2.2.0   other-modules:
src/Test/Hspec/Discover.hs view
@@ -11,16 +11,10 @@ ) where  import           Prelude hiding (mapM)-import           Control.Applicative-import           Data.Maybe-import           Data.List-import           Data.Traversable-import           Control.Monad.Trans.State  import           Test.Hspec.Core.Spec import           Test.Hspec.Core.Runner import           Test.Hspec.Formatters-import           Test.Hspec.Core.Util (safeTry)  class IsFormatter a where   toFormatter :: a -> IO Formatter@@ -37,62 +31,4 @@   hspecWith defaultConfig {configFormatter = Just f} spec  postProcessSpec :: FilePath -> Spec -> Spec-postProcessSpec = locationHeuristicFromFile--locationHeuristicFromFile :: FilePath -> Spec -> Spec-locationHeuristicFromFile file spec = do-  mInput <- either (const Nothing) Just <$> (runIO . safeTry . readFile) file-  let lookupLoc = maybe (\_ _ _ -> Nothing) (lookupLocation file)  mInput-  runIO (runSpecM spec) >>= fromSpecList . addLoctions lookupLoc--addLoctions :: (Int -> Int -> String -> Maybe Location) -> [SpecTree a] -> [SpecTree a]-addLoctions lookupLoc = map (fmap f) . enumerate-  where-    f :: ((Int, Int), Item a) -> Item a-    f ((n, total), item) = item {itemLocation = itemLocation item <|> lookupLoc n total (itemRequirement item)}--type EnumerateM = State [(String, Int)]--enumerate :: [SpecTree a] -> [Tree (ActionWith a) ((Int, Int), (Item a))]-enumerate tree = (mapM (traverse addPosition) tree >>= mapM (traverse addTotal)) `evalState` []-  where-    addPosition :: Item a -> EnumerateM (Int, Item a)-    addPosition item = (,) <$> getOccurrence (itemRequirement item) <*> pure item--    addTotal :: (Int, Item a) -> EnumerateM ((Int, Int), Item a)-    addTotal (n, item) = do-      total <- getTotal (itemRequirement item)-      return ((n, total), item)--    getTotal :: String -> EnumerateM Int-    getTotal requirement = do-      gets $ fromMaybe err . lookup requirement-      where-        err = error ("Test.Hspec.Discover.getTotal: No entry for requirement " ++ show requirement ++ "!")--    getOccurrence :: String -> EnumerateM Int-    getOccurrence requirement = do-      xs <- get-      let n = maybe 1 succ (lookup requirement xs)-      put ((requirement, n) : filter ((/= requirement) . fst) xs)-      return n--lookupLocation :: FilePath -> String -> Int -> Int -> String -> Maybe Location-lookupLocation file input n total requirement = loc-  where-    loc :: Maybe Location-    loc = Location file <$> line <*> pure 0 <*> pure BestEffort--    line :: Maybe Int-    line = case occurrences of-      xs | length xs == total -> Just (xs !! pred n)-      _ -> Nothing--    occurrences :: [Int]-    occurrences = map fst (filter p inputLines)-      where-        p :: (Int, String) -> Bool-        p = isInfixOf (show requirement) . snd--    inputLines :: [(Int, String)]-    inputLines = zip [1..] (lines input)+postProcessSpec _ = id
− src/Test/Hspec/HUnit.hs
@@ -1,24 +0,0 @@-module Test.Hspec.HUnit {-# DEPRECATED "use \"Test.Hspec.Contrib.HUnit\" from package @hspec-contrib@ instead" #-}-(--- * Interoperability with HUnit-  fromHUnitTest-) where--import           Test.Hspec.Core.Spec-import           Test.HUnit (Test (..))---- |--- Convert a HUnit test suite to a spec.  This can be used to run existing--- HUnit tests with Hspec.-fromHUnitTest :: Test -> Spec-fromHUnitTest t = case t of-  TestList xs -> mapM_ go xs-  x -> go x-  where-    go :: Test -> Spec-    go t_ = case t_ of-      TestLabel s (TestCase e) -> it s e-      TestLabel s (TestList xs) -> describe s (mapM_ go xs)-      TestLabel s x -> describe s (go x)-      TestList xs -> describe "<unlabeled>" (mapM_ go xs)-      TestCase e -> it "<unlabeled>" e