hspec-meta 2.10.5 → 2.11.2
raw patch · 64 files changed
+3071/−3681 lines, 64 filesdep +HUnitdep +haskell-lexerdep +hspec-expectationsdep −clockdep −ghcdep −ghc-boot-thdep ~QuickCheckdep ~ansi-terminaldep ~basePVP ok
version bump matches the API change (PVP)
Dependencies added: HUnit, haskell-lexer, hspec-expectations, process, tf-random
Dependencies removed: clock, ghc, ghc-boot-th, setenv
Dependency ranges changed: QuickCheck, ansi-terminal, base, call-stack, quickcheck-io
API changes (from Hackage documentation)
- Test.Hspec.Meta: anyArithException :: Selector ArithException
- Test.Hspec.Meta: anyErrorCall :: Selector ErrorCall
- Test.Hspec.Meta: anyException :: Selector SomeException
- Test.Hspec.Meta: anyIOException :: Selector IOException
- Test.Hspec.Meta: errorCall :: String -> Selector ErrorCall
- Test.Hspec.Meta: expectationFailure :: HasCallStack => String -> Expectation
- Test.Hspec.Meta: infix 1 `shouldThrow`
- Test.Hspec.Meta: shouldBe :: (HasCallStack, Show a, Eq a) => a -> a -> Expectation
- Test.Hspec.Meta: shouldContain :: (HasCallStack, Show a, Eq a) => [a] -> [a] -> Expectation
- Test.Hspec.Meta: shouldEndWith :: (HasCallStack, Show a, Eq a) => [a] -> [a] -> Expectation
- Test.Hspec.Meta: shouldMatchList :: (HasCallStack, Show a, Eq a) => [a] -> [a] -> Expectation
- Test.Hspec.Meta: shouldNotBe :: (HasCallStack, Show a, Eq a) => a -> a -> Expectation
- Test.Hspec.Meta: shouldNotContain :: (HasCallStack, Show a, Eq a) => [a] -> [a] -> Expectation
- Test.Hspec.Meta: shouldNotReturn :: (HasCallStack, Show a, Eq a) => IO a -> a -> Expectation
- Test.Hspec.Meta: shouldNotSatisfy :: (HasCallStack, Show a) => a -> (a -> Bool) -> Expectation
- Test.Hspec.Meta: shouldReturn :: (HasCallStack, Show a, Eq a) => IO a -> a -> Expectation
- Test.Hspec.Meta: shouldSatisfy :: (HasCallStack, Show a) => a -> (a -> Bool) -> Expectation
- Test.Hspec.Meta: shouldStartWith :: (HasCallStack, Show a, Eq a) => [a] -> [a] -> Expectation
- Test.Hspec.Meta: shouldThrow :: (HasCallStack, Exception e) => IO a -> Selector e -> Expectation
- Test.Hspec.Meta: type Expectation = Assertion
- Test.Hspec.Meta: type HasCallStack = ?callStack :: CallStack
- Test.Hspec.Meta: type Selector a = (a -> Bool)
+ Test.Hspec.Meta: sequential :: SpecWith a -> SpecWith a
Files
- LICENSE +1/−1
- hspec-core/src/GetOpt/Declarative/Interpret.hs +3/−4
- hspec-core/src/GetOpt/Declarative/Util.hs +1/−15
- hspec-core/src/Test/Hspec/Core/Compat.hs +67/−20
- hspec-core/src/Test/Hspec/Core/Config.hs +18/−6
- hspec-core/src/Test/Hspec/Core/Config/Definition.hs +82/−19
- hspec-core/src/Test/Hspec/Core/Config/Options.hs +1/−8
- hspec-core/src/Test/Hspec/Core/Example.hs +47/−40
- hspec-core/src/Test/Hspec/Core/Example/Location.hs +0/−2
- hspec-core/src/Test/Hspec/Core/FailureReport.hs +2/−2
- hspec-core/src/Test/Hspec/Core/Format.hs +29/−18
- hspec-core/src/Test/Hspec/Core/Formatters.hs +2/−3
- hspec-core/src/Test/Hspec/Core/Formatters/Diff.hs +66/−4
- hspec-core/src/Test/Hspec/Core/Formatters/Internal.hs +56/−21
- hspec-core/src/Test/Hspec/Core/Formatters/Pretty.hs +32/−39
- hspec-core/src/Test/Hspec/Core/Formatters/Pretty/Parser.hs +78/−366
- hspec-core/src/Test/Hspec/Core/Formatters/Pretty/Parser/Parser.hs +34/−0
- hspec-core/src/Test/Hspec/Core/Formatters/Pretty/Parser/Types.hs +0/−21
- hspec-core/src/Test/Hspec/Core/Formatters/V1.hs +4/−291
- hspec-core/src/Test/Hspec/Core/Formatters/V1/Internal.hs +359/−0
- hspec-core/src/Test/Hspec/Core/Formatters/V1/Monad.hs +8/−2
- hspec-core/src/Test/Hspec/Core/Formatters/V2.hs +108/−50
- hspec-core/src/Test/Hspec/Core/Hooks.hs +12/−8
- hspec-core/src/Test/Hspec/Core/QuickCheckUtil.hs +29/−9
- hspec-core/src/Test/Hspec/Core/Runner.hs +205/−142
- hspec-core/src/Test/Hspec/Core/Runner/Eval.hs +88/−117
- hspec-core/src/Test/Hspec/Core/Runner/JobQueue.hs +116/−0
- hspec-core/src/Test/Hspec/Core/Runner/PrintSlowSpecItems.hs +5/−3
- hspec-core/src/Test/Hspec/Core/Runner/Result.hs +56/−3
- hspec-core/src/Test/Hspec/Core/Shuffle.hs +4/−4
- hspec-core/src/Test/Hspec/Core/Spec.hs +7/−5
- hspec-core/src/Test/Hspec/Core/Spec/Monad.hs +2/−0
- hspec-core/src/Test/Hspec/Core/Timer.hs +0/−1
- hspec-core/src/Test/Hspec/Core/Tree.hs +26/−17
- hspec-core/src/Test/Hspec/Core/Util.hs +33/−2
- hspec-core/vendor/Control/Concurrent/Async.hs +0/−963
- hspec-core/vendor/Data/Algorithm/Diff.hs +0/−115
- hspec-core/vendor/stm-2.5.0.1/Control/Concurrent/STM/TMVar.hs +0/−168
- hspec-discover/src/Test/Hspec/Discover/Config.hs +1/−1
- hspec-discover/src/Test/Hspec/Discover/Run.hs +2/−2
- hspec-discover/src/Test/Hspec/Discover/Sort.hs +1/−1
- hspec-meta.cabal +40/−42
- hspec/src/Test/Hspec.hs +96/−0
- hspec/src/Test/Hspec/Discover.hs +34/−0
- hspec/src/Test/Hspec/Formatters.hs +8/−0
- hspec/src/Test/Hspec/QuickCheck.hs +60/−0
- hspec/src/Test/Hspec/Runner.hs +2/−0
- src/Test/Hspec.hs +0/−95
- src/Test/Hspec/Discover.hs +0/−33
- src/Test/Hspec/Formatters.hs +0/−7
- src/Test/Hspec/QuickCheck.hs +0/−28
- src/Test/Hspec/Runner.hs +0/−2
- vendor/Control/Concurrent/Async.hs +963/−0
- vendor/Data/Algorithm/Diff.hs +115/−0
- vendor/HUnit-1.6.2.0/src/Test/HUnit.hs +0/−80
- vendor/HUnit-1.6.2.0/src/Test/HUnit/Base.hs +0/−361
- vendor/HUnit-1.6.2.0/src/Test/HUnit/Lang.hs +0/−104
- vendor/HUnit-1.6.2.0/src/Test/HUnit/Terminal.hs +0/−42
- vendor/HUnit-1.6.2.0/src/Test/HUnit/Text.hs +0/−152
- vendor/hspec-expectations-0.8.2/src/Test/Hspec/Expectations.hs +0/−189
- vendor/hspec-expectations-0.8.2/src/Test/Hspec/Expectations/Contrib.hs +0/−26
- vendor/hspec-expectations-0.8.2/src/Test/Hspec/Expectations/Matcher.hs +0/−26
- vendor/stm-2.5.0.1/Control/Concurrent/STM/TMVar.hs +168/−0
- version.yaml +0/−1
LICENSE view
@@ -1,4 +1,4 @@-Copyright (c) 2011-2022 Simon Hengel <sol@typeful.net>+Copyright (c) 2011-2023 Simon Hengel <sol@typeful.net> Copyright (c) 2011-2012 Trystan Spangler <trystan.s@comcast.net> Copyright (c) 2011-2011 Greg Weber <greg@gregweber.info>
hspec-core/src/GetOpt/Declarative/Interpret.hs view
@@ -7,13 +7,12 @@ import Prelude () import Test.Hspec.Core.Compat-import Data.Maybe import System.Console.GetOpt (OptDescr, ArgOrder(..), getOpt) import qualified System.Console.GetOpt as GetOpt import GetOpt.Declarative.Types-import GetOpt.Declarative.Util (mkUsageInfo, mapOptDescr)+import GetOpt.Declarative.Util (mkUsageInfo) data InvalidArgument = InvalidArgument String String @@ -31,7 +30,7 @@ usage :: String usage = "Usage: " ++ prog ++ " [OPTION]...\n\n"- ++ (intercalate "\n" $ map (uncurry mkUsageInfo) documentedOptions)+ ++ intercalate "\n" (map (uncurry mkUsageInfo) documentedOptions) addHelpFlag :: [(a, [OptDescr a1])] -> [(a, [OptDescr (Maybe a1)])] addHelpFlag opts = case opts of@@ -41,7 +40,7 @@ help = Nothing noHelp :: [OptDescr a] -> [OptDescr (Maybe a)]- noHelp = map (mapOptDescr Just)+ noHelp = map (fmap Just) parseWithHelp :: [OptDescr (Maybe (config -> Either InvalidArgument config))] -> config -> [String] -> Maybe (Either String config) parseWithHelp options config args = case getOpt Permute options args of
hspec-core/src/GetOpt/Declarative/Util.hs view
@@ -1,5 +1,5 @@ {-# LANGUAGE CPP #-}-module GetOpt.Declarative.Util (mkUsageInfo, mapOptDescr) where+module GetOpt.Declarative.Util (mkUsageInfo) where import Prelude () import Test.Hspec.Core.Compat@@ -31,17 +31,3 @@ Option "" ["[no-]" ++ optionA] arg help : condenseNoOptions ys x : xs -> x : condenseNoOptions xs [] -> []--mapOptDescr :: (a -> b) -> OptDescr a -> OptDescr b-#if MIN_VERSION_base(4,7,0)-mapOptDescr = fmap-#else-mapOptDescr f opt = case opt of- Option short long arg help -> Option short long (mapArgDescr f arg) help--mapArgDescr :: (a -> b) -> ArgDescr a -> ArgDescr b-mapArgDescr f arg = case arg of- NoArg a -> NoArg (f a)- ReqArg parse name -> ReqArg (fmap f parse) name- OptArg parse name -> OptArg (fmap f parse) name-#endif
hspec-core/src/Test/Hspec/Core/Compat.hs view
@@ -4,6 +4,7 @@ , module Test.Hspec.Core.Compat ) where +import Control.Exception as Imports import Control.Arrow as Imports ((>>>), (&&&), first, second) import Control.Applicative as Imports import Control.Monad as Imports hiding (@@ -15,10 +16,16 @@ , sequence , sequence_ )+import Data.Maybe as Imports import Data.Foldable as Imports+import Data.CallStack as Imports (HasCallStack) +import System.IO+import System.Exit+import System.Environment+ #if MIN_VERSION_base(4,11,0)-import Data.Functor as Imports+import Data.Functor as Imports ((<&>)) #endif import Data.Traversable as Imports@@ -27,13 +34,12 @@ stripPrefix , isPrefixOf , isInfixOf+ , isSuffixOf , intercalate , inits , tails , sortBy-#if MIN_VERSION_base(4,8,0) , sortOn-#endif ) import Prelude as Imports hiding (@@ -57,21 +63,22 @@ , sequence , sequence_ , sum-#if !MIN_VERSION_base(4,6,0)- , catch-#endif ) -import Data.Typeable (Typeable, typeOf, typeRepTyCon)+import Data.Typeable (Typeable, typeOf, typeRepTyCon, tyConModule, tyConName) import Data.IORef as Imports +#if MIN_VERSION_base(4,12,0)+import GHC.ResponseFile as Imports (unescapeArgs)+#else+import Data.Char+#endif+ #if MIN_VERSION_base(4,6,0) import Text.Read as Imports (readMaybe) import System.Environment as Imports (lookupEnv) #else-import Control.Exception import Text.Read-import System.Environment import qualified Text.ParserCombinators.ReadP as P #endif @@ -79,12 +86,13 @@ import Data.Ord (comparing) #endif -import Data.Typeable (tyConModule, tyConName)+#if MIN_VERSION_base(4,7,0)+import Data.Bool as Imports (bool)+#endif+ import Control.Concurrent -#if MIN_VERSION_base(4,9,0)-import Control.Exception as Imports (interruptible)-#else+#if !MIN_VERSION_base(4,9,0) import GHC.IO #endif @@ -104,7 +112,7 @@ atomicWriteIORef :: IORef a -> a -> IO () atomicWriteIORef ref a = do x <- atomicModifyIORef ref (\_ -> (a, ()))- x `seq` return ()+ x `seq` pass -- | Parse a string using the 'Read' instance. -- Succeeds if there is exactly one valid result.@@ -160,14 +168,53 @@ guarded :: Alternative m => (a -> Bool) -> a -> m a guarded p a = if p a then pure a else empty -#if !MIN_VERSION_base(4,8,0)-sortOn :: Ord b => (a -> b) -> [a] -> [a]-sortOn f =- map snd . sortBy (comparing fst) . map (\x -> let y = f x in y `seq` (y, x))-#endif- #if !MIN_VERSION_base(4,11,0) infixl 1 <&> (<&>) :: Functor f => f a -> (a -> b) -> f b (<&>) = flip (<$>)+#endif++endsWith :: Eq a => [a] -> [a] -> Bool+endsWith = flip isSuffixOf++pass :: Applicative m => m ()+pass = pure ()++die :: String -> IO a+die err = do+ name <- getProgName+ hPutStrLn stderr $ name <> ": " <> err+ exitFailure++#if !MIN_VERSION_base(4,12,0)+unescapeArgs :: String -> [String]+unescapeArgs = filter (not . null) . unescape++data Quoting = NoneQ | SngQ | DblQ++unescape :: String -> [String]+unescape args = reverse . map reverse $ go args NoneQ False [] []+ where+ -- n.b., the order of these cases matters; these are cribbed from gcc+ -- case 1: end of input+ go [] _q _bs a as = a:as+ -- case 2: back-slash escape in progress+ go (c:cs) q True a as = go cs q False (c:a) as+ -- case 3: no back-slash escape in progress, but got a back-slash+ go (c:cs) q False a as+ | '\\' == c = go cs q True a as+ -- case 4: single-quote escaping in progress+ go (c:cs) SngQ False a as+ | '\'' == c = go cs NoneQ False a as+ | otherwise = go cs SngQ False (c:a) as+ -- case 5: double-quote escaping in progress+ go (c:cs) DblQ False a as+ | '"' == c = go cs NoneQ False a as+ | otherwise = go cs DblQ False (c:a) as+ -- case 6: no escaping is in progress+ go (c:cs) NoneQ False a as+ | isSpace c = go cs NoneQ False [] (a:as)+ | '\'' == c = go cs SngQ False a as+ | '"' == c = go cs DblQ False a as+ | otherwise = go cs NoneQ False (c:a) as #endif
hspec-core/src/Test/Hspec/Core/Config.hs view
@@ -18,8 +18,7 @@ import Prelude () import Test.Hspec.Core.Compat -import Control.Exception-import Data.Maybe+import GHC.IO.Exception (IOErrorType(UnsupportedOperation)) import System.IO import System.IO.Error import System.Exit@@ -30,11 +29,21 @@ import Test.Hspec.Core.Util import Test.Hspec.Core.Config.Options-import Test.Hspec.Core.Config.Definition (Config(..), ColorMode(..), UnicodeMode(..), defaultConfig, filterOr)+import Test.Hspec.Core.Config.Definition (Config(..), ColorMode(..), UnicodeMode(..), mkDefaultConfig, filterOr) import Test.Hspec.Core.FailureReport import Test.Hspec.Core.QuickCheckUtil (mkGen) import Test.Hspec.Core.Example (Params(..), defaultParams)+import qualified Test.Hspec.Core.Formatters.V2 as V2 +defaultConfig :: Config+defaultConfig = mkDefaultConfig $ map (fmap V2.formatterToFormat) [+ ("checks", V2.checks)+ , ("specdoc", V2.specdoc)+ , ("progress", V2.progress)+ , ("failed-examples", V2.failed_examples)+ , ("silent", V2.silent)+ ]+ -- | Add a filter predicate to config. If there is already a filter predicate, -- then combine them with `||`. configAddFilter :: (Path -> Bool) -> Config -> Config@@ -95,7 +104,7 @@ -- -- 1. @~/.hspec@ (a config file in the user's home directory) -- 1. @.hspec@ (a config file in the current working directory)--- 1. the environment variable @HSPEC_OPTIONS@+-- 1. [environment variables starting with @HSPEC_@](https://hspec.github.io/options.html#specifying-options-through-environment-variables) -- 1. the provided list of command-line options (the second argument to @readConfig@) -- -- (precedence from low to high)@@ -140,10 +149,13 @@ readGlobalConfigFile :: IO (Maybe ConfigFile) readGlobalConfigFile = do- mHome <- tryJust (guard . isDoesNotExistError) getHomeDirectory+ mHome <- tryJust (guard . isPotentialHomeDirError) getHomeDirectory case mHome of Left _ -> return Nothing Right home -> readConfigFile (home </> ".hspec")+ where+ isPotentialHomeDirError e =+ isDoesNotExistError e || ioeGetErrorType e == UnsupportedOperation readLocalConfigFile :: IO (Maybe ConfigFile) readLocalConfigFile = do@@ -155,7 +167,7 @@ readConfigFile :: FilePath -> IO (Maybe ConfigFile) readConfigFile name = do exists <- doesFileExist name- if exists then Just . (,) name . words <$> readFile name else return Nothing+ if exists then Just . (,) name . unescapeArgs <$> readFile name else return Nothing exitWithMessage :: ExitCode -> String -> IO a exitWithMessage err msg = do
hspec-core/src/Test/Hspec/Core/Config/Definition.hs view
@@ -4,7 +4,7 @@ , ColorMode(..) , UnicodeMode(..) , filterOr-, defaultConfig+, mkDefaultConfig , commandLineOnlyOptions , formatterOptions@@ -20,11 +20,14 @@ import Prelude () import Test.Hspec.Core.Compat +import System.Directory (getTemporaryDirectory, removeFile)+import System.IO (openTempFile, hClose)+import System.Process (system)+ import Test.Hspec.Core.Example (Params(..), defaultParams) import Test.Hspec.Core.Format (Format, FormatConfig) import Test.Hspec.Core.Formatters.Pretty (pretty2)-import qualified Test.Hspec.Core.Formatters.V1 as V1-import qualified Test.Hspec.Core.Formatters.V2 as V2+import qualified Test.Hspec.Core.Formatters.V1.Monad as V1 import Test.Hspec.Core.Util import GetOpt.Declarative@@ -43,6 +46,7 @@ , configFailOnEmpty :: Bool , configFailOnFocused :: Bool , configFailOnPending :: Bool+, configFailOnEmptyDescription :: Bool , configPrintSlowItems :: Maybe Int , configPrintCpuTime :: Bool , configFailFast :: Bool@@ -65,24 +69,37 @@ , configColorMode :: ColorMode , configUnicodeMode :: UnicodeMode , configDiff :: Bool+, configDiffContext :: Maybe Int++-- |+-- An action that is used to print diffs. The first argument is the value of+-- `configDiffContext`. The remaining two arguments are the @expected@ and+-- @actual@ value.+--+-- @since 2.10.6+, configExternalDiff :: Maybe (Maybe Int -> String -> String -> IO ())+ , configPrettyPrint :: Bool , configPrettyPrintFunction :: Bool -> String -> String -> (String, String) , configTimes :: Bool-, configAvailableFormatters :: [(String, FormatConfig -> IO Format)]+, configExpertMode :: Bool -- ^ @since 2.11.2+, configAvailableFormatters :: [(String, FormatConfig -> IO Format)] -- ^ @since 2.9.0 , configFormat :: Maybe (FormatConfig -> IO Format)-, configFormatter :: Maybe V1.Formatter -- ^ deprecated, use `configFormat` instead+, configFormatter :: Maybe V1.Formatter , configHtmlOutput :: Bool , configConcurrentJobs :: Maybe Int }+{-# DEPRECATED configFormatter "Use [@useFormatter@](https://hackage.haskell.org/package/hspec-api/docs/Test-Hspec-Api-Formatters-V1.html#v:useFormatter) instead." #-} -defaultConfig :: Config-defaultConfig = Config {+mkDefaultConfig :: [(String, FormatConfig -> IO Format)] -> Config+mkDefaultConfig formatters = Config { configIgnoreConfigFile = False , configDryRun = False , configFocusedOnly = False , configFailOnEmpty = False , configFailOnFocused = False , configFailOnPending = False+, configFailOnEmptyDescription = False , configPrintSlowItems = Nothing , configPrintCpuTime = False , configFailFast = False@@ -101,22 +118,36 @@ , configColorMode = ColorAuto , configUnicodeMode = UnicodeAuto , configDiff = True+, configDiffContext = Just defaultDiffContext+, configExternalDiff = Nothing , configPrettyPrint = True , configPrettyPrintFunction = pretty2 , configTimes = False-, configAvailableFormatters = map (fmap V2.formatterToFormat) [- ("checks", V2.checks)- , ("specdoc", V2.specdoc)- , ("progress", V2.progress)- , ("failed-examples", V2.failed_examples)- , ("silent", V2.silent)- ]+, configExpertMode = False+, configAvailableFormatters = formatters , configFormat = Nothing , configFormatter = Nothing , configHtmlOutput = False , configConcurrentJobs = Nothing } +defaultDiffContext :: Int+defaultDiffContext = 3++externalDiff :: String -> String -> String -> IO ()+externalDiff command expected actual = do+ tmp <- getTemporaryDirectory+ withTempFile tmp "hspec-expected" expected $ \ expectedFile -> do+ withTempFile tmp "hspec-actual" actual $ \ actualFile -> do+ void . system $ unwords [command, expectedFile, actualFile]++withTempFile :: FilePath -> FilePath -> String -> (FilePath -> IO a) -> IO a+withTempFile dir file contents action = do+ bracket (openTempFile dir file) (removeFile . fst) $ \ (path, h) -> do+ hClose h+ writeFile path contents+ action path+ option :: String -> OptionSetter config -> String -> Option config option name arg help = Option name Nothing arg help True @@ -137,20 +168,33 @@ formatterOptions :: [(String, FormatConfig -> IO Format)] -> [Option Config] formatterOptions formatters = [- mkOption "format" (Just 'f') (argument "FORMATTER" readFormatter setFormatter) helpForFormat+ mkOption "format" (Just 'f') (argument "NAME" readFormatter setFormatter) helpForFormat , mkFlag "color" setColor "colorize the output" , mkFlag "unicode" setUnicode "output unicode" , mkFlag "diff" setDiff "show colorized diffs"+ , option "diff-context" (argument "N" readDiffContext setDiffContext) $ unlines [+ "output N lines of diff context (default: " <> show defaultDiffContext <> ")"+ , "use a value of 'full' to see the full context"+ ]+ , option "diff-command" (argument "CMD" return setDiffCommand) "use an external diff command\nexample: --diff-command=\"git diff\"" , mkFlag "pretty" setPretty "try to pretty-print diff values" , mkFlag "times" setTimes "report times for individual spec items" , mkOptionNoArg "print-cpu-time" Nothing setPrintCpuTime "include used CPU time in summary" , printSlowItemsOption+ , mkFlag "expert" setExpertMode "be less verbose" -- undocumented for now, as we probably want to change this to produce a -- standalone HTML report in the future , undocumented $ mkOptionNoArg "html" Nothing setHtml "produce HTML output" ] where+ setDiffCommand :: String -> Config -> Config+ setDiffCommand command config = config {+ configExternalDiff = case strip command of+ "" -> Nothing+ _ -> Just $ \ _context -> externalDiff command+ }+ setHtml config = config {configHtmlOutput = True} helpForFormat :: String@@ -171,6 +215,16 @@ setDiff :: Bool -> Config -> Config setDiff v config = config {configDiff = v} + readDiffContext :: String -> Maybe (Maybe Int)+ readDiffContext input = case input of+ "full" -> Just Nothing+ _ -> case readMaybe input of+ Nothing -> Nothing+ mn -> Just (find (>= 0) mn)++ setDiffContext :: Maybe Int -> Config -> Config+ setDiffContext value c = c { configDiffContext = value }+ setPretty :: Bool -> Config -> Config setPretty v config = config {configPrettyPrint = v} @@ -179,6 +233,9 @@ setPrintCpuTime config = config {configPrintCpuTime = True} + setExpertMode :: Bool -> Config -> Config+ setExpertMode v config = config {configExpertMode = v}+ printSlowItemsOption :: Option Config printSlowItemsOption = Option name (Just 'p') (OptArg "N" arg) "print the N slowest spec items (default: 10)" True where@@ -192,9 +249,8 @@ parseArg :: String -> Config -> Maybe Config parseArg input c = case readMaybe input of- Just 0 -> Just (setter Nothing c)- Just n -> Just (setter (Just n) c) Nothing -> Nothing+ mn -> Just (setter (find (> 0) mn) c) smallCheckOptions :: [Option Config] smallCheckOptions = [@@ -235,6 +291,7 @@ FailOnEmpty | FailOnFocused | FailOnPending+ | FailOnEmptyDescription deriving (Bounded, Enum) allFailOnItems :: [FailOn]@@ -245,6 +302,7 @@ FailOnEmpty -> "empty" FailOnFocused -> "focused" FailOnPending -> "pending"+ FailOnEmptyDescription -> "empty-description" readFailOn :: String -> Maybe FailOn readFailOn = (`lookup` items)@@ -279,7 +337,7 @@ , mkOption "jobs" (Just 'j') (argument "N" readMaxJobs setMaxJobs) "run at most N parallelizable tests simultaneously (default: number of available processors)" ] where- strict = allFailOnItems+ strict = [FailOnFocused, FailOnPending] readFailOnItems :: String -> Maybe [FailOn] readFailOnItems = mapM readFailOn . splitOn ','@@ -292,9 +350,10 @@ showFailOn item <> ": " <> help item where help item = case item of- FailOnEmpty -> "fail if no spec items have been run"+ FailOnEmpty -> "fail if all spec items have been filtered" FailOnFocused -> "fail on focused spec items" FailOnPending -> "fail on pending spec items"+ FailOnEmptyDescription -> "fail on empty descriptions" setFailOnItems :: Bool -> [FailOn] -> Config -> Config setFailOnItems value = flip $ foldr (`setItem` value)@@ -303,6 +362,7 @@ FailOnEmpty -> setFailOnEmpty FailOnFocused -> setFailOnFocused FailOnPending -> setFailOnPending+ FailOnEmptyDescription -> setFailOnEmptyDescription readMaxJobs :: String -> Maybe Int readMaxJobs s = do@@ -330,6 +390,9 @@ setFailOnPending :: Bool -> Config -> Config setFailOnPending value config = config {configFailOnPending = value}++ setFailOnEmptyDescription :: Bool -> Config -> Config+ setFailOnEmptyDescription value config = config {configFailOnEmptyDescription = value} setStrict :: Bool -> Config -> Config setStrict = (`setFailOnItems` strict)
hspec-core/src/Test/Hspec/Core/Config/Options.hs view
@@ -49,14 +49,7 @@ foldM (parseFileOptions prog) config configFiles >>= maybe return (parseEnvVarOptions prog) envVar >>= parseEnvironmentOptions env- >>= traverseTuple (parseCommandLineOptions prog args)--traverseTuple :: Applicative f => (a -> f b) -> (c, a) -> f (c, b)-#if MIN_VERSION_base(4,7,0)-traverseTuple = traverse-#else-traverseTuple f (c, a) = (,) c <$> f a-#endif+ >>= traverse (parseCommandLineOptions prog args) parseCommandLineOptions :: String -> [String] -> Config -> Either (ExitCode, String) Config parseCommandLineOptions prog args config = case Declarative.parseCommandLineOptions (commandLineOptions formatters) prog args config of
hspec-core/src/Test/Hspec/Core/Example.hs view
@@ -1,6 +1,5 @@ {-# LANGUAGE CPP #-} {-# LANGUAGE RecordWildCards #-}-{-# LANGUAGE DeriveDataTypeable #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE TypeFamilies #-} {-# LANGUAGE TypeSynonymInstances #-}@@ -21,6 +20,8 @@ , safeEvaluateExample -- END RE-EXPORTED from Test.Hspec.Core.Spec , safeEvaluateResultStatus+, exceptionToResultStatus+, toLocation ) where import Prelude ()@@ -28,11 +29,9 @@ import qualified Test.HUnit.Lang as HUnit -import Data.CallStack+import Data.CallStack (SrcLoc(..)) -import Control.Exception import Control.DeepSeq-import Data.Typeable (Typeable) import qualified Test.QuickCheck as QC import Test.Hspec.Expectations (Expectation) @@ -70,57 +69,66 @@ data Result = Result { resultInfo :: String , resultStatus :: ResultStatus-} deriving (Show, Typeable)+} deriving Show data ResultStatus = Success | Pending (Maybe Location) (Maybe String) | Failure (Maybe Location) FailureReason- deriving (Show, Typeable)+ deriving Show data FailureReason = NoReason | Reason String+ | ColorizedReason String | ExpectedButGot (Maybe String) String String | Error (Maybe String) SomeException- deriving (Show, Typeable)+ deriving Show instance NFData FailureReason where rnf reason = case reason of NoReason -> () Reason r -> r `deepseq` ()+ ColorizedReason r -> r `deepseq` () ExpectedButGot p e a -> p `deepseq` e `deepseq` a `deepseq` ()- Error m e -> m `deepseq` e `seq` ()+ Error m e -> m `deepseq` show e `deepseq` () instance Exception ResultStatus -safeEvaluateExample :: Example e => e -> Params -> (ActionWith (Arg e) -> IO ()) -> ProgressCallback -> IO Result-safeEvaluateExample example params around progress = safeEvaluate $ forceResult <$> evaluateExample example params around progress- where- forceResult :: Result -> Result- forceResult r@(Result info status) = info `deepseq` (forceResultStatus status) `seq` r+forceResult :: Result -> Result+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- Failure _ m -> m `deepseq` r+forceResultStatus :: ResultStatus -> ResultStatus+forceResultStatus r = case r of+ Success -> r+ Pending _ m -> m `deepseq` r+ Failure _ m -> m `deepseq` r +safeEvaluateExample :: Example e => e -> Params -> (ActionWith (Arg e) -> IO ()) -> ProgressCallback -> IO Result+safeEvaluateExample example params around progress = safeEvaluate $ evaluateExample example params around progress+ safeEvaluate :: IO Result -> IO Result safeEvaluate action = do- r <- safeTry action- return $ case r of- Left e -> Result "" (toResultStatus e)- Right result -> result+ r <- safeTry $ forceResult <$> action+ case r of+ Left e -> Result "" <$> exceptionToResultStatus e+ Right result -> return result safeEvaluateResultStatus :: IO ResultStatus -> IO ResultStatus-safeEvaluateResultStatus action = either toResultStatus id <$> safeTry action+safeEvaluateResultStatus action = do+ r <- safeTry $ forceResultStatus <$> action+ case r of+ Left e -> exceptionToResultStatus e+ Right status -> return status -toResultStatus :: SomeException -> ResultStatus-toResultStatus e- | Just result <- fromException e = result- | Just hunit <- fromException e = hunitFailureToResult Nothing hunit- | otherwise = Failure Nothing $ Error Nothing e+exceptionToResultStatus :: SomeException -> IO ResultStatus+exceptionToResultStatus = safeEvaluateResultStatus . pure . toResultStatus+ where+ toResultStatus :: SomeException -> ResultStatus+ toResultStatus e+ | Just result <- fromException e = result+ | Just hunit <- fromException e = hunitFailureToResult Nothing hunit+ | otherwise = Failure Nothing $ Error Nothing e instance Example Result where type Arg Result = ()@@ -128,10 +136,8 @@ instance Example (a -> Result) where type Arg (a -> Result) = a- evaluateExample example _params action _callback = do- ref <- newIORef (Result "" Success)- action (evaluate . example >=> writeIORef ref)- readIORef ref+ evaluateExample example _params hook _callback = do+ liftHook (Result "" Success) hook (evaluate . example) instance Example Bool where type Arg Bool = ()@@ -139,10 +145,8 @@ instance Example (a -> Bool) where type Arg (a -> Bool) = a- evaluateExample p _params action _callback = do- ref <- newIORef (Result "" Success)- action (evaluate . example >=> writeIORef ref)- readIORef ref+ evaluateExample p _params hook _callback = do+ liftHook (Result "" Success) hook (evaluate . example) where example a | p a = Result "" Success@@ -166,16 +170,19 @@ where location = case mLoc of Nothing -> Nothing- Just loc -> Just $ Location (srcLocFile loc) (srcLocStartLine loc) (srcLocStartCol loc)+ Just loc -> Just $ toLocation loc where addPre :: String -> String addPre xs = case pre of Just x -> x ++ "\n" ++ xs Nothing -> xs +toLocation :: SrcLoc -> Location+toLocation loc = Location (srcLocFile loc) (srcLocStartLine loc) (srcLocStartCol loc)+ instance Example (a -> Expectation) where type Arg (a -> Expectation) = a- evaluateExample e _ action _ = action e >> return (Result "" Success)+ evaluateExample e _ hook _ = hook e >> return (Result "" Success) instance Example QC.Property where type Arg QC.Property = ()@@ -183,8 +190,8 @@ instance Example (a -> QC.Property) where 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)+ evaluateExample p c hook progressCallback = do+ r <- QC.quickCheckWithResult (paramsQuickCheckArgs c) {QC.chatty = False} (QCP.callback qcProgressCallback $ aroundProperty hook p) return $ fromQuickCheckResult r where qcProgressCallback = QCP.PostTest QCP.NotCounterexample $
hspec-core/src/Test/Hspec/Core/Example/Location.hs view
@@ -16,9 +16,7 @@ import Prelude () import Test.Hspec.Core.Compat -import Control.Exception import Data.Char-import Data.Maybe import GHC.IO.Exception #ifdef mingw32_HOST_OS
hspec-core/src/Test/Hspec/Core/FailureReport.hs view
@@ -9,7 +9,7 @@ import Test.Hspec.Core.Compat #ifndef __GHCJS__-import System.SetEnv (setEnv)+import System.Environment (setEnv) import Test.Hspec.Core.Util (safeTry) #endif import System.IO@@ -35,7 +35,7 @@ -- into the environment is a non-essential feature we just disable this to be -- able to run hspec test-suites with ghcjs at all. Should be reverted once -- the issue is fixed.- return ()+ pass #else -- on Windows this can throw an exception when the input is too large, hence -- we use `safeTry` here
hspec-core/src/Test/Hspec/Core/Format.hs view
@@ -1,8 +1,14 @@ {-# LANGUAGE RankNTypes #-} {-# LANGUAGE ExistentialQuantification #-} -- |--- Stability: experimental-module Test.Hspec.Core.Format (+-- Stability: unstable+--+-- This is an unstable API. Use+-- [Test.Hspec.Api.Format.V2](https://hackage.haskell.org/package/hspec-api/docs/Test-Hspec-Api-Format-V2.html)+-- instead.+module Test.Hspec.Core.Format+-- {-# WARNING "Use [Test.Hspec.Api.Format.V2](https://hackage.haskell.org/package/hspec-api/docs/Test-Hspec-Api-Format-V2.html) instead." #-}+( Format , FormatConfig(..) , Event(..)@@ -19,9 +25,8 @@ import Prelude () import Test.Hspec.Core.Compat -import Control.Exception import Control.Concurrent-import Control.Concurrent.Async (async)+import Control.Concurrent.Async (Async, async) import qualified Control.Concurrent.Async as Async import Control.Monad.IO.Class @@ -59,15 +64,20 @@ , formatConfigReportProgress :: Bool , formatConfigOutputUnicode :: Bool , formatConfigUseDiff :: Bool-, formatConfigPrettyPrint :: Bool -- ^ Deprecated: use `formatConfigPrettyPrintFunction` instead+, formatConfigDiffContext :: Maybe Int+, formatConfigExternalDiff :: Maybe (String -> String -> IO ())+, formatConfigPrettyPrint :: Bool , formatConfigPrettyPrintFunction :: Maybe (String -> String -> (String, String)) , formatConfigPrintTimes :: Bool , formatConfigHtmlOutput :: Bool , formatConfigPrintCpuTime :: Bool , formatConfigUsedSeed :: Integer , formatConfigExpectedTotalCount :: Int+, formatConfigExpertMode :: Bool } +{-# DEPRECATED formatConfigPrettyPrint "Use `formatConfigPrettyPrintFunction` instead" #-}+ data Signal = Ok | NotOk SomeException monadic :: MonadIO m => (m () -> IO ()) -> (Event -> m ()) -> IO Format@@ -92,23 +102,24 @@ event <- takeEvent format event case event of- Done {} -> return ()+ Done {} -> pass _ -> do signal Ok go - t <- async $ do+ worker <- async $ do (run go >> signal Ok) `catch` (signal . NotOk) return $ \ event -> do- running <- Async.poll t- case running of- Just _ -> return ()- Nothing -> do- putEvent event- r <- wait- case r of- Ok -> return ()- NotOk err -> do- Async.wait t- throwIO err+ running <- asyncRunning worker+ when running $ do+ putEvent event+ r <- wait+ case r of+ Ok -> pass+ NotOk err -> do+ Async.wait worker+ throwIO err++asyncRunning :: Async () -> IO Bool+asyncRunning worker = maybe True (const False) <$> Async.poll worker
hspec-core/src/Test/Hspec/Core/Formatters.hs view
@@ -1,6 +1,5 @@--- |--- Deprecated\: Use "Test.Hspec.Core.Formatters.V1" instead.+{-# OPTIONS_GHC -fno-warn-deprecations #-} module Test.Hspec.Core.Formatters--- {-# DEPRECATED "Use \"Test.Hspec.Core.Formatters.V1\" instead." #-}+{-# DEPRECATED "Use [Test.Hspec.Api.Formatters.V1](https://hackage.haskell.org/package/hspec-api/docs/Test-Hspec-Api-Formatters-V1.html) instead." #-} (module V1) where import Test.Hspec.Core.Formatters.V1 as V1
hspec-core/src/Test/Hspec/Core/Formatters/Diff.hs view
@@ -15,12 +15,74 @@ import Data.Char import qualified Data.Algorithm.Diff as Diff -data Diff = First String | Second String | Both String+data Diff = First String | Second String | Both String | Omitted Int deriving (Eq, Show) -diff :: String -> String -> [Diff]-diff expected actual = map (toDiff . fmap concat) $ Diff.getGroupedDiff (partition expected) (partition actual)+-- |+-- Split a string at line boundaries.+--+-- >>> splitLines "foo\nbar\nbaz"+-- ["foo\n","bar\n","baz"]+--+-- prop> concat (splitLines xs) == xs+splitLines :: String -> [String]+splitLines = go+ where+ go xs = case break (== '\n') xs of+ (ys, '\n' : zs) -> (ys ++ ['\n']) : go zs+ ("", "") -> []+ _ -> [xs] +data TrimMode = FirstChunck | Chunck | LastChunck++trim :: Int -> [Diff] -> [Diff]+trim context = \ chunks -> case chunks of+ [] -> []+ x : xs -> trimChunk FirstChunck x (go xs)+ where+ omitThreshold = 3++ go chunks = case chunks of+ [] -> []+ [x] -> trimChunk LastChunck x []+ x : xs -> trimChunk Chunck x (go xs)++ trimChunk mode chunk = case chunk of+ Both xs | omitted >= omitThreshold -> keep start . (Omitted omitted :) . keep end+ where+ omitted :: Int+ omitted = n - keepStart - keepEnd++ keepStart :: Int+ keepStart = case mode of+ FirstChunck -> 0+ _ -> succ context++ keepEnd :: Int+ keepEnd = case mode of+ LastChunck -> 0+ _ -> if xs `endsWith` "\n" then context else succ context++ n :: Int+ n = length allLines++ allLines :: [String]+ allLines = splitLines xs++ start :: [String]+ start = take keepStart allLines++ end :: [String]+ end = drop (keepStart + omitted) allLines+ _ -> (chunk :)++ keep xs+ | null xs = id+ | otherwise = (Both (concat xs) :)++diff :: Maybe Int -> String -> String -> [Diff]+diff context expected actual = maybe id trim context $ map (toDiff . fmap concat) $ Diff.getGroupedDiff (partition expected) (partition actual)+ toDiff :: Diff.Diff String -> Diff toDiff d = case d of Diff.First xs -> First xs@@ -47,7 +109,7 @@ | otherwise = (x :) splitEscape :: String -> Maybe (String, String)-splitEscape xs = splitNumericEscape xs <|> (msum $ map split escapes)+splitEscape xs = splitNumericEscape xs <|> msum (map split escapes) where split :: String -> Maybe (String, String) split escape = (,) escape <$> stripPrefix escape xs
hspec-core/src/Test/Hspec/Core/Formatters/Internal.hs view
@@ -1,6 +1,7 @@ {-# LANGUAGE CPP #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE LambdaCase #-} module Test.Hspec.Core.Formatters.Internal ( Formatter(..) , Item(..)@@ -35,11 +36,15 @@ , outputUnicode , useDiff+, diffContext+, externalDiffAction , prettyPrint , prettyPrintFunction , extraChunk , missingChunk +, unlessExpert+ #ifdef TEST , runFormatM , splitLines@@ -50,16 +55,14 @@ import Test.Hspec.Core.Compat import qualified System.IO as IO-import System.IO (Handle, stdout)-import Control.Exception (bracket_, bracket)+import System.IO (stdout) import System.Console.ANSI-import Control.Monad.Trans.State hiding (state, gets, modify)+import Control.Monad.Trans.Reader (ReaderT(..), ask) import Control.Monad.IO.Class import Data.Char (isSpace) import Data.List (groupBy) import qualified System.CPUTime as CPUTime -import Test.Hspec.Core.Formatters.V1.Monad (FailureRecord(..)) import Test.Hspec.Core.Format import Test.Hspec.Core.Clock @@ -86,8 +89,14 @@ , formatterDone :: FormatM () } +data FailureRecord = FailureRecord {+ failureRecordLocation :: Maybe Location+, failureRecordPath :: Path+, failureRecordMessage :: FailureReason+}+ formatterToFormat :: Formatter -> FormatConfig -> IO Format-formatterToFormat Formatter{..} config = monadic (runFormatM config) $ \ event -> case event of+formatterToFormat Formatter{..} config = monadic (runFormatM config) $ \ case Started -> formatterStarted GroupStarted path -> formatterGroupStarted path GroupDone path -> formatterGroupDone path@@ -111,6 +120,33 @@ useDiff :: FormatM Bool useDiff = getConfig formatConfigUseDiff +-- | Do nothing on `--expert`, otherwise run the given action.+--+-- @since 2.11.2+unlessExpert :: FormatM () -> FormatM ()+unlessExpert action = getConfig formatConfigExpertMode >>= \ case+ False -> action+ True -> return ()++-- |+-- Return the value of `Test.Hspec.Core.Runner.configDiffContext`.+--+-- @since 2.10.6+diffContext :: FormatM (Maybe Int)+diffContext = getConfig formatConfigDiffContext++-- | An action for printing diffs.+--+-- The action takes @expected@ and @actual@ as arguments.+--+-- When this is a `Just`-value then it should be used instead of any built-in+-- diff implementation. A `Just`-value also implies that `useDiff` returns+-- `True`.+--+-- @since 2.10.6+externalDiffAction :: FormatM (Maybe (String -> String -> IO ()))+externalDiffAction = getConfig formatConfigExternalDiff+ -- | Return `True` if the user requested pretty diffs, `False` otherwise. prettyPrint :: FormatM Bool prettyPrint = maybe False (const True) <$> getConfig formatConfigPrettyPrintFunction@@ -124,6 +160,8 @@ prettyPrintFunction = getConfig formatConfigPrettyPrintFunction -- | Return `True` if the user requested unicode output, `False` otherwise.+--+-- @since 2.9.0 outputUnicode :: FormatM Bool outputUnicode = getConfig formatConfigOutputUnicode @@ -143,12 +181,12 @@ -- | A lifted version of `Control.Monad.Trans.State.gets` gets :: (FormatterState -> a) -> FormatM a gets f = FormatM $ do- f <$> (get >>= liftIO . readIORef)+ f <$> (ask >>= liftIO . readIORef) -- | A lifted version of `Control.Monad.Trans.State.modify` modify :: (FormatterState -> FormatterState) -> FormatM () modify f = FormatM $ do- get >>= liftIO . (`modifyIORef'` f)+ ask >>= liftIO . (`modifyIORef'` f) data FormatterState = FormatterState { stateSuccessCount :: !Int@@ -163,22 +201,19 @@ getConfig :: (FormatConfig -> a) -> FormatM a getConfig f = gets (f . stateConfig) -getHandle :: FormatM Handle-getHandle = return stdout- -- | The random seed that is used for QuickCheck. usedSeed :: FormatM Integer 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)+newtype FormatM a = FormatM (ReaderT (IORef FormatterState) IO a) deriving (Functor, Applicative, Monad, MonadIO) runFormatM :: FormatConfig -> FormatM a -> IO a runFormatM config (FormatM action) = withLineBuffering $ do time <- getMonotonicTime- cpuTime <- if (formatConfigPrintCpuTime config) then Just <$> CPUTime.getCPUTime else pure Nothing+ cpuTime <- if formatConfigPrintCpuTime config then Just <$> CPUTime.getCPUTime else pure Nothing let progress = formatConfigReportProgress config && not (formatConfigHtmlOutput config)@@ -191,7 +226,7 @@ , stateConfig = config { formatConfigReportProgress = progress } , stateColor = Nothing }- newIORef state >>= evalStateT action+ newIORef state >>= runReaderT action withLineBuffering :: IO a -> IO a withLineBuffering action = bracket (IO.hGetBuffering stdout) (IO.hSetBuffering stdout) $ \ _ -> do@@ -219,16 +254,17 @@ -- | Get the number of spec items that will have been encountered when this run -- completes (if it is not terminated early).+--+-- @since 2.9.0 getExpectedTotalCount :: FormatM Int getExpectedTotalCount = getConfig formatConfigExpectedTotalCount writeTransient :: String -> FormatM () writeTransient new = do reportProgress <- getConfig formatConfigReportProgress- when (reportProgress) $ do- h <- getHandle- write $ new- liftIO $ IO.hFlush h+ when reportProgress $ do+ write new+ liftIO $ IO.hFlush stdout write $ "\r" ++ replicate (length new) ' ' ++ "\r" -- | Append some output to the report.@@ -242,10 +278,9 @@ writeChunk :: String -> FormatM () writeChunk str = do- h <- getHandle let- plainOutput = IO.hPutStr h str- colorOutput color = bracket_ (hSetSGR h [color]) (hSetSGR h [Reset]) plainOutput+ plainOutput = IO.hPutStr stdout str+ colorOutput color = bracket_ (hSetSGR stdout [color]) (hSetSGR stdout [Reset]) plainOutput mColor <- gets stateColor liftIO $ case mColor of Just (SetColor Foreground _ _) | all isSpace str -> plainOutput@@ -327,7 +362,7 @@ getCPUTime = do t1 <- liftIO CPUTime.getCPUTime mt0 <- gets stateCpuStartTime- return $ toSeconds <$> ((-) <$> pure t1 <*> mt0)+ return $ toSeconds <$> ((t1 -) <$> mt0) where toSeconds x = Seconds (fromIntegral x / (10.0 ^ (12 :: Integer)))
hspec-core/src/Test/Hspec/Core/Formatters/Pretty.hs view
@@ -5,6 +5,7 @@ #ifdef TEST , pretty , recoverString+, recoverMultiLineString #endif ) where @@ -20,49 +21,46 @@ import Test.Hspec.Core.Formatters.Pretty.Parser pretty2 :: Bool -> String -> String -> (String, String)-pretty2 unicode expected actual = case (recoverString unicode expected, recoverString unicode actual) of+pretty2 unicode expected actual = case (recoverMultiLineString unicode expected, recoverMultiLineString unicode actual) of (Just expected_, Just actual_) -> (expected_, actual_) _ -> case (pretty unicode expected, pretty unicode actual) of- (Just expected_, Just actual_) -> (expected_, actual_)-#if __GLASGOW_HASKELL__ >= 802+ (Just expected_, Just actual_) | expected_ /= actual_ -> (expected_, actual_) _ -> (expected, actual)-#else- _ -> (rec expected, rec actual)- where- rec = if unicode then urecover else id - urecover :: String -> String- urecover xs = maybe xs ushow $ readMaybe xs-#endif+recoverString :: String -> Maybe String+recoverString xs = case xs of+ '"' : _ -> case reverse xs of+ '"' : _ -> readMaybe xs+ _ -> Nothing+ _ -> Nothing -recoverString :: Bool -> String -> Maybe String-recoverString unicode input = case readMaybe input of+recoverMultiLineString :: Bool -> String -> Maybe String+recoverMultiLineString unicode input = case recoverString input of Just r | shouldParseBack r -> Just r _ -> Nothing where shouldParseBack = (&&) <$> all isSafe <*> isMultiLine isMultiLine = lines >>> length >>> (> 1)- isSafe c = (unicode || isAscii c) && (not $ isControl c) || c == '\n'+ isSafe c = (unicode || isAscii c) && not (isControl c) || c == '\n' pretty :: Bool -> String -> Maybe String-pretty unicode = parseExpression >=> render_+pretty unicode = parseValue >=> render_ where- render_ :: Expression -> Maybe String- render_ expr = guard (shouldParseBack expr) >> Just (renderExpression unicode expr)+ render_ :: Value -> Maybe String+ render_ value = guard (shouldParseBack value) >> Just (renderValue unicode value) - shouldParseBack :: Expression -> Bool+ shouldParseBack :: Value -> Bool shouldParseBack = go where- go expr = case expr of- Literal (String _) -> True- Literal _ -> False- Id _ -> False- App (Id _) e -> go e- App _ _ -> False- Parentheses e -> go e+ go value = case value of+ Char _ -> False+ String _ -> True+ Rational _ _ -> False+ Number _ -> False+ Record _ _ -> True+ Constructor _ xs -> any go xs Tuple xs -> any go xs List xs -> any go xs- Record _ _ -> True newtype Builder = Builder ShowS @@ -91,24 +89,19 @@ instance IsString Builder where fromString = Builder . showString -renderExpression :: Bool -> Expression -> String-renderExpression unicode = runBuilder . render+renderValue :: Bool -> Value -> String+renderValue unicode = runBuilder . render where- renderLiteral lit = case lit of+ render :: Value -> Builder+ render value = case value of Char c -> shows c String str -> if unicode then Builder $ ushows str else shows str- Integer n -> shows n- Rational n -> fromString n-- render :: Expression -> Builder- render expr = case expr of- Literal lit -> renderLiteral lit- Id name -> fromString name- App a b -> render a <> " " <> render b- Parentheses e@Record{} -> render e- Parentheses e -> "(" <> render e <> ")"+ Rational n d -> render n <> " % " <> render d+ Number n -> fromString n+ Record name fields -> fromString name <> " {\n " <> (intercalate ",\n " $ map renderField fields) <> "\n}"+ Constructor name values -> intercalate " " (fromString name : map render values)+ Tuple [e@Record{}] -> render e Tuple xs -> "(" <> intercalate ", " (map render xs) <> ")" List xs -> "[" <> intercalate ", " (map render xs) <> "]"- Record name fields -> fromString name <> " {\n " <> (intercalate ",\n " $ map renderField fields) <> "\n}" renderField (name, value) = fromString name <> " = " <> render value
hspec-core/src/Test/Hspec/Core/Formatters/Pretty/Parser.hs view
@@ -1,395 +1,107 @@-{-# LANGUAGE CPP #-}-{-# LANGUAGE RankNTypes #-}-{-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE FlexibleInstances #-}-#if __GLASGOW_HASKELL__ >= 810-{-# LANGUAGE EmptyCase #-}-#endif module Test.Hspec.Core.Formatters.Pretty.Parser (- Expression(..)-, Literal(..)-, parseExpression-, unsafeParseExpression+ Value(..)+, parseValue ) where import Prelude ()-import Test.Hspec.Core.Compat hiding (fail)-import Test.Hspec.Core.Formatters.Pretty.Parser.Types--#ifndef __GHCJS__-#if __GLASGOW_HASKELL__ >= 802 && __GLASGOW_HASKELL__ <= 904-#define PRETTY_PRINTING_SUPPORTED-#endif-#endif--#ifndef PRETTY_PRINTING_SUPPORTED--parseExpression :: String -> Maybe Expression-parseExpression _ = Nothing--unsafeParseExpression :: String -> Maybe Expression-unsafeParseExpression _ = Nothing--#else--import GHC.Stack-import GHC.Exception (throw, errorCallWithCallStackException)--#if __GLASGOW_HASKELL__ >= 904-import GHC.Utils.Error-import GHC.Utils.Outputable-#endif--#if __GLASGOW_HASKELL__ >= 804 && __GLASGOW_HASKELL__ < 904-import GHC.LanguageExtensions.Type-#endif--#if __GLASGOW_HASKELL__ >= 902-import GHC.Types.SourceText-#elif __GLASGOW_HASKELL__ >= 900-import GHC.Types.Basic-import GHC.Unit.Types-#endif--#if __GLASGOW_HASKELL__ >= 900-import qualified GHC.Parser as GHC-import GHC.Parser.Lexer-import GHC.Data.StringBuffer-import GHC.Data.FastString-import GHC.Types.SrcLoc-import GHC.Types.Name-import GHC.Types.Name.Reader-import GHC.Parser.PostProcess hiding (Tuple)-#else-import Lexer-import qualified Parser as GHC-import StringBuffer-import FastString-import SrcLoc-import Name-import RdrName-import BasicTypes-import Module-#endif--#if __GLASGOW_HASKELL__ >= 804 && __GLASGOW_HASKELL__ < 904-#if __GLASGOW_HASKELL__ >= 900-import qualified GHC.Data.EnumSet as EnumSet-#else-import qualified EnumSet-#endif-#endif--#if __GLASGOW_HASKELL__ == 810-import RdrHsSyn hiding (Tuple)-#endif--#if __GLASGOW_HASKELL__ >= 810-import GHC.Hs-#else-import HsSyn-#endif--#if __GLASGOW_HASKELL__ <= 806-import Data.Bits-import Control.Exception-#endif--parseExpression :: String -> Maybe Expression-parseExpression = parseWith (const Nothing)--unsafeParseExpression :: String -> Maybe Expression-unsafeParseExpression = parseWith throwError--parseWith :: (Error -> Maybe Expression) -> String -> Maybe Expression-parseWith err = parse >=> either err Just . toExpression--data Error = Error CallStack String--throwError :: Error -> a-throwError (Error stack err) = throw $ errorCallWithCallStackException err stack--fail :: HasCallStack => String -> Either Error a-fail = Left . Error callStack--class ToExpression a where- toExpression :: a -> Either Error Expression--#if __GLASGOW_HASKELL__ < 806-#define _x-#endif--#if __GLASGOW_HASKELL__ >= 900-#define X(name, expr)-#elif __GLASGOW_HASKELL__ == 810-#define X(name, expr) name none -> case none of-#elif __GLASGOW_HASKELL__ >= 806-#define X(name, expr) name none -> case none of NoExt -> expr-#else-#define X(name, expr)-#endif--#if __GLASGOW_HASKELL__ >= 804-#define GhcPsHsLit GhcPs-#else-type GhcPs = RdrName-#define GhcPsHsLit-#endif--#if __GLASGOW_HASKELL__ >= 902-#define _listSynExpr-#endif--#if __GLASGOW_HASKELL__ >= 806-#define RecCon(name, fields) RecordCon _ (L _ name) fields-#else-#define RecCon(name, fields) RecordCon (L _ name) _ _ fields-#endif+import Test.Hspec.Core.Compat -#define REJECT(name) name{} -> fail "name"+import Test.Hspec.Core.Formatters.Pretty.Parser.Parser hiding (Parser)+import qualified Test.Hspec.Core.Formatters.Pretty.Parser.Parser as P -instance ToExpression (HsExpr GhcPs) where- toExpression expr = case expr of- HsVar _x name -> toExpression name- HsLit _x lit -> toExpression lit- HsOverLit _x lit -> toExpression lit- HsApp _x f x -> App <$> toExpression f <*> toExpression x- NegApp _x e _ -> toExpression e >>= \ x -> case x of- Literal (Rational n) -> return $ Literal (Rational $ '-' : n)- Literal (Integer n) -> return $ Literal (Integer $ negate n)- _ -> fail "NegApp"+import Language.Haskell.Lexer hiding (Pos(..)) - HsPar _x-#if __GLASGOW_HASKELL__ >= 904- _ e _ ->-#else- e ->-#endif- Parentheses <$> toExpression e- ExplicitTuple _x xs _ -> Tuple <$> mapM toExpression xs- ExplicitList _ _listSynExpr xs -> List <$> mapM toExpression xs- RecCon(name, fields) -> Record (showRdrName name) <$> (recordFields $ rec_flds fields)- where-#if __GLASGOW_HASKELL__ >= 904- hsRecFieldLbl = hfbLHS- hsRecFieldArg = hfbRHS-#endif- fieldName = showFieldLabel . unLoc . hsRecFieldLbl- recordFields = mapM (recordField . unLoc)- recordField field = (,) (fieldName field) <$> toExpression (hsRecFieldArg field)+type Name = String - REJECT(HsUnboundVar)- REJECT(HsOverLabel)- REJECT(HsIPVar)- REJECT(HsLam)- REJECT(HsLamCase)- REJECT(HsAppType)- REJECT(OpApp)- REJECT(SectionL)- REJECT(SectionR)- REJECT(ExplicitSum)- REJECT(HsCase)- REJECT(HsIf)- REJECT(HsMultiIf)- REJECT(HsLet)- REJECT(HsDo)- REJECT(RecordUpd)- REJECT(ExprWithTySig)- REJECT(ArithSeq)- REJECT(HsSpliceE)- REJECT(HsProc)- REJECT(HsStatic)+data Value =+ Char Char+ | String String+ | Rational Value Value+ | Number String+ | Record Name [(Name, Value)]+ | Constructor Name [Value]+ | Tuple [Value]+ | List [Value]+ deriving (Eq, Show) -#if __GLASGOW_HASKELL__ >= 904- REJECT(HsRecSel)- REJECT(HsTypedBracket)- REJECT(HsUntypedBracket)-#endif-#if __GLASGOW_HASKELL__ >= 902- REJECT(HsGetField)- REJECT(HsProjection)-#endif-#if __GLASGOW_HASKELL__ >= 900- REJECT(HsPragE)-#endif+type Parser = P.Parser (Token, String) -#if __GLASGOW_HASKELL__ < 904- REJECT(HsConLikeOut)- REJECT(HsRecFld)- REJECT(HsBracket)- REJECT(HsRnBracketOut)- REJECT(HsTcBracketOut)- REJECT(HsTick)- REJECT(HsBinTick)-#endif-#if __GLASGOW_HASKELL__ < 900- REJECT(HsSCC)- REJECT(HsCoreAnn)- REJECT(HsTickPragma)- REJECT(HsWrap)-#endif-#if __GLASGOW_HASKELL__ < 810- REJECT(HsArrApp)- REJECT(HsArrForm)- REJECT(EWildPat)- REJECT(EAsPat)- REJECT(EViewPat)- REJECT(ELazyPat)-#endif-#if __GLASGOW_HASKELL__ < 806- REJECT(HsAppTypeOut)- REJECT(ExplicitPArr)- REJECT(ExprWithTySigOut)- REJECT(PArrSeq)-#endif- X(XExpr, fail "XExpr")+parseValue :: String -> Maybe Value+parseValue input = case runParser value (tokenize input) of+ Just (v, []) -> Just v+ _ -> Nothing -instance ToExpression RdrName where- toExpression = return . Id . showRdrName+value :: Parser Value+value =+ char+ <|> string+ <|> rational+ <|> number+ <|> record+ <|> constructor+ <|> tuple+ <|> list -instance ToExpression (HsTupArg GhcPs) where- toExpression t = case t of- Present _x expr -> toExpression expr- Missing _ -> fail "Missing (tuple section)"- X(XTupArg, fail "XTupArg")+char :: Parser Value+char = Char <$> (token CharLit >>= readA) -instance ToExpression e => ToExpression (GenLocated l e) where- toExpression (L _ e) = toExpression e+string :: Parser Value+string = String <$> (token StringLit >>= readA) -instance ToExpression (HsOverLit GhcPs) where- toExpression = toExpression . ol_val+rational :: Parser Value+rational = Rational <$> (number <|> tuple) <* require (Varsym, "%") <*> number -#if __GLASGOW_HASKELL__ > 802-#define _integralSource+number :: Parser Value+number = integer <|> float+ where+ integer :: Parser Value+ integer = Number <$> token IntLit -instance ToExpression IntegralLit where- toExpression il = toExpression (il_value il)-#endif+ float :: Parser Value+ float = Number <$> token FloatLit -instance ToExpression OverLitVal where- toExpression lit = case lit of- HsIntegral _integralSource il -> toExpression il- HsFractional fl -> toExpression fl- HsIsString _ str -> toExpression str+record :: Parser Value+record = Record <$> token Conid <* special "{" <*> fields <* special "}"+ where+ fields :: Parser [(Name, Value)]+ fields = field `sepBy1` comma -instance ToExpression FractionalLit where- toExpression fl = case fl_text fl of-#if __GLASGOW_HASKELL__ > 802- REJECT(NoSourceText)- SourceText n-#else- n-#endif- -> return . Literal $ Rational n+ field :: Parser (Name, Value)+ field = (,) <$> token Varid <* equals <*> value -instance ToExpression FastString where- toExpression = return . Literal . String . unpackFS+constructor :: Parser Value+constructor = Constructor <$> token Conid <*> many value -instance ToExpression Integer where- toExpression = return . Literal . Integer+tuple :: Parser Value+tuple = Tuple <$> (special "(" *> items) <* special ")" -instance ToExpression Char where- toExpression = return . Literal . Char+list :: Parser Value+list = List <$> (special "[" *> items) <* special "]" -instance ToExpression (HsLit GhcPsHsLit) where- toExpression lit = case lit of- HsChar _ c -> toExpression c- HsString _ str -> toExpression str- REJECT(HsCharPrim)- REJECT(HsStringPrim)- REJECT(HsInt)- REJECT(HsIntPrim)- REJECT(HsWordPrim)- REJECT(HsInt64Prim)- REJECT(HsWord64Prim)- REJECT(HsInteger)- REJECT(HsRat)- REJECT(HsFloatPrim)- REJECT(HsDoublePrim)- X(XLit, fail "XLit")+items :: Parser [Value]+items = value `sepBy` comma -showFieldLabel :: FieldOcc GhcPs -> String-showFieldLabel label = case label of-#if __GLASGOW_HASKELL__ >= 806- FieldOcc _ (L _ name) -> showRdrName name-#else- FieldOcc (L _ name) _ -> showRdrName name-#endif- X(XFieldOcc, "")+special :: String -> Parser ()+special s = require (Special, s) -showRdrName :: RdrName -> String-showRdrName n = case n of- Unqual name -> showOccName name- Qual _ name -> showOccName name- Orig _ name -> showOccName name- Exact name -> showOccName (nameOccName name)+comma :: Parser ()+comma = special "," -showOccName :: OccName -> String-showOccName = unpackFS . occNameFS+equals :: Parser ()+equals = require (Reservedop, "=") -parse :: String -> Maybe (HsExpr GhcPs)-parse input = case runParser input pHsExpr of- POk _ (L _ x) -> Just x- PFailed {} -> Nothing- where- pHsExpr = do- r <- GHC.parseExpression- runPV (unECP r)+token :: Token -> Parser String+token t = snd <$> satisfy (fst >>> (== t)) -#if __GLASGOW_HASKELL__ <= 900-#if __GLASGOW_HASKELL__ >= 810- unECP = runECP_PV-#else- unECP = return- runPV = id-#endif-#endif+require :: (Token, String) -> Parser ()+require t = void $ satisfy (== t) -runParser :: String -> P a -> ParseResult a-runParser str parser = unP parser parseState+tokenize :: String -> [(Token, String)]+tokenize = go . map (fmap snd) . rmSpace . lexerPass0 where- location = mkRealSrcLoc "" 1 1- input = stringToStringBuffer str- parseState = initParserState opts input location- opts = mkParserOpts warn-#if __GLASGOW_HASKELL__ >= 904- (DiagOpts mempty mempty False False Nothing defaultSDocContext)-#endif- extensions False False False True--#if __GLASGOW_HASKELL__ >= 804 && __GLASGOW_HASKELL__ < 904- warn = EnumSet.empty-#else- warn = mempty-#endif--#if __GLASGOW_HASKELL__ >= 904- extensions = ["TraditionalRecordSyntax"]-#elif __GLASGOW_HASKELL__ >= 804- extensions = EnumSet.fromList [TraditionalRecordSyntax]-#else- extensions = mempty-#endif--#if __GLASGOW_HASKELL__ <= 900- initParserState = mkPStatePure- mkParserOpts warningFlags extensionFlags = mkParserFlags' warningFlags extensionFlags unit-#if __GLASGOW_HASKELL__ == 900- unit = UnitId ""-#else- unit = fsToUnitId ""-#endif-#endif--#if __GLASGOW_HASKELL__ <= 806- mkParserFlags' ws es u _ _ _ _ = assert (traditionalRecordSyntaxEnabled extensionsBitmap) $- ParserFlags ws es u extensionsBitmap- extensionsBitmap = shift 1 traditionalRecordSyntaxBit-#if __GLASGOW_HASKELL__ == 806- traditionalRecordSyntaxBit = 28-#else- traditionalRecordSyntaxBit = 29-#endif-#endif--#endif+ go :: [(Token, String)] -> [(Token, String)]+ go tokens = case tokens of+ [] -> []+ (Varsym, "-") : (IntLit, n) : xs -> (IntLit, "-" ++ n) : go xs+ (Varsym, "-") : (FloatLit, n) : xs -> (FloatLit, "-" ++ n) : go xs+ x : xs -> x : go xs
+ hspec-core/src/Test/Hspec/Core/Formatters/Pretty/Parser/Parser.hs view
@@ -0,0 +1,34 @@+{-# LANGUAGE DeriveFunctor #-}+module Test.Hspec.Core.Formatters.Pretty.Parser.Parser where++import Prelude ()+import Test.Hspec.Core.Compat++newtype Parser token a = Parser { runParser :: [token] -> Maybe (a, [token]) }+ deriving Functor++instance Applicative (Parser token) where+ pure a = Parser $ \ input -> Just (a, input)+ (<*>) = ap++instance Monad (Parser token) where+ return = pure+ p1 >>= p2 = Parser $ runParser p1 >=> uncurry (runParser . p2)++instance Alternative (Parser token) where+ empty = Parser $ const Nothing+ p1 <|> p2 = Parser $ \ input -> runParser p1 input <|> runParser p2 input++satisfy :: (token -> Bool) -> Parser token token+satisfy p = Parser $ \ input -> case input of+ t : ts | p t -> Just (t, ts)+ _ -> Nothing++sepBy :: Alternative m => m a -> m sep -> m [a]+sepBy p sep = sepBy1 p sep <|> pure []++sepBy1 :: Alternative m => m a -> m sep -> m [a]+sepBy1 p sep = (:) <$> p <*> many (sep *> p)++readA :: (Alternative m, Read a) => String -> m a+readA = maybe empty pure . readMaybe
− hspec-core/src/Test/Hspec/Core/Formatters/Pretty/Parser/Types.hs
@@ -1,21 +0,0 @@-module Test.Hspec.Core.Formatters.Pretty.Parser.Types where--import Prelude ()-import Test.Hspec.Core.Compat--data Expression =- Literal Literal- | Id String- | App Expression Expression- | Parentheses Expression- | Tuple [Expression]- | List [Expression]- | Record String [(String, Expression)]- deriving (Eq, Show)--data Literal =- Char Char- | String String- | Integer Integer- | Rational String- deriving (Eq, Show)
hspec-core/src/Test/Hspec/Core/Formatters/V1.hs view
@@ -1,12 +1,11 @@-{-# LANGUAGE CPP #-}-{-# LANGUAGE RecordWildCards #-} -- | -- Stability: deprecated -- -- This module contains formatters that can be used with -- `Test.Hspec.Core.Runner.hspecWith`.-module Test.Hspec.Core.Formatters.V1 (-+module Test.Hspec.Core.Formatters.V1+{-# DEPRECATED "Use [Test.Hspec.Api.Formatters.V1](https://hackage.haskell.org/package/hspec-api/docs/Test-Hspec-Api-Formatters-V1.html) instead." #-}+( -- * Formatters silent , checks@@ -60,290 +59,4 @@ ) where import Prelude ()-import Test.Hspec.Core.Compat hiding (First)--import Data.Maybe-import Test.Hspec.Core.Util-import Test.Hspec.Core.Clock-import Test.Hspec.Core.Example (Location(..))-import Text.Printf-import Control.Monad.IO.Class-import Control.Exception---- We use an explicit import list for "Test.Hspec.Formatters.Internal", to make--- sure, that we only use the public API to implement formatters.------ Everything imported here has to be re-exported, so that users can implement--- their own formatters.-import Test.Hspec.Core.Formatters.V1.Monad (- Formatter(..)- , FailureReason(..)- , FormatM-- , getSuccessCount- , getPendingCount- , getFailCount- , getTotalCount-- , FailureRecord(..)- , getFailMessages- , usedSeed-- , getCPUTime- , getRealTime-- , write- , writeLine- , writeTransient-- , withInfoColor- , withSuccessColor- , withPendingColor- , withFailColor-- , useDiff- , extraChunk- , missingChunk- )--import Test.Hspec.Core.Format (FormatConfig, Format)--import Test.Hspec.Core.Formatters.Diff-import qualified Test.Hspec.Core.Formatters.V2 as V2-import Test.Hspec.Core.Formatters.V1.Monad (Item(..), Result(..), Environment(..), interpretWith)--formatterToFormat :: Formatter -> FormatConfig -> IO Format-formatterToFormat = V2.formatterToFormat . legacyFormatterToFormatter--legacyFormatterToFormatter :: Formatter -> V2.Formatter-legacyFormatterToFormatter Formatter{..} = V2.Formatter {- V2.formatterStarted = interpret headerFormatter-, V2.formatterGroupStarted = interpret . uncurry exampleGroupStarted-, V2.formatterGroupDone = interpret . const exampleGroupDone-, V2.formatterProgress = \ path -> interpret . exampleProgress path-, V2.formatterItemStarted = interpret . exampleStarted-, V2.formatterItemDone = \ path item -> interpret $ do- case itemResult item of- Success -> exampleSucceeded path (itemInfo item)- Pending _ reason -> examplePending path (itemInfo item) reason- Failure _ reason -> exampleFailed path (itemInfo item) reason-, V2.formatterDone = interpret $ failedFormatter >> footerFormatter-}--interpret :: FormatM a -> V2.FormatM a-interpret = interpretWith Environment {- environmentGetSuccessCount = V2.getSuccessCount-, environmentGetPendingCount = V2.getPendingCount-, environmentGetFailMessages = V2.getFailMessages-, environmentUsedSeed = V2.usedSeed-, environmentPrintTimes = V2.printTimes-, environmentGetCPUTime = V2.getCPUTime-, environmentGetRealTime = V2.getRealTime-, environmentWrite = V2.write-, environmentWriteTransient = V2.writeTransient-, environmentWithFailColor = V2.withFailColor-, environmentWithSuccessColor = V2.withSuccessColor-, environmentWithPendingColor = V2.withPendingColor-, environmentWithInfoColor = V2.withInfoColor-, environmentUseDiff = V2.useDiff-, environmentExtraChunk = V2.extraChunk-, environmentMissingChunk = V2.missingChunk-, environmentLiftIO = liftIO-}--silent :: Formatter-silent = Formatter {- headerFormatter = return ()-, exampleGroupStarted = \_ _ -> return ()-, exampleGroupDone = return ()-, exampleStarted = \_ -> return ()-, exampleProgress = \_ _ -> return ()-, exampleSucceeded = \ _ _ -> return ()-, exampleFailed = \_ _ _ -> return ()-, examplePending = \_ _ _ -> return ()-, failedFormatter = return ()-, footerFormatter = return ()-}--checks :: Formatter-checks = specdoc {- exampleStarted = \(nesting, requirement) -> do- writeTransient $ indentationFor nesting ++ requirement ++ " [ ]"--, exampleProgress = \(nesting, requirement) p -> do- writeTransient $ indentationFor nesting ++ requirement ++ " [" ++ (formatProgress p) ++ "]"--, exampleSucceeded = \(nesting, requirement) info -> do- writeResult nesting requirement info $ withSuccessColor $ write "✔"--, exampleFailed = \(nesting, requirement) info _ -> do- writeResult nesting requirement info $ withFailColor $ write "✘"--, examplePending = \(nesting, requirement) info reason -> do- writeResult nesting requirement info $ withPendingColor $ write "‐"-- withPendingColor $ do- writeLine $ indentationFor ("" : nesting) ++ "# PENDING: " ++ fromMaybe "No reason given" reason-} where- indentationFor nesting = replicate (length nesting * 2) ' '-- writeResult :: [String] -> String -> String -> FormatM () -> FormatM ()- writeResult nesting requirement info action = do- write $ indentationFor nesting ++ requirement ++ " ["- action- writeLine "]"- forM_ (lines info) $ \ s ->- writeLine $ indentationFor ("" : nesting) ++ s-- formatProgress (current, total)- | total == 0 = show current- | otherwise = show current ++ "/" ++ show total--specdoc :: Formatter-specdoc = silent {-- headerFormatter = do- writeLine ""--, exampleGroupStarted = \nesting name -> do- writeLine (indentationFor nesting ++ name)--, exampleProgress = \_ p -> do- writeTransient (formatProgress p)--, exampleSucceeded = \(nesting, requirement) info -> withSuccessColor $ do- writeLine $ indentationFor nesting ++ requirement- forM_ (lines info) $ \ s ->- writeLine $ indentationFor ("" : nesting) ++ s--, exampleFailed = \(nesting, requirement) info _ -> withFailColor $ do- n <- getFailCount- writeLine $ indentationFor nesting ++ requirement ++ " FAILED [" ++ show n ++ "]"- forM_ (lines info) $ \ s ->- writeLine $ indentationFor ("" : nesting) ++ s--, examplePending = \(nesting, requirement) info reason -> withPendingColor $ do- writeLine $ indentationFor nesting ++ requirement- forM_ (lines info) $ \ s ->- writeLine $ indentationFor ("" : nesting) ++ s- writeLine $ indentationFor ("" : nesting) ++ "# PENDING: " ++ fromMaybe "No reason given" reason--, failedFormatter = defaultFailedFormatter--, footerFormatter = defaultFooter-} where- indentationFor nesting = replicate (length nesting * 2) ' '- formatProgress (current, total)- | total == 0 = show current- | otherwise = show current ++ "/" ++ show total---progress :: Formatter-progress = silent {- exampleSucceeded = \_ _ -> withSuccessColor $ write "."-, exampleFailed = \_ _ _ -> withFailColor $ write "F"-, examplePending = \_ _ _ -> withPendingColor $ write "."-, failedFormatter = defaultFailedFormatter-, footerFormatter = defaultFooter-}---failed_examples :: Formatter-failed_examples = silent {- failedFormatter = defaultFailedFormatter-, footerFormatter = defaultFooter-}--defaultFailedFormatter :: FormatM ()-defaultFailedFormatter = do- writeLine ""-- failures <- getFailMessages-- unless (null failures) $ do- writeLine "Failures:"- writeLine ""-- forM_ (zip [1..] failures) $ \x -> do- formatFailure x- writeLine ""-- write "Randomized with seed " >> usedSeed >>= writeLine . show- writeLine ""- where- formatFailure :: (Int, FailureRecord) -> FormatM ()- formatFailure (n, FailureRecord mLoc path reason) = do- forM_ mLoc $ \loc -> do- withInfoColor $ writeLine (formatLoc loc)- write (" " ++ show n ++ ") ")- writeLine (formatRequirement path)- case reason of- NoReason -> return ()- Reason err -> withFailColor $ indent err- ExpectedButGot preface expected actual -> do- mapM_ indent preface-- b <- useDiff-- let threshold = 2 :: Seconds-- mchunks <- liftIO $ if b- then timeout threshold (evaluate $ diff expected actual)- else return Nothing-- case mchunks of- Just chunks -> do- writeDiff chunks extraChunk missingChunk- Nothing -> do- writeDiff [First expected, Second actual] write write- where- indented output text = case break (== '\n') text of- (xs, "") -> output xs- (xs, _ : ys) -> output (xs ++ "\n") >> write (indentation ++ " ") >> indented output ys-- writeDiff chunks extra missing = do- withFailColor $ write (indentation ++ "expected: ")- forM_ chunks $ \ chunk -> case chunk of- Both a -> indented write a- First a -> indented extra a- Second _ -> return ()- writeLine ""-- withFailColor $ write (indentation ++ " but got: ")- forM_ chunks $ \ chunk -> case chunk of- Both a -> indented write a- First _ -> return ()- Second a -> indented missing a- writeLine ""-- Error _ e -> withFailColor . indent $ (("uncaught exception: " ++) . formatException) e-- writeLine ""- writeLine (" To rerun use: --match " ++ show (joinPath path))- where- indentation = " "- indent message = do- forM_ (lines message) $ \line -> do- writeLine (indentation ++ line)- formatLoc (Location file line column) = " " ++ file ++ ":" ++ show line ++ ":" ++ show column ++ ": "--defaultFooter :: FormatM ()-defaultFooter = do-- writeLine =<< (++)- <$> (printf "Finished in %1.4f seconds" <$> getRealTime)- <*> (maybe "" (printf ", used %1.4f seconds of CPU time") <$> getCPUTime)-- fails <- getFailCount- pending <- getPendingCount- total <- getTotalCount-- let- output =- pluralize total "example"- ++ ", " ++ pluralize fails "failure"- ++ if pending == 0 then "" else ", " ++ show pending ++ " pending"- c | fails /= 0 = withFailColor- | pending /= 0 = withPendingColor- | otherwise = withSuccessColor- c $ writeLine output+import Test.Hspec.Core.Formatters.V1.Internal
+ hspec-core/src/Test/Hspec/Core/Formatters/V1/Internal.hs view
@@ -0,0 +1,359 @@+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE NamedFieldPuns #-}+{-# LANGUAGE LambdaCase #-}+module Test.Hspec.Core.Formatters.V1.Internal (+-- * Formatters+ silent+, checks+, specdoc+, progress+, failed_examples++-- * Implementing a custom Formatter+-- |+-- A formatter is a set of actions. Each action is evaluated when a certain+-- situation is encountered during a test run.+--+-- Actions live in the `FormatM` monad. It provides access to the runner state+-- and primitives for appending to the generated report.+, Formatter (..)+, FailureReason (..)+, FormatM+, formatterToFormat++-- ** Accessing the runner state+, getSuccessCount+, getPendingCount+, getFailCount+, getTotalCount++, FailureRecord (..)+, getFailMessages+, usedSeed++, Seconds(..)+, getCPUTime+, getRealTime++-- ** Appending to the generated report+, write+, writeLine+, writeTransient++-- ** Dealing with colors+, withInfoColor+, withSuccessColor+, withPendingColor+, withFailColor++, useDiff+, extraChunk+, missingChunk++-- ** Helpers+, formatException+) where++import Prelude ()+import Test.Hspec.Core.Compat hiding (First)++import Test.Hspec.Core.Util+import Test.Hspec.Core.Clock+import Test.Hspec.Core.Example (Location(..))+import Text.Printf+import Control.Monad.IO.Class++-- We use an explicit import list for "Test.Hspec.Formatters.Internal", to make+-- sure, that we only use the public API to implement formatters.+--+-- Everything imported here has to be re-exported, so that users can implement+-- their own formatters.+import Test.Hspec.Core.Formatters.V1.Monad (+ Formatter(..)+ , FailureReason(..)+ , FormatM++ , getSuccessCount+ , getPendingCount+ , getFailCount+ , getTotalCount++ , FailureRecord(..)+ , getFailMessages+ , usedSeed++ , getCPUTime+ , getRealTime++ , write+ , writeLine+ , writeTransient++ , withInfoColor+ , withSuccessColor+ , withPendingColor+ , withFailColor++ , useDiff+ , extraChunk+ , missingChunk+ )++import Test.Hspec.Core.Format (FormatConfig, Format)++import Test.Hspec.Core.Formatters.Diff+import qualified Test.Hspec.Core.Formatters.V2 as V2+import Test.Hspec.Core.Formatters.V1.Monad (Item(..), Result(..), Environment(..), interpretWith)++formatterToFormat :: Formatter -> FormatConfig -> IO Format+formatterToFormat = V2.formatterToFormat . legacyFormatterToFormatter++legacyFormatterToFormatter :: Formatter -> V2.Formatter+legacyFormatterToFormatter Formatter{..} = V2.Formatter {+ V2.formatterStarted = interpret headerFormatter+, V2.formatterGroupStarted = interpret . uncurry exampleGroupStarted+, V2.formatterGroupDone = interpret . const exampleGroupDone+, V2.formatterProgress = \ path -> interpret . exampleProgress path+, V2.formatterItemStarted = interpret . exampleStarted+, V2.formatterItemDone = \ path item -> interpret $ do+ case itemResult item of+ Success -> exampleSucceeded path (itemInfo item)+ Pending _ reason -> examplePending path (itemInfo item) reason+ Failure _ reason -> exampleFailed path (itemInfo item) (unliftFailureReason reason)+, V2.formatterDone = interpret $ failedFormatter >> footerFormatter+}++unliftFailureRecord :: V2.FailureRecord -> FailureRecord+unliftFailureRecord V2.FailureRecord{..} = FailureRecord {+ failureRecordLocation+, failureRecordPath+, failureRecordMessage = unliftFailureReason failureRecordMessage+}++unliftFailureReason :: V2.FailureReason -> FailureReason+unliftFailureReason = \ case+ V2.NoReason -> NoReason+ V2.Reason reason -> Reason reason+ V2.ColorizedReason reason -> Reason (stripAnsi reason)+ V2.ExpectedButGot preface expected actual -> ExpectedButGot preface expected actual+ V2.Error info e -> Error info e++interpret :: FormatM a -> V2.FormatM a+interpret = interpretWith Environment {+ environmentGetSuccessCount = V2.getSuccessCount+, environmentGetPendingCount = V2.getPendingCount+, environmentGetFailMessages = map unliftFailureRecord <$> V2.getFailMessages+, environmentUsedSeed = V2.usedSeed+, environmentPrintTimes = V2.printTimes+, environmentGetCPUTime = V2.getCPUTime+, environmentGetRealTime = V2.getRealTime+, environmentWrite = V2.write+, environmentWriteTransient = V2.writeTransient+, environmentWithFailColor = V2.withFailColor+, environmentWithSuccessColor = V2.withSuccessColor+, environmentWithPendingColor = V2.withPendingColor+, environmentWithInfoColor = V2.withInfoColor+, environmentUseDiff = V2.useDiff+, environmentExtraChunk = V2.extraChunk+, environmentMissingChunk = V2.missingChunk+, environmentLiftIO = liftIO+}++silent :: Formatter+silent = Formatter {+ headerFormatter = pass+, exampleGroupStarted = \_ _ -> pass+, exampleGroupDone = pass+, exampleStarted = \_ -> pass+, exampleProgress = \_ _ -> pass+, exampleSucceeded = \ _ _ -> pass+, exampleFailed = \_ _ _ -> pass+, examplePending = \_ _ _ -> pass+, failedFormatter = pass+, footerFormatter = pass+}++checks :: Formatter+checks = specdoc {+ exampleStarted = \(nesting, requirement) -> do+ writeTransient $ indentationFor nesting ++ requirement ++ " [ ]"++, exampleProgress = \(nesting, requirement) p -> do+ writeTransient $ indentationFor nesting ++ requirement ++ " [" ++ (formatProgress p) ++ "]"++, exampleSucceeded = \(nesting, requirement) info -> do+ writeResult nesting requirement info $ withSuccessColor $ write "✔"++, exampleFailed = \(nesting, requirement) info _ -> do+ writeResult nesting requirement info $ withFailColor $ write "✘"++, examplePending = \(nesting, requirement) info reason -> do+ writeResult nesting requirement info $ withPendingColor $ write "‐"++ withPendingColor $ do+ writeLine $ indentationFor ("" : nesting) ++ "# PENDING: " ++ fromMaybe "No reason given" reason+} where+ indentationFor nesting = replicate (length nesting * 2) ' '++ writeResult :: [String] -> String -> String -> FormatM () -> FormatM ()+ writeResult nesting requirement info action = do+ write $ indentationFor nesting ++ requirement ++ " ["+ action+ writeLine "]"+ forM_ (lines info) $ \ s ->+ writeLine $ indentationFor ("" : nesting) ++ s++ formatProgress (current, total)+ | total == 0 = show current+ | otherwise = show current ++ "/" ++ show total++specdoc :: Formatter+specdoc = silent {++ headerFormatter = do+ writeLine ""++, exampleGroupStarted = \nesting name -> do+ writeLine (indentationFor nesting ++ name)++, exampleProgress = \_ p -> do+ writeTransient (formatProgress p)++, exampleSucceeded = \(nesting, requirement) info -> withSuccessColor $ do+ writeLine $ indentationFor nesting ++ requirement+ forM_ (lines info) $ \ s ->+ writeLine $ indentationFor ("" : nesting) ++ s++, exampleFailed = \(nesting, requirement) info _ -> withFailColor $ do+ n <- getFailCount+ writeLine $ indentationFor nesting ++ requirement ++ " FAILED [" ++ show n ++ "]"+ forM_ (lines info) $ \ s ->+ writeLine $ indentationFor ("" : nesting) ++ s++, examplePending = \(nesting, requirement) info reason -> withPendingColor $ do+ writeLine $ indentationFor nesting ++ requirement+ forM_ (lines info) $ \ s ->+ writeLine $ indentationFor ("" : nesting) ++ s+ writeLine $ indentationFor ("" : nesting) ++ "# PENDING: " ++ fromMaybe "No reason given" reason++, failedFormatter = defaultFailedFormatter++, footerFormatter = defaultFooter+} where+ indentationFor nesting = replicate (length nesting * 2) ' '+ formatProgress (current, total)+ | total == 0 = show current+ | otherwise = show current ++ "/" ++ show total+++progress :: Formatter+progress = silent {+ exampleSucceeded = \_ _ -> withSuccessColor $ write "."+, exampleFailed = \_ _ _ -> withFailColor $ write "F"+, examplePending = \_ _ _ -> withPendingColor $ write "."+, failedFormatter = defaultFailedFormatter+, footerFormatter = defaultFooter+}+++failed_examples :: Formatter+failed_examples = silent {+ failedFormatter = defaultFailedFormatter+, footerFormatter = defaultFooter+}++defaultFailedFormatter :: FormatM ()+defaultFailedFormatter = do+ writeLine ""++ failures <- getFailMessages++ unless (null failures) $ do+ writeLine "Failures:"+ writeLine ""++ forM_ (zip [1..] failures) $ \x -> do+ formatFailure x+ writeLine ""++ write "Randomized with seed " >> usedSeed >>= writeLine . show+ writeLine ""+ where+ formatFailure :: (Int, FailureRecord) -> FormatM ()+ formatFailure (n, FailureRecord mLoc path reason) = do+ forM_ mLoc $ \loc -> do+ withInfoColor $ writeLine (formatLoc loc)+ write (" " ++ show n ++ ") ")+ writeLine (formatRequirement path)+ case reason of+ NoReason -> pass+ Reason err -> withFailColor $ indent err+ ExpectedButGot preface expected actual -> do+ mapM_ indent preface++ b <- useDiff++ let threshold = 2 :: Seconds++ mchunks <- liftIO $ if b+ then timeout threshold (evaluate $ diff Nothing expected actual)+ else return Nothing++ case mchunks of+ Just chunks -> do+ writeDiff chunks extraChunk missingChunk+ Nothing -> do+ writeDiff [First expected, Second actual] write write+ where+ indented output text = case break (== '\n') text of+ (xs, "") -> output xs+ (xs, _ : ys) -> output (xs ++ "\n") >> write (indentation ++ " ") >> indented output ys++ writeDiff chunks extra missing = do+ withFailColor $ write (indentation ++ "expected: ")+ forM_ chunks $ \ case+ Both a -> indented write a+ First a -> indented extra a+ Second _ -> pass+ Omitted _ -> pass+ writeLine ""++ withFailColor $ write (indentation ++ " but got: ")+ forM_ chunks $ \ case+ Both a -> indented write a+ First _ -> pass+ Second a -> indented missing a+ Omitted _ -> pass+ writeLine ""++ Error _ e -> withFailColor . indent $ (("uncaught exception: " ++) . formatException) e++ writeLine ""+ writeLine (" To rerun use: --match " ++ show (joinPath path))+ where+ indentation = " "+ indent message = do+ forM_ (lines message) $ \line -> do+ writeLine (indentation ++ line)+ formatLoc (Location file line column) = " " ++ file ++ ":" ++ show line ++ ":" ++ show column ++ ": "++defaultFooter :: FormatM ()+defaultFooter = do++ writeLine =<< (++)+ <$> (printf "Finished in %1.4f seconds" <$> getRealTime)+ <*> (maybe "" (printf ", used %1.4f seconds of CPU time") <$> getCPUTime)++ fails <- getFailCount+ pending <- getPendingCount+ total <- getTotalCount++ let+ output =+ pluralize total "example"+ ++ ", " ++ pluralize fails "failure"+ ++ if pending == 0 then "" else ", " ++ show pending ++ " pending"+ c | fails /= 0 = withFailColor+ | pending /= 0 = withPendingColor+ | otherwise = withSuccessColor+ c $ writeLine output
hspec-core/src/Test/Hspec/Core/Formatters/V1/Monad.hs view
@@ -2,7 +2,6 @@ {-# LANGUAGE RankNTypes #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE RecordWildCards #-}-{-# LANGUAGE StandaloneDeriving #-} {-# LANGUAGE ExistentialQuantification #-} module Test.Hspec.Core.Formatters.V1.Monad ( Formatter(..)@@ -48,7 +47,14 @@ import Test.Hspec.Core.Formatters.V1.Free import Test.Hspec.Core.Clock-import Test.Hspec.Core.Format+import Test.Hspec.Core.Format hiding (FailureReason)++data FailureReason =+ NoReason+ | Reason String+ | ExpectedButGot (Maybe String) String String+ | Error (Maybe String) SomeException+ deriving Show data Formatter = Formatter {
hspec-core/src/Test/Hspec/Core/Formatters/V2.hs view
@@ -1,11 +1,14 @@ {-# LANGUAGE CPP #-}+{-# LANGUAGE LambdaCase #-} -- |--- Stability: experimental+-- Stability: unstable ----- This module contains formatters that can be used with--- `Test.Hspec.Core.Runner.hspecWith`.-module Test.Hspec.Core.Formatters.V2 (-+-- This is an unstable API. Use+-- [Test.Hspec.Api.Formatters.V3](https://hackage.haskell.org/package/hspec-api/docs/Test-Hspec-Api-Formatters-V3.html)+-- instead.+module Test.Hspec.Core.Formatters.V2+-- {-# WARNING "Use [Test.Hspec.Api.Formatters.V3](https://hackage.haskell.org/package/hspec-api/docs/Test-Hspec-Api-Formatters-V3.html) instead." #-}+( -- * Formatters silent , checks@@ -21,6 +24,9 @@ -- Actions live in the `FormatM` monad. It provides access to the runner state -- and primitives for appending to the generated report. , Formatter (..)+, Path+, Progress+, Location(..) , Item(..) , Result(..) , FailureReason (..)@@ -58,11 +64,16 @@ , outputUnicode , useDiff+, diffContext+, externalDiffAction , prettyPrint , prettyPrintFunction , extraChunk , missingChunk +-- ** expert mode+, unlessExpert+ -- ** Helpers , formatLocation , formatException@@ -76,16 +87,15 @@ import Prelude () import Test.Hspec.Core.Compat hiding (First)+import System.IO (hFlush, stdout) import Data.Char-import Data.Maybe import Test.Hspec.Core.Util import Test.Hspec.Core.Clock-import Test.Hspec.Core.Example (Location(..))+import Test.Hspec.Core.Example (Location(..), Progress) import Text.Printf import Test.Hspec.Core.Formatters.Pretty.Unicode (ushow) import Control.Monad.IO.Class-import Control.Exception -- We use an explicit import list for "Test.Hspec.Formatters.Monad", to make -- sure, that we only use the public API to implement formatters.@@ -126,29 +136,33 @@ , outputUnicode , useDiff+ , diffContext+ , externalDiffAction , prettyPrint , prettyPrintFunction , extraChunk , missingChunk++ , unlessExpert ) import Test.Hspec.Core.Formatters.Diff silent :: Formatter silent = Formatter {- formatterStarted = return ()-, formatterGroupStarted = \ _ -> return ()-, formatterGroupDone = \ _ -> return ()-, formatterProgress = \ _ _ -> return ()-, formatterItemStarted = \ _ -> return ()-, formatterItemDone = \ _ _ -> return ()-, formatterDone = return ()+ formatterStarted = pass+, formatterGroupStarted = \ _ -> pass+, formatterGroupDone = \ _ -> pass+, formatterProgress = \ _ _ -> pass+, formatterItemStarted = \ _ -> pass+, formatterItemDone = \ _ _ -> pass+, formatterDone = pass } checks :: Formatter checks = specdoc { formatterProgress = \(nesting, requirement) p -> do- writeTransient $ indentationFor nesting ++ requirement ++ " [" ++ (formatProgress p) ++ "]"+ writeTransient $ indentationFor nesting ++ requirement ++ " [" ++ formatProgress p ++ "]" , formatterItemStarted = \(nesting, requirement) -> do writeTransient $ indentationFor nesting ++ requirement ++ " [ ]"@@ -161,8 +175,8 @@ Pending {} -> (withPendingColor, fallback "‐" "-") Failure {} -> (withFailColor, fallback "✘" "x") case itemResult item of- Success {} -> return ()- Failure {} -> return ()+ Success {} -> pass+ Failure {} -> pass Pending _ reason -> withPendingColor $ do writeLine $ indentationFor ("" : nesting) ++ "# PENDING: " ++ fromMaybe "No reason given" reason } where@@ -237,10 +251,12 @@ progress :: Formatter progress = failed_examples {- formatterItemDone = \ _ item -> case itemResult item of- Success{} -> withSuccessColor $ write "."- Pending{} -> withPendingColor $ write "."- Failure{} -> withFailColor $ write "F"+ formatterItemDone = \ _ item -> do+ case itemResult item of+ Success{} -> withSuccessColor $ write "."+ Pending{} -> withPendingColor $ write "."+ Failure{} -> withFailColor $ write "F"+ liftIO $ hFlush stdout } failed_examples :: Formatter@@ -273,8 +289,9 @@ write (" " ++ show n ++ ") ") writeLine (formatRequirement path) case reason of- NoReason -> return ()+ NoReason -> pass Reason err -> withFailColor $ indent err+ ColorizedReason err -> indent err ExpectedButGot preface expected_ actual_ -> do pretty <- prettyPrintFunction let@@ -288,15 +305,24 @@ let threshold = 2 :: Seconds - mchunks <- liftIO $ if b- then timeout threshold (evaluate $ diff expected actual)- else return Nothing - case mchunks of- Just chunks -> do- writeDiff chunks extraChunk missingChunk+ mExternalDiff <- externalDiffAction++ case mExternalDiff of+ Just externalDiff -> do+ liftIO $ externalDiff expected actual+ Nothing -> do- writeDiff [First expected, Second actual] write write+ context <- diffContext+ mchunks <- liftIO $ if b+ then timeout threshold (evaluate $ diff context expected actual)+ else return Nothing++ case mchunks of+ Just chunks -> do+ writeDiff chunks extraChunk missingChunk+ Nothing -> do+ writeDiff [First expected, Second actual] write write where writeDiff chunks extra missing = do writeChunks "expected: " (expectedChunks chunks) extra@@ -305,9 +331,10 @@ writeChunks :: String -> [Chunk] -> (String -> FormatM ()) -> FormatM () writeChunks pre chunks colorize = do withFailColor $ write (indentation ++ pre)- forM_ (indentChunks indentation_ chunks) $ \ chunk -> case chunk of+ forM_ (indentChunks indentation_ chunks) $ \ case PlainChunk a -> write a ColorChunk a -> colorize a+ Informational a -> withInfoColor $ write a writeLine "" where indentation_ = indentation ++ replicate (length pre) ' '@@ -316,39 +343,65 @@ mapM_ indent info withFailColor . indent $ "uncaught exception: " ++ formatException e - writeLine "" - let path_ = (if unicode then ushow else show) (joinPath path)- writeLine (" To rerun use: --match " ++ path_)+ unlessExpert $ do+ let path_ = (if unicode then ushow else show) (joinPath path)+ writeLine ""+ writeLine (" To rerun use: --match " ++ path_) where indentation = " " indent message = do forM_ (lines message) $ \line -> do writeLine (indentation ++ line) -data Chunk = Original String | Modified String+data Chunk = Original String | Modified String | OmittedLines Int deriving (Eq, Show) expectedChunks :: [Diff] -> [Chunk]-expectedChunks = mapMaybe $ \ chunk -> case chunk of+expectedChunks = mapMaybe $ \ case Both a -> Just $ Original a First a -> Just $ Modified a Second _ -> Nothing+ Omitted n -> Just $ OmittedLines n actualChunks :: [Diff] -> [Chunk]-actualChunks = mapMaybe $ \ chunk -> case chunk of+actualChunks = mapMaybe $ \ case Both a -> Just $ Original a First _ -> Nothing Second a -> Just $ Modified a+ Omitted n -> Just $ OmittedLines n -data ColorChunk = PlainChunk String | ColorChunk String+data ColorChunk = PlainChunk String | ColorChunk String | Informational String deriving (Eq, Show) +data StartsWith = StartsWithNewline | StartsWithNonNewline+ deriving Eq+ indentChunks :: String -> [Chunk] -> [ColorChunk]-indentChunks indentation = concatMap $ \ chunk -> case chunk of- Original y -> [indentOriginal indentation y]- Modified y -> indentModified indentation y+indentChunks indentation = go+ where+ go :: [Chunk] -> [ColorChunk]+ go = \ case+ Original x : xs -> indentOriginal indentation x : go xs+ Modified x : xs -> indentModified (startsWith xs) indentation x ++ go xs+ OmittedLines n : xs -> Informational (formatOmittedLines n) : go xs+ [] -> [] + startsWith :: [Chunk] -> StartsWith+ startsWith xs+ | all isSpace (takeWhile (/= '\n') $ unChunks xs) = StartsWithNewline+ | otherwise = StartsWithNonNewline++ unChunks :: [Chunk] -> String+ unChunks = \ case+ Original x : xs -> x ++ unChunks xs+ Modified x : xs -> x ++ unChunks xs+ OmittedLines {} : _ -> ""+ [] -> ""++ formatOmittedLines :: Int -> String+ formatOmittedLines n = "@@ " <> show n <> " lines omitted @@\n" <> indentation+ indentOriginal :: String -> String -> ColorChunk indentOriginal indentation = PlainChunk . go where@@ -356,18 +409,21 @@ (xs, _ : ys) -> xs ++ "\n" ++ indentation ++ go ys (xs, "") -> xs -indentModified :: String -> String -> [ColorChunk]-indentModified indentation = go+indentModified :: StartsWith -> String -> String -> [ColorChunk]+indentModified nextChunk indentation = go where- go text = case text of+ go :: String -> [ColorChunk]+ go = \ case+ "" -> [] "\n" -> [PlainChunk "\n", ColorChunk indentation] '\n' : ys@('\n' : _) -> PlainChunk "\n" : ColorChunk indentation : go ys- _ -> case break (== '\n') text of- (xs, _ : ys) -> segment xs ++ PlainChunk ('\n' : indentation) : go ys- (xs, "") -> segment xs+ '\n' : xs -> PlainChunk ('\n' : indentation) : go xs+ text -> case break (== '\n') text of+ (xs, "") | nextChunk == StartsWithNonNewline -> [ColorChunk xs]+ (xs, ys) -> segment xs ++ go ys + segment :: String -> [ColorChunk] segment xs = case span isSpace $ reverse xs of- ("", "") -> [] ("", _) -> [ColorChunk xs] (_, "") -> [ColorChunk xs] (ys, zs) -> [ColorChunk (reverse zs), ColorChunk (reverse ys)]@@ -388,10 +444,12 @@ pluralize total "example" ++ ", " ++ pluralize fails "failure" ++ if pending == 0 then "" else ", " ++ show pending ++ " pending"- c | fails /= 0 = withFailColor++ color+ | fails /= 0 = withFailColor | pending /= 0 = withPendingColor | otherwise = withSuccessColor- c $ writeLine output+ color $ writeLine output formatLocation :: Location -> String formatLocation (Location file line column) = file ++ ":" ++ show line ++ ":" ++ show column ++ ": "
hspec-core/src/Test/Hspec/Core/Hooks.hs view
@@ -4,7 +4,12 @@ {-# LANGUAGE ConstraintKinds #-} -- | Stability: provisional module Test.Hspec.Core.Hooks (- before+-- * Types+ Spec+, SpecWith+, ActionWith+-- * Hooks+, before , before_ , beforeWith , beforeAll@@ -31,9 +36,7 @@ import Prelude () import Test.Hspec.Core.Compat-import Data.CallStack (HasCallStack) -import Control.Exception (SomeException, finally, throwIO, try) import Control.Concurrent import Test.Hspec.Core.Example@@ -111,11 +114,12 @@ -- | Run a custom action before and/or after every spec item. aroundWith :: (ActionWith a -> ActionWith b) -> SpecWith a -> SpecWith b-aroundWith = mapSpecItem_ . modifyAroundAction+aroundWith = mapSpecItem_ . modifyHook -modifyAroundAction :: (ActionWith a -> ActionWith b) -> Item a -> Item b-modifyAroundAction action item@Item{itemExample = e} =- item{ itemExample = \params aroundAction -> e params (aroundAction . action) }+modifyHook :: (ActionWith a -> ActionWith b) -> Item a -> Item b+modifyHook action item = item {+ itemExample = \ params hook -> itemExample item params (hook . action)+ } -- | Wrap an action around the given spec. aroundAll :: HasCallStack => (ActionWith a -> IO ()) -> SpecWith a -> Spec@@ -178,7 +182,7 @@ signal doCleanupNow r <- takeMVar released case r of- Released -> return ()+ Released -> pass ExceptionDuringRelease err -> throwIO err return (acquire, release)
hspec-core/src/Test/Hspec/Core/QuickCheckUtil.hs view
@@ -1,11 +1,27 @@ {-# LANGUAGE RecordWildCards #-}-module Test.Hspec.Core.QuickCheckUtil where+{-# LANGUAGE CPP #-}+module Test.Hspec.Core.QuickCheckUtil (+ liftHook+, aroundProperty +, QuickCheckResult(..)+, Status(..)+, QuickCheckFailure(..)+, parseQuickCheckResult++, formatNumbers++, mkGen+, newSeed+#ifdef TEST+, stripSuffix+, splitBy+#endif+) where+ import Prelude () import Test.Hspec.Core.Compat -import Control.Exception-import Data.Maybe import Data.Int import System.Random @@ -21,17 +37,21 @@ import Test.Hspec.Core.Util +liftHook :: r -> ((a -> IO ()) -> IO ()) -> (a -> IO r) -> IO r+liftHook def hook inner = do+ ref <- newIORef def+ hook $ inner >=> writeIORef ref+ readIORef ref+ aroundProperty :: ((a -> IO ()) -> IO ()) -> (a -> Property) -> Property-aroundProperty action p = MkProperty . MkGen $ \r n -> aroundProp action $ \a -> (unGen . unProperty $ p a) r n+aroundProperty hook p = MkProperty . MkGen $ \r n -> aroundProp hook $ \a -> (unGen . unProperty $ p a) r n aroundProp :: ((a -> IO ()) -> IO ()) -> (a -> Prop) -> Prop-aroundProp action p = MkProp $ aroundRose action (\a -> unProp $ p a)+aroundProp hook p = MkProp $ aroundRose hook (\a -> unProp $ p a) aroundRose :: ((a -> IO ()) -> IO ()) -> (a -> Rose QCP.Result) -> Rose QCP.Result-aroundRose action r = ioRose $ do- ref <- newIORef (return QCP.succeeded)- action $ \a -> reduceRose (r a) >>= writeIORef ref- readIORef ref+aroundRose hook r = ioRose $ do+ liftHook (return QCP.succeeded) hook $ \ a -> reduceRose (r a) newSeed :: IO Int newSeed = fst . randomR (0, fromIntegral (maxBound :: Int32)) <$>
hspec-core/src/Test/Hspec/Core/Runner.hs view
@@ -1,9 +1,22 @@+{-# OPTIONS_GHC -fno-warn-deprecations #-} {-# LANGUAGE CPP #-} {-# LANGUAGE BangPatterns #-}+{-# LANGUAGE ScopedTypeVariables #-} -- | -- Stability: provisional module Test.Hspec.Core.Runner (+-- * Simple interface+ hspec+, hspecWith+, hspecResult+, hspecWithResult++-- ** Summary+, Summary (..)+, isSuccess+, evaluateSummary+ -- * Running a spec {- | To run a spec `hspec` performs a sequence of steps:@@ -16,7 +29,8 @@ The four primitives `evalSpec`, `readConfig`, `runSpecForest` and `evaluateResult` each perform one of these steps respectively. -`hspec` is defined in terms of these primitives:+`hspec` is defined in terms of these primitives. Loosely speaking, a definition+for @hspec@ is: @ hspec = `evalSpec` `defaultConfig` >=> \\ (config, spec) ->@@ -26,55 +40,57 @@ >>= `evaluateResult` @ -If you need more control over how a spec is run use these primitives individually.+Loosely speaking in the sense that this definition of @hspec@ ignores+@--rerun-all-on-success@. +Using these primitives individually gives you more control over how a spec is+run. However, if you need support for @--rerun-all-on-success@ then you should+try hard to solve your use case with one of `hspec`, `hspecWith`, `hspecResult`+or `hspecWithResult`.+ -}- hspec , evalSpec , runSpecForest , evaluateResult --- * Config-, Config (..)-, ColorMode (..)-, UnicodeMode(..)-, Path-, defaultConfig-, registerFormatter-, registerDefaultFormatter-, configAddFilter-, readConfig---- * Result+-- ** Result --- ** Spec Result+-- *** Spec Result , Test.Hspec.Core.Runner.Result.SpecResult , Test.Hspec.Core.Runner.Result.specResultItems , Test.Hspec.Core.Runner.Result.specResultSuccess+, toSummary --- ** Result Item+-- *** Result Item , Test.Hspec.Core.Runner.Result.ResultItem , Test.Hspec.Core.Runner.Result.resultItemPath , Test.Hspec.Core.Runner.Result.resultItemStatus , Test.Hspec.Core.Runner.Result.resultItemIsFailure --- ** Result Item Status+-- *** Result Item Status , Test.Hspec.Core.Runner.Result.ResultItemStatus(..) +-- * Config+, Config (..)+, ColorMode (..)+, UnicodeMode(..)+, Path+, defaultConfig+, registerFormatter+, registerDefaultFormatter+, configAddFilter+, readConfig+ -- * Legacy--- | The following primitives are deprecated. Use `runSpecForest` instead.-, hspecWith-, hspecResult-, hspecWithResult , runSpec --- ** Summary-, Summary (..)-, toSummary-, isSuccess-, evaluateSummary+-- * Re-exports+, Spec+, SpecWith #ifdef TEST+, UseColor(..)+, ProgressReporting(..) , rerunAll , specToEvalForest , colorOutputSupported@@ -85,12 +101,10 @@ import Prelude () import Test.Hspec.Core.Compat -import Data.Maybe import NonEmpty (nonEmpty) import System.IO import System.Environment (getArgs, withArgs)-import System.Exit-import qualified Control.Exception as E+import System.Exit (exitFailure) import System.Random import Control.Monad.ST import Data.STRef@@ -99,7 +113,9 @@ import qualified Test.QuickCheck as QC import Test.Hspec.Core.Util (Path)+import Test.Hspec.Core.Clock import Test.Hspec.Core.Spec hiding (pruneTree, pruneForest)+import Test.Hspec.Core.Tree (formatDefaultDescription) import Test.Hspec.Core.Config import Test.Hspec.Core.Format (Format, FormatConfig(..)) import qualified Test.Hspec.Core.Formatters.V1 as V1@@ -109,18 +125,24 @@ import Test.Hspec.Core.Shuffle import Test.Hspec.Core.Runner.PrintSlowSpecItems-import Test.Hspec.Core.Runner.Eval hiding (Tree(..))+import Test.Hspec.Core.Runner.Eval hiding (ColorMode(..), Tree(..)) import qualified Test.Hspec.Core.Runner.Eval as Eval import Test.Hspec.Core.Runner.Result -- | -- Make a formatter available for use with @--format@.+--+-- @since 2.10.5 registerFormatter :: (String, FormatConfig -> IO Format) -> Config -> Config+{-# DEPRECATED registerFormatter "Use [@registerFormatter@](https://hackage.haskell.org/package/hspec-api/docs/Test-Hspec-Api-Format-V2.html#v:registerFormatter) instead." #-} registerFormatter formatter config = config { configAvailableFormatters = formatter : configAvailableFormatters config } -- | -- Make a formatter available for use with @--format@ and use it by default.+--+-- @since 2.10.5 registerDefaultFormatter :: (String, FormatConfig -> IO Format) -> Config -> Config+{-# DEPRECATED registerDefaultFormatter "Use [@useFormatter@](https://hackage.haskell.org/package/hspec-api/docs/Test-Hspec-Api-Format-V2.html#v:useFormatter) instead." #-} registerDefaultFormatter formatter@(_, format) config = (registerFormatter formatter config) { configFormat = Just format } applyFilterPredicates :: Config -> [Tree c EvalItem] -> [Tree c EvalItem]@@ -143,10 +165,10 @@ | otherwise = id where removeCleanup :: IO () -> IO ()- removeCleanup _ = return ()+ removeCleanup _ = pass markSuccess :: EvalItem -> EvalItem- markSuccess item = item {evalItemAction = \ _ -> return $ Result "" Success}+ markSuccess item = item {evalItemAction = \ _ -> return (0, Result "" Success)} -- | Run a given spec and write a report to `stdout`. -- Exit with `exitFailure` if at least one spec item fails.@@ -155,11 +177,7 @@ -- is not always desirable. Use `evalSpec` and `runSpecForest` if you need -- more control over these aspects. hspec :: Spec -> IO ()-hspec = evalSpec defaultConfig >=> \ (config, spec) ->- getArgs- >>= readConfig config- >>= doNotLeakCommandLineArgumentsToExamples . runSpecForest spec- >>= evaluateResult+hspec = hspecWith defaultConfig -- | -- Evaluate a `Spec` to a forest of `SpecTree`s. This does not execute any@@ -187,12 +205,7 @@ -- | Run given spec with custom options. -- This is similar to `hspec`, but more flexible. hspecWith :: Config -> Spec -> IO ()-hspecWith defaults = hspecWithResult defaults >=> evaluateSummary---- | `True` if the given `Summary` indicates that there were no--- failures, `False` otherwise.-isSuccess :: Summary -> Bool-isSuccess summary = summaryFailures summary == 0+hspecWith defaults = hspecWithSpecResult defaults >=> evaluateResult -- | Exit with `exitFailure` if the given `Summary` indicates that there was at -- least one failure.@@ -216,11 +229,39 @@ -- items. If you need this, you have to check the `Summary` yourself and act -- accordingly. hspecWithResult :: Config -> Spec -> IO Summary-hspecWithResult defaults = evalSpec defaults >=> \ (config, spec) ->- getArgs- >>= readConfig config- >>= doNotLeakCommandLineArgumentsToExamples . fmap toSummary . runSpecForest spec+hspecWithResult config = fmap toSummary . hspecWithSpecResult config +hspecWithSpecResult :: Config -> Spec -> IO SpecResult+hspecWithSpecResult defaults spec = do+ (c, forest) <- evalSpec defaults spec+ config <- getArgs >>= readConfig c+ oldFailureReport <- readFailureReportOnRerun config++ let+ normalMode :: IO SpecResult+ normalMode = doNotLeakCommandLineArgumentsToExamples $ runSpecForest_ oldFailureReport forest config++ rerunAllMode :: IO SpecResult+ rerunAllMode = do+ result <- normalMode+ if rerunAll config oldFailureReport result then+ hspecWithSpecResult defaults spec+ else+ return result++ -- With --rerun-all we may run the spec twice. For that reason GHC can not+ -- optimize away the spec tree. That means that the whole spec tree has to+ -- be constructed in memory and we loose constant space behavior.+ --+ -- By separating between rerunAllMode and normalMode here, we retain+ -- constant space behavior in normalMode.+ --+ -- see: https://github.com/hspec/hspec/issues/169+ if configRerunAllOnSuccess config then do+ rerunAllMode+ else do+ normalMode+ -- | -- /Note/: `runSpec` is deprecated. It ignores any modifications applied -- through `modifyConfig`. Use `evalSpec` and `runSpecForest` instead.@@ -241,62 +282,65 @@ -- -- @since 2.10.0 runSpecForest :: [SpecTree ()] -> Config -> IO SpecResult-runSpecForest spec c_ = do- oldFailureReport <- readFailureReportOnRerun c_+runSpecForest spec config = do+ oldFailureReport <- readFailureReportOnRerun config+ runSpecForest_ oldFailureReport spec config - c <- ensureSeed (applyFailureReport oldFailureReport c_)+mapItem :: (Item a -> Item b) -> [SpecTree a] -> [SpecTree b]+mapItem f = map (fmap f) - if configRerunAllOnSuccess c- -- With --rerun-all we may run the spec twice. For that reason GHC can not- -- optimize away the spec tree. That means that the whole spec tree has to- -- be constructed in memory and we loose constant space behavior.- --- -- By separating between rerunAllMode and normalMode here, we retain- -- constant space behavior in normalMode.- --- -- see: https://github.com/hspec/hspec/issues/169- then rerunAllMode c oldFailureReport- else normalMode c- where- normalMode c = runSpecForest_ c spec- rerunAllMode c oldFailureReport = do- result <- runSpecForest_ c spec- if rerunAll c oldFailureReport result- then runSpecForest spec c_- else return result+mapItemIf :: (Item a -> Bool) -> (Item a -> Item a) -> [SpecTree a] -> [SpecTree a]+mapItemIf p f = mapItem $ \ item -> if p item then f item else item -runSpecForest_ :: Config -> [SpecTree ()] -> IO SpecResult-runSpecForest_ config spec = runEvalTree config (specToEvalForest config spec)+addDefaultDescriptions :: [SpecTree a] -> [SpecTree a]+addDefaultDescriptions = mapItem addDefaultDescription+ where+ addDefaultDescription :: Item a -> Item a+ addDefaultDescription item+ | null (itemRequirement item) = item { itemRequirement = defaultRequirement }+ | otherwise = item+ where+ defaultRequirement = maybe "(unspecified behavior)" formatDefaultDescription (itemLocation item) -mapItem :: (Item a -> Item b) -> [SpecTree a] -> [SpecTree b]-mapItem f = map (fmap f)+failItemsWithEmptyDescription :: Config -> [SpecTree a] -> [SpecTree a]+failItemsWithEmptyDescription config+ | configFailOnEmptyDescription config = mapItemIf condition (failWith failure)+ | otherwise = id+ where+ condition = null . itemRequirement+ failure = "item has no description; failing due to --fail-on=empty-description" failFocusedItems :: Config -> [SpecTree a] -> [SpecTree a] failFocusedItems config- | configFailOnFocused config = mapItem failFocused+ | configFailOnFocused config = mapItemIf condition (failWith failure) | otherwise = id+ where+ condition = itemIsFocused+ failure = "item is focused; failing due to --fail-on=focused" -failFocused :: Item a -> Item a-failFocused item = item {itemExample = example}+failWith :: forall a. String -> Item a -> Item a+failWith reason item = item {itemExample = example} where- failure = Failure Nothing (Reason "item is focused; failing due to --fail-on=focused")- example- | itemIsFocused item = \ params hook p -> do- Result info status <- itemExample item params hook p- return $ Result info $ case status of- Success -> failure- Pending _ _ -> failure- Failure{} -> status- | otherwise = itemExample item+ failure :: ResultStatus+ failure = Failure Nothing (Reason reason) + example :: Params -> (ActionWith a -> IO ()) -> ProgressCallback -> IO Result+ example params hook p = do+ Result info status <- itemExample item params hook p+ return $ Result info $ case status of+ Success -> failure+ Pending _ _ -> failure+ Failure{} -> status+ failPendingItems :: Config -> [SpecTree a] -> [SpecTree a] failPendingItems config | configFailOnPending config = mapItem failPending | otherwise = id -failPending :: Item a -> Item a+failPending :: forall a. Item a -> Item a failPending item = item {itemExample = example} where+ example :: Params -> (ActionWith a -> IO ()) -> ProgressCallback -> IO Result example params hook p = do Result info status <- itemExample item params hook p return $ Result info $ case status of@@ -308,28 +352,35 @@ | configFocusedOnly config = spec | otherwise = focusForest spec -runEvalTree :: Config -> [EvalTree] -> IO SpecResult-runEvalTree config spec = do- let- failOnEmpty = configFailOnEmpty config- seed = (fromJust . configQuickCheckSeed) config- qcArgs = configQuickCheckArgs config- !numberOfItems = countSpecItems spec+runSpecForest_ :: Maybe FailureReport -> [SpecTree ()] -> Config -> IO SpecResult+runSpecForest_ oldFailureReport spec c_ = do - concurrentJobs <- case configConcurrentJobs config of- Nothing -> getDefaultConcurrentJobs- Just n -> return n+ config <- ensureSeed (applyFailureReport oldFailureReport c_) - (reportProgress, useColor) <- colorOutputSupported (configColorMode config) (hSupportsANSI stdout)+ colorMode <- colorOutputSupported (configColorMode config) (hSupportsANSI stdout) outputUnicode <- unicodeOutputSupported (configUnicodeMode config) stdout - results <- fmap (toSpecResult failOnEmpty) . withHiddenCursor reportProgress stdout $ do+ let+ filteredSpec = specToEvalForest config spec+ seed = (fromJust . configQuickCheckSeed) config+ qcArgs = configQuickCheckArgs config+ !numberOfItems = countEvalItems filteredSpec++ when (configFailOnEmpty config && numberOfItems == 0) $ do+ when (countSpecItems spec /= 0) $ do+ die "all spec items have been filtered; failing due to --fail-on=empty"++ concurrentJobs <- maybe getDefaultConcurrentJobs return $ configConcurrentJobs config++ results <- fmap toSpecResult . withHiddenCursor (progressReporting colorMode) stdout $ do let formatConfig = FormatConfig {- formatConfigUseColor = useColor- , formatConfigReportProgress = reportProgress+ formatConfigUseColor = shouldUseColor colorMode+ , formatConfigReportProgress = progressReporting colorMode == ProgressReportingEnabled , formatConfigOutputUnicode = outputUnicode , formatConfigUseDiff = configDiff config+ , formatConfigDiffContext = configDiffContext config+ , formatConfigExternalDiff = if configDiff config then ($ configDiffContext config) <$> configExternalDiff config else Nothing , formatConfigPrettyPrint = configPrettyPrint config , formatConfigPrettyPrintFunction = if configPrettyPrint config then Just (configPrettyPrintFunction config outputUnicode) else Nothing , formatConfigPrintTimes = configTimes config@@ -337,6 +388,7 @@ , formatConfigPrintCpuTime = configPrintCpuTime config , formatConfigUsedSeed = seed , formatConfigExpectedTotalCount = numberOfItems+ , formatConfigExpertMode = configExpertMode config } formatter = fromMaybe (V2.formatterToFormat V2.checks) (configFormat config <|> V1.formatterToFormat <$> configFormatter config)@@ -348,8 +400,9 @@ evalConfigFormat = format , evalConfigConcurrentJobs = concurrentJobs , evalConfigFailFast = configFailFast config+ , evalConfigColorMode = bool Eval.ColorDisabled Eval.ColorEnabled (shouldUseColor colorMode) }- runFormatter evalConfig spec+ runFormatter evalConfig filteredSpec let failures :: [Path]@@ -359,17 +412,11 @@ return results -toSummary :: SpecResult -> Summary-toSummary result = Summary {- summaryExamples = length items-, summaryFailures = length failures-} where- items = specResultItems result- failures = filter resultItemIsFailure items- specToEvalForest :: Config -> [SpecTree ()] -> [EvalTree] specToEvalForest config =- failFocusedItems config+ failItemsWithEmptyDescription config+ >>> addDefaultDescriptions+ >>> failFocusedItems config >>> failPendingItems config >>> focusSpec config >>> toEvalItemForest params@@ -378,8 +425,13 @@ >>> randomize >>> pruneForest where+ seed :: Integer seed = (fromJust . configQuickCheckSeed) config++ params :: Params params = Params (configQuickCheckArgs config) (configSmallCheckDepth config)++ randomize :: [Tree c a] -> [Tree c a] randomize | configRandomize config = randomizeForest seed | otherwise = id@@ -401,7 +453,12 @@ toEvalItemForest params = bimapForest id toEvalItem . filterForest itemIsFocused where toEvalItem :: Item () -> EvalItem- toEvalItem (Item requirement loc isParallelizable _isFocused e) = EvalItem requirement loc (fromMaybe False isParallelizable) (e params withUnit)+ toEvalItem (Item requirement loc isParallelizable _isFocused e) = EvalItem {+ evalItemDescription = requirement+ , evalItemLocation = loc+ , evalItemConcurrency = if isParallelizable == Just True then Concurrent else Sequential+ , evalItemAction = \ progress -> measure $ e params withUnit progress+ } withUnit :: ActionWith () -> IO () withUnit action = action ()@@ -419,21 +476,43 @@ doNotLeakCommandLineArgumentsToExamples :: IO a -> IO a doNotLeakCommandLineArgumentsToExamples = withArgs [] -withHiddenCursor :: Bool -> Handle -> IO a -> IO a-withHiddenCursor useColor h- | useColor = E.bracket_ (hHideCursor h) (hShowCursor h)- | otherwise = id+withHiddenCursor :: ProgressReporting -> Handle -> IO a -> IO a+withHiddenCursor progress h = case progress of+ ProgressReportingDisabled -> id+ ProgressReportingEnabled -> bracket_ (hHideCursor h) (hShowCursor h) -colorOutputSupported :: ColorMode -> IO Bool -> IO (Bool, Bool)+data UseColor = ColorDisabled | ColorEnabled ProgressReporting+ deriving (Eq, Show)++data ProgressReporting = ProgressReportingDisabled | ProgressReportingEnabled+ deriving (Eq, Show)++shouldUseColor :: UseColor -> Bool+shouldUseColor c = case c of+ ColorDisabled -> False+ ColorEnabled _ -> True++progressReporting :: UseColor -> ProgressReporting+progressReporting c = case c of+ ColorDisabled -> ProgressReportingDisabled+ ColorEnabled r -> r++colorOutputSupported :: ColorMode -> IO Bool -> IO UseColor colorOutputSupported mode isTerminalDevice = do github <- githubActions buildkite <- lookupEnv "BUILDKITE" <&> (== Just "true")- useColor <- case mode of- ColorAuto -> (github ||) <$> colorTerminal- ColorNever -> return False- ColorAlways -> return True- let reportProgress = not github && not buildkite && useColor- return (reportProgress, useColor)+ let+ progress :: ProgressReporting+ progress+ | github || buildkite = ProgressReportingDisabled+ | otherwise = ProgressReportingEnabled++ colorEnabled :: UseColor+ colorEnabled = ColorEnabled progress+ case mode of+ ColorAuto -> bool ColorDisabled colorEnabled . (github ||) <$> colorTerminal+ ColorNever -> return ColorDisabled+ ColorAlways -> return colorEnabled where githubActions :: IO Bool githubActions = lookupEnv "GITHUB_ACTIONS" <&> (== Just "true")@@ -459,29 +538,13 @@ && specResultSuccess result && (not . null) (failureReportPaths oldFailureReport) --- | Summary of a test run.-data Summary = Summary {- summaryExamples :: !Int-, summaryFailures :: !Int-} deriving (Eq, Show)--instance Monoid Summary where- mempty = Summary 0 0-#if MIN_VERSION_base(4,11,0)-instance Semigroup Summary where-#endif- (Summary x1 x2)-#if MIN_VERSION_base(4,11,0)- <>-#else- `mappend`-#endif- (Summary y1 y2) = Summary (x1 + y1) (x2 + y2)- randomizeForest :: Integer -> [Tree c a] -> [Tree c a] randomizeForest seed t = runST $ do ref <- newSTRef (mkStdGen $ fromIntegral seed) shuffleForest ref t -countSpecItems :: [Eval.Tree c a] -> Int+countEvalItems :: [Eval.Tree c a] -> Int+countEvalItems = getSum . foldMap (foldMap . const $ Sum 1)++countSpecItems :: [Tree c a] -> Int countSpecItems = getSum . foldMap (foldMap . const $ Sum 1)
hspec-core/src/Test/Hspec/Core/Runner/Eval.hs view
@@ -1,40 +1,24 @@ {-# LANGUAGE CPP #-}-{-# LANGUAGE DeriveFunctor #-}-{-# LANGUAGE DeriveFoldable #-} {-# LANGUAGE DeriveTraversable #-}-{-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE RecordWildCards #-} {-# LANGUAGE ConstraintKinds #-}-{-# LANGUAGE RankNTypes #-}--#if MIN_VERSION_base(4,6,0) && !MIN_VERSION_base(4,7,0)--- Control.Concurrent.QSem is deprecated in base-4.6.0.*-{-# OPTIONS_GHC -fno-warn-deprecations #-}-#endif- module Test.Hspec.Core.Runner.Eval ( EvalConfig(..)+, ColorMode(..) , EvalTree , Tree(..) , EvalItem(..)+, Concurrency(..) , runFormatter #ifdef TEST , mergeResults-, runSequentially #endif ) where import Prelude () import Test.Hspec.Core.Compat hiding (Monad)-import qualified Test.Hspec.Core.Compat as M -import qualified Control.Exception as E-import Control.Concurrent-import Control.Concurrent.Async hiding (cancel)- import Control.Monad.IO.Class (liftIO)-import qualified Control.Monad.IO.Class as M- import Control.Monad.Trans.Reader import Control.Monad.Trans.Class @@ -45,29 +29,31 @@ import qualified Test.Hspec.Core.Format as Format import Test.Hspec.Core.Clock import Test.Hspec.Core.Example.Location-import Test.Hspec.Core.Example (safeEvaluateResultStatus)+import Test.Hspec.Core.Example (safeEvaluateResultStatus, exceptionToResultStatus) import qualified NonEmpty import NonEmpty (NonEmpty(..)) +import Test.Hspec.Core.Runner.JobQueue+ data Tree c a = Node String (NonEmpty (Tree c a)) | NodeWithCleanup (Maybe (String, Location)) c (NonEmpty (Tree c a)) | Leaf a deriving (Eq, Show, Functor, Foldable, Traversable) --- for compatibility with GHC < 7.10.1-type Monad m = (Functor m, Applicative m, M.Monad m)-type MonadIO m = (Monad m, M.MonadIO m)- data EvalConfig = EvalConfig { evalConfigFormat :: Format , evalConfigConcurrentJobs :: Int , evalConfigFailFast :: Bool+, evalConfigColorMode :: ColorMode } +data ColorMode = ColorDisabled | ColorEnabled+ data Env = Env { envConfig :: EvalConfig+, envFailed :: IORef Bool , envResults :: IORef [(Path, Format.Item)] } @@ -78,14 +64,21 @@ type EvalM = ReaderT Env IO +setFailed :: EvalM ()+setFailed = do+ ref <- asks envFailed+ liftIO $ writeIORef ref True++hasFailed :: EvalM Bool+hasFailed = do+ ref <- asks envFailed+ liftIO $ readIORef ref+ addResult :: Path -> Format.Item -> EvalM () addResult path item = do ref <- asks envResults liftIO $ modifyIORef ref ((path, item) :) -getResults :: EvalM [(Path, Format.Item)]-getResults = reverse <$> (asks envResults >>= liftIO . readIORef)- reportItem :: Path -> Maybe Location -> EvalM (Seconds, Result) -> EvalM () reportItem path loc action = do reportItemStarted path@@ -96,17 +89,33 @@ reportItemDone :: Path -> Format.Item -> EvalM () reportItemDone path item = do+ let+ isFailure = case Format.itemResult item of+ Format.Success{} -> False+ Format.Pending{} -> False+ Format.Failure{} -> True+ when isFailure setFailed addResult path item formatEvent $ Format.ItemDone path item reportResult :: Path -> Maybe Location -> (Seconds, Result) -> EvalM () reportResult path loc (duration, result) = do+ mode <- asks (evalConfigColorMode . envConfig) case result of Result info status -> reportItemDone path $ Format.Item loc duration info $ case status of Success -> Format.Success Pending loc_ reason -> Format.Pending loc_ reason Failure loc_ err@(Error _ e) -> Format.Failure (loc_ <|> extractLocation e) err- Failure loc_ err -> Format.Failure loc_ err+ Failure loc_ err -> Format.Failure loc_ $ case mode of+ ColorEnabled -> err+ ColorDisabled -> case err of+ NoReason -> err+ Reason _ -> err+ ExpectedButGot _ _ _ -> err+ ColorizedReason r -> Reason (stripAnsi r)+#if __GLASGOW_HASKELL__ < 900+ Error _ _ -> err+#endif groupStarted :: Path -> EvalM () groupStarted = formatEvent . Format.GroupStarted@@ -117,62 +126,71 @@ data EvalItem = EvalItem { evalItemDescription :: String , evalItemLocation :: Maybe Location-, evalItemParallelize :: Bool-, evalItemAction :: ProgressCallback -> IO Result+, evalItemConcurrency :: Concurrency+, evalItemAction :: ProgressCallback -> IO (Seconds, Result) } type EvalTree = Tree (IO ()) EvalItem -- | Evaluate all examples of a given spec and produce a report.-runFormatter :: EvalConfig -> [EvalTree] -> IO ([(Path, Format.Item)])+runFormatter :: EvalConfig -> [EvalTree] -> IO [(Path, Format.Item)] runFormatter config specs = do- ref <- newIORef []+ withJobQueue (evalConfigConcurrentJobs config) $ \ queue -> do+ withTimer 0.05 $ \ timer -> do+ env <- mkEnv+ runningSpecs_ <- enqueueItems queue specs - let- start = parallelizeTree (evalConfigConcurrentJobs config) specs- cancel = cancelMany . concatMap toList . map (fmap fst)+ let+ applyReportProgress :: RunningItem_ IO -> RunningItem+ applyReportProgress item = fmap (. reportProgress timer) item - E.bracket start cancel $ \ runningSpecs -> do- withTimer 0.05 $ \ timer -> do+ runningSpecs :: [RunningTree ()]+ runningSpecs = applyCleanup $ map (fmap applyReportProgress) runningSpecs_ - format Format.Started- runReaderT (run . applyCleanup $ map (fmap (fmap (. reportProgress timer) . snd)) runningSpecs) (Env config ref) `E.finally` do- results <- reverse <$> readIORef ref- format (Format.Done results)+ getResults :: IO [(Path, Format.Item)]+ getResults = reverse <$> readIORef (envResults env) - results <- reverse <$> readIORef ref- return results+ formatItems :: IO ()+ formatItems = runReaderT (eval runningSpecs) env++ formatDone :: IO ()+ formatDone = getResults >>= format . Format.Done++ format Format.Started+ formatItems `finally` formatDone+ getResults where+ mkEnv :: IO Env+ mkEnv = Env config <$> newIORef False <*> newIORef []++ format :: Format format = evalConfigFormat config + reportProgress :: IO Bool -> Path -> Progress -> IO () reportProgress timer path progress = do r <- timer when r $ do format (Format.Progress path progress) -cancelMany :: [Async a] -> IO ()-cancelMany asyncs = do- mapM_ (killThread . asyncThreadId) asyncs- mapM_ waitCatch asyncs- data Item a = Item {- _itemDescription :: String-, _itemLocation :: Maybe Location+ itemDescription :: String+, itemLocation :: Maybe Location , itemAction :: a } deriving Functor -type Job m p a = (p -> m ()) -> m a- type RunningItem = Item (Path -> IO (Seconds, Result)) type RunningTree c = Tree c RunningItem +type RunningItem_ m = Item (Job m Progress (Seconds, Result))+type RunningTree_ m = Tree (IO ()) (RunningItem_ m)+ applyCleanup :: [RunningTree (IO ())] -> [RunningTree ()] applyCleanup = map go where go t = case t of- Leaf a -> Leaf a Node label xs -> Node label (go <$> xs) NodeWithCleanup loc cleanup xs -> NodeWithCleanup loc () (addCleanupToLastLeaf loc cleanup $ go <$> xs)+ Leaf a -> Leaf a addCleanupToLastLeaf :: Maybe (String, Location) -> IO () -> NonEmpty (RunningTree ()) -> NonEmpty (RunningTree ()) addCleanupToLastLeaf loc cleanup = go@@ -180,9 +198,9 @@ go = NonEmpty.reverse . mapHead goNode . NonEmpty.reverse goNode node = case node of+ Node description xs -> Node description (go xs)+ NodeWithCleanup loc_ () xs -> NodeWithCleanup loc_ () (go xs) Leaf item -> Leaf (addCleanupToItem loc cleanup item)- NodeWithCleanup loc_ () zs -> NodeWithCleanup loc_ () (go zs)- Node description zs -> Node description (go zs) mapHead :: (a -> a) -> NonEmpty a -> NonEmpty a mapHead f xs = case xs of@@ -212,62 +230,23 @@ Just (name, _) -> Just $ "in " ++ name ++ "-hook:" Nothing -> Nothing -type RunningItem_ m = (Async (), Item (Job m Progress (Seconds, Result)))-type RunningTree_ m = Tree (IO ()) (RunningItem_ m)--data Semaphore = Semaphore {- semaphoreWait :: IO ()-, semaphoreSignal :: IO ()-}--parallelizeTree :: MonadIO m => Int -> [EvalTree] -> IO [RunningTree_ m]-parallelizeTree n specs = do- sem <- newQSem n- mapM (traverse $ parallelizeItem sem) specs--parallelizeItem :: MonadIO m => QSem -> EvalItem -> IO (RunningItem_ m)-parallelizeItem sem EvalItem{..} = do- (asyncAction, evalAction) <- parallelize (Semaphore (waitQSem sem) (signalQSem sem)) evalItemParallelize (interruptible . evalItemAction)- return (asyncAction, Item evalItemDescription evalItemLocation evalAction)--parallelize :: MonadIO m => Semaphore -> Bool -> Job IO 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- (asyncAction, evalAction) <- runParallel (Semaphore (takeMVar mvar) (return ())) action- return (asyncAction, \ notifyPartial -> liftIO (putMVar mvar ()) >> evalAction notifyPartial)--data Parallel p a = Partial p | Return a+enqueueItems :: MonadIO m => JobQueue -> [EvalTree] -> IO [RunningTree_ m]+enqueueItems queue = mapM (traverse $ enqueueItem queue) -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)+enqueueItem :: MonadIO m => JobQueue -> EvalItem -> IO (RunningItem_ m)+enqueueItem queue EvalItem{..} = do+ job <- enqueueJob queue evalItemConcurrency evalItemAction+ return Item {+ itemDescription = evalItemDescription+ , itemLocation = evalItemLocation+ , itemAction = job >=> liftIO . either exceptionToResult return+ } where- worker mvar = 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- Partial p -> do- notifyPartial p- eval mvar notifyPartial- Return result -> return result--replaceMVar :: MVar a -> a -> IO ()-replaceMVar mvar p = tryTakeMVar mvar >> putMVar mvar p+ exceptionToResult :: SomeException -> IO (Seconds, Result)+ exceptionToResult err = (,) 0 . Result "" <$> exceptionToResultStatus err -run :: [RunningTree ()] -> EvalM ()-run specs = do+eval :: [RunningTree ()] -> EvalM ()+eval specs = do failFast <- asks (evalConfigFailFast . envConfig) sequenceActions failFast (concatMap foldSpec specs) where@@ -315,18 +294,10 @@ sequenceActions failFast = go where go :: [EvalM ()] -> EvalM ()- go [] = return ()+ go [] = pass go (action : actions) = do action stopNow <- case failFast of False -> return False- True -> any itemIsFailure <$> getResults+ True -> hasFailed unless stopNow (go actions)-- itemIsFailure :: (Path, Format.Item) -> Bool- itemIsFailure = isFailure . Format.itemResult . snd- where- isFailure r = case r of- Format.Success{} -> False- Format.Pending{} -> False- Format.Failure{} -> True
+ hspec-core/src/Test/Hspec/Core/Runner/JobQueue.hs view
@@ -0,0 +1,116 @@+{-# LANGUAGE CPP #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE ConstraintKinds #-}++module Test.Hspec.Core.Runner.JobQueue (+ MonadIO+, Job+, Concurrency(..)+, JobQueue+, withJobQueue+, enqueueJob+) where++import Prelude ()+import Test.Hspec.Core.Compat hiding (Monad)+import qualified Test.Hspec.Core.Compat as M++import Control.Concurrent+import Control.Concurrent.Async (Async, AsyncCancelled(..), async, waitCatch, asyncThreadId)++import Control.Monad.IO.Class (liftIO)+import qualified Control.Monad.IO.Class as M++-- for compatibility with GHC < 7.10.1+type Monad m = (Functor m, Applicative m, M.Monad m)+type MonadIO m = (Monad m, M.MonadIO m)++type Job m progress a = (progress -> m ()) -> m a++data Concurrency = Sequential | Concurrent++data JobQueue = JobQueue {+ _semaphore :: Semaphore+, _cancelQueue :: CancelQueue+}++data Semaphore = Semaphore {+ _wait :: IO ()+, _signal :: IO ()+}++type CancelQueue = IORef [Async ()]++withJobQueue :: Int -> (JobQueue -> IO a) -> IO a+withJobQueue concurrency = bracket new cancelAll+ where+ new :: IO JobQueue+ new = JobQueue <$> newSemaphore concurrency <*> newIORef []++ cancelAll :: JobQueue -> IO ()+ cancelAll (JobQueue _ cancelQueue) = readIORef cancelQueue >>= cancelMany++ cancelMany :: [Async a] -> IO ()+ cancelMany jobs = do+ mapM_ notifyCancel jobs+ mapM_ waitCatch jobs++ notifyCancel :: Async a -> IO ()+ notifyCancel = flip throwTo AsyncCancelled . asyncThreadId++newSemaphore :: Int -> IO Semaphore+newSemaphore capacity = do+ sem <- newQSem capacity+ return $ Semaphore (waitQSem sem) (signalQSem sem)++enqueueJob :: MonadIO m => JobQueue -> Concurrency -> Job IO progress a -> IO (Job m progress (Either SomeException a))+enqueueJob (JobQueue sem cancelQueue) concurrency = case concurrency of+ Sequential -> runSequentially cancelQueue+ Concurrent -> runConcurrently sem cancelQueue++runSequentially :: forall m progress a. MonadIO m => CancelQueue -> Job IO progress a -> IO (Job m progress (Either SomeException a))+runSequentially cancelQueue action = do+ barrier <- newEmptyMVar+ let+ wait :: IO ()+ wait = takeMVar barrier++ signal :: m ()+ signal = liftIO $ putMVar barrier ()++ job <- runConcurrently (Semaphore wait pass) cancelQueue action+ return $ \ notifyPartial -> signal >> job notifyPartial++data Partial progress a = Partial progress | Done++runConcurrently :: forall m progress a. MonadIO m => Semaphore -> CancelQueue -> Job IO progress a -> IO (Job m progress (Either SomeException a))+runConcurrently (Semaphore wait signal) cancelQueue action = do+ result :: MVar (Partial progress a) <- newEmptyMVar+ let+ worker :: IO a+ worker = bracket_ wait signal $ do+ interruptible (action partialResult) `finally` done+ where+ partialResult :: progress -> IO ()+ partialResult = replaceMVar result . Partial++ done :: IO ()+ done = replaceMVar result Done++ pushOnCancelQueue :: Async a -> IO ()+ pushOnCancelQueue = (modifyIORef cancelQueue . (:) . void)++ job <- bracket (async worker) pushOnCancelQueue return++ let+ waitForResult :: (progress -> m ()) -> m (Either SomeException a)+ waitForResult notifyPartial = do+ r <- liftIO (takeMVar result)+ case r of+ Partial progress -> notifyPartial progress >> waitForResult notifyPartial+ Done -> liftIO $ waitCatch job++ return waitForResult++replaceMVar :: MVar a -> a -> IO ()+replaceMVar mvar p = tryTakeMVar mvar >> putMVar mvar p
hspec-core/src/Test/Hspec/Core/Runner/PrintSlowSpecItems.hs view
@@ -7,6 +7,8 @@ import Prelude () import Test.Hspec.Core.Compat +import System.IO (stderr, hPutStrLn)+ import Test.Hspec.Core.Util import Test.Hspec.Core.Format @@ -26,9 +28,9 @@ Done items -> do let xs = slowItems n $ map toSlowItem items unless (null xs) $ do- putStrLn "\nSlow spec items:"+ hPutStrLn stderr "\nSlow spec items:" mapM_ printSlowSpecItem xs- _ -> return ()+ _ -> pass toSlowItem :: (Path, Item) -> SlowItem toSlowItem (path, item) = SlowItem (itemLocation item) path (toMilliseconds $ itemDuration item)@@ -38,4 +40,4 @@ printSlowSpecItem :: SlowItem -> IO () printSlowSpecItem SlowItem{..} = do- putStrLn $ " " ++ maybe "" formatLocation location ++ joinPath path ++ " (" ++ show duration ++ "ms)"+ hPutStrLn stderr $ " " ++ maybe "" formatLocation location ++ joinPath path ++ " (" ++ show duration ++ "ms)"
hspec-core/src/Test/Hspec/Core/Runner/Result.hs view
@@ -1,3 +1,4 @@+{-# LANGUAGE CPP #-} module Test.Hspec.Core.Runner.Result ( -- RE-EXPORTED from Test.Hspec.Core.Runner SpecResult(SpecResult)@@ -10,6 +11,10 @@ , resultItemIsFailure , ResultItemStatus(..)++, Summary(..)+, toSummary+, isSuccess -- END RE-EXPORTED from Test.Hspec.Core.Runner , toSpecResult@@ -21,16 +26,32 @@ import Test.Hspec.Core.Util import qualified Test.Hspec.Core.Format as Format +-- |+-- @since 2.10.0 data SpecResult = SpecResult {+ -- |+ -- @since 2.10.0 specResultItems :: [ResultItem]++ -- |+ -- @since 2.10.0 , specResultSuccess :: !Bool } deriving (Eq, Show) +-- |+-- @since 2.10.0 data ResultItem = ResultItem {+ -- |+ -- @since 2.10.0 resultItemPath :: Path++ -- |+ -- @since 2.10.0 , resultItemStatus :: ResultItemStatus } deriving (Eq, Show) +-- |+-- @since 2.10.0 resultItemIsFailure :: ResultItem -> Bool resultItemIsFailure item = case resultItemStatus item of ResultItemSuccess -> False@@ -43,11 +64,11 @@ | ResultItemFailure deriving (Eq, Show) -toSpecResult :: Bool -> [(Path, Format.Item)] -> SpecResult-toSpecResult failOnEmpty results = SpecResult items success+toSpecResult :: [(Path, Format.Item)] -> SpecResult+toSpecResult results = SpecResult items success where items = map toResultItem results- success = not (failOnEmpty && null results) && all (not . resultItemIsFailure) items+ success = all (not . resultItemIsFailure) items toResultItem :: (Path, Format.Item) -> ResultItem toResultItem (path, item) = ResultItem path status@@ -56,3 +77,35 @@ Format.Success{} -> ResultItemSuccess Format.Pending{} -> ResultItemPending Format.Failure{} -> ResultItemFailure++-- | Summary of a test run.+data Summary = Summary {+ summaryExamples :: !Int+, summaryFailures :: !Int+} deriving (Eq, Show)++instance Monoid Summary where+ mempty = Summary 0 0+#if MIN_VERSION_base(4,11,0)+instance Semigroup Summary where+#endif+ (Summary x1 x2)+#if MIN_VERSION_base(4,11,0)+ <>+#else+ `mappend`+#endif+ (Summary y1 y2) = Summary (x1 + y1) (x2 + y2)++toSummary :: SpecResult -> Summary+toSummary result = Summary {+ summaryExamples = length items+, summaryFailures = length failures+} where+ items = specResultItems result+ failures = filter resultItemIsFailure items++-- | `True` if the given `Summary` indicates that there were no+-- failures, `False` otherwise.+isSuccess :: Summary -> Bool+isSuccess summary = summaryFailures summary == 0
hspec-core/src/Test/Hspec/Core/Shuffle.hs view
@@ -16,16 +16,16 @@ import Data.STRef import Data.Array.ST -shuffleForest :: STRef s StdGen -> [Tree c a] -> ST s [Tree c a]+shuffleForest :: STRef st StdGen -> [Tree c a] -> ST st [Tree c a] shuffleForest ref xs = (shuffle ref xs >>= mapM (shuffleTree ref)) -shuffleTree :: STRef s StdGen -> Tree c a -> ST s (Tree c a)+shuffleTree :: STRef st StdGen -> Tree c a -> ST st (Tree c a) shuffleTree ref t = case t of Node d xs -> Node d <$> shuffleForest ref xs NodeWithCleanup loc c xs -> NodeWithCleanup loc c <$> shuffleForest ref xs Leaf {} -> return t -shuffle :: STRef s StdGen -> [a] -> ST s [a]+shuffle :: STRef st StdGen -> [a] -> ST st [a] shuffle ref xs = do arr <- mkArray xs bounds@(_, n) <- getBounds arr@@ -41,5 +41,5 @@ writeSTRef ref gen return a -mkArray :: [a] -> ST s (STArray s Int a)+mkArray :: [a] -> ST st (STArray st Int a) mkArray xs = newListArray (1, length xs) xs
hspec-core/src/Test/Hspec/Core/Spec.hs view
@@ -73,13 +73,15 @@ , Test.Hspec.Core.Tree.location , focusForest++-- * Re-exports+, HasCallStack+, Expectation ) where import Prelude () import Test.Hspec.Core.Compat -import qualified Control.Exception as E-import Data.CallStack import Control.Monad.Trans.Class (lift) import Control.Monad.Trans.Reader (asks) @@ -188,16 +190,16 @@ -- > it "can format text in a way that everyone likes" $ -- > pending pending :: HasCallStack => Expectation-pending = E.throwIO (Pending location Nothing)+pending = throwIO (Pending location Nothing) pending_ :: Expectation-pending_ = (E.throwIO (Pending Nothing Nothing))+pending_ = (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 :: HasCallStack => String -> Expectation-pendingWith = E.throwIO . Pending location . Just+pendingWith = throwIO . Pending location . Just -- | Get the path of `describe` labels, from the root all the way in to the -- call-site of this function.
hspec-core/src/Test/Hspec/Core/Spec/Monad.hs view
@@ -71,6 +71,8 @@ mapSpecForest :: ([SpecTree a] -> [SpecTree b]) -> SpecM a r -> SpecM b r mapSpecForest f (SpecM specs) = SpecM (mapWriterT (fmap (fmap (second f))) specs) +-- {-# DEPRECATED mapSpecItem "Use `mapSpecItem_` instead." #-}+-- | Deprecated: Use `mapSpecItem_` instead. mapSpecItem :: (ActionWith a -> ActionWith b) -> (Item a -> Item b) -> SpecWith a -> SpecWith b mapSpecItem _ = mapSpecItem_
hspec-core/src/Test/Hspec/Core/Timer.hs view
@@ -3,7 +3,6 @@ import Prelude () import Test.Hspec.Core.Compat -import Control.Exception import Control.Concurrent.Async import Test.Hspec.Core.Clock
hspec-core/src/Test/Hspec/Core/Tree.hs view
@@ -22,14 +22,16 @@ , location -- END RE-EXPORTED from Test.Hspec.Core.Spec , callSite+, formatDefaultDescription+, toModuleName ) where import Prelude () import Test.Hspec.Core.Compat -import Data.CallStack (HasCallStack, SrcLoc(..))+import Data.Char+import System.FilePath import qualified Data.CallStack as CallStack-import Data.Maybe import Test.Hspec.Core.Example @@ -52,7 +54,7 @@ where go spec = case spec of Node d xs -> Node d (map go xs)- NodeWithCleanup loc cleanup xs -> NodeWithCleanup loc (g cleanup) (map go xs)+ NodeWithCleanup loc action xs -> NodeWithCleanup loc (g action) (map go xs) Leaf item -> Leaf (f item) filterTree :: (a -> Bool) -> Tree c a -> Maybe (Tree c a)@@ -122,17 +124,18 @@ where msg :: HasCallStack => String msg- | null s = fromMaybe "(no description given)" defaultDescription+ | null s = maybe "(no description given)" formatDefaultDescription location | otherwise = s -- | The @specItem@ function creates a spec item.-specItem :: (HasCallStack, Example a) => String -> a -> SpecTree (Arg a)-specItem s e = Leaf $ Item requirement location Nothing False (safeEvaluateExample e)- where- requirement :: HasCallStack => String- requirement- | null s = fromMaybe "(unspecified behavior)" defaultDescription- | otherwise = s+specItem :: (HasCallStack, Example e) => String -> e -> SpecTree (Arg e)+specItem s e = Leaf Item {+ itemRequirement = s+ , itemLocation = location+ , itemIsParallelizable = Nothing+ , itemIsFocused = False+ , itemExample = safeEvaluateExample e+ } location :: HasCallStack => Maybe Location location = snd <$> callSite@@ -140,10 +143,16 @@ callSite :: HasCallStack => Maybe (String, Location) callSite = fmap toLocation <$> CallStack.callSite -defaultDescription :: HasCallStack => Maybe String-defaultDescription = case CallStack.callSite of- Just (_, loc) -> Just (srcLocModule loc ++ "[" ++ show (srcLocStartLine loc) ++ ":" ++ show (srcLocStartCol loc) ++ "]")- Nothing -> Nothing+formatDefaultDescription :: Location -> String+formatDefaultDescription loc = toModuleName (locationFile loc) ++ "[" ++ show (locationLine loc) ++ ":" ++ show (locationColumn loc) ++ "]" -toLocation :: SrcLoc -> Location-toLocation loc = Location (srcLocFile loc) (srcLocStartLine loc) (srcLocStartCol loc)+toModuleName :: FilePath -> String+toModuleName = intercalate "." . reverse . takeWhile isModuleNameComponent . reverse . splitDirectories . dropExtension++isModuleNameComponent :: String -> Bool+isModuleNameComponent name = case name of+ x : xs -> isUpper x && all isIdChar xs+ _ -> False++isIdChar :: Char -> Bool+isIdChar c = isAlphaNum c || c == '_' || c == '\''
hspec-core/src/Test/Hspec/Core/Util.hs view
@@ -1,9 +1,11 @@+{-# LANGUAGE ViewPatterns #-} -- | Stability: unstable module Test.Hspec.Core.Util ( -- * String functions pluralize , strip , lineBreaksAt+, stripAnsi -- * Working with paths , Path@@ -21,7 +23,6 @@ import Data.Char (isSpace) import GHC.IO.Exception-import Control.Exception import Control.Concurrent.Async -- |@@ -38,17 +39,23 @@ -- -- >>> pluralize 2 "example" -- "2 examples"+--+-- @since 2.0.0 pluralize :: Int -> String -> String pluralize 1 s = "1 " ++ s pluralize n s = show n ++ " " ++ s ++ "s" -- | Strip leading and trailing whitespace+--+-- @since 2.0.0 strip :: String -> String strip = dropWhile isSpace . reverse . dropWhile isSpace . reverse -- |--- ensure that lines are not longer than given `n`, insert line breaks at word+-- Ensure that lines are not longer than given `n`, insert line breaks at word -- boundaries+--+-- @since 2.0.0 lineBreaksAt :: Int -> String -> [String] lineBreaksAt n = concatMap f . lines where@@ -65,20 +72,38 @@ else s : go (y, ys) -- |+-- Remove ANSI color escape sequences.+--+-- @since 2.11.0+stripAnsi :: String -> String+stripAnsi = go+ where+ go input = case input of+ '\ESC' : '[' : (dropWhile (`elem` "0123456789;") -> 'm' : xs) -> go xs+ x : xs -> x : go xs+ [] -> []++-- | -- A `Path` describes the location of a spec item within a spec tree. -- -- It consists of a list of group descriptions and a requirement description.+--+-- @since 2.0.0 type Path = ([String], String) -- | -- Join a `Path` with slashes. The result will have a leading and a trailing -- slash.+--+-- @since 2.5.4 joinPath :: Path -> String joinPath (groups, requirement) = "/" ++ intercalate "/" (groups ++ [requirement]) ++ "/" -- | -- Try to create a proper English sentence from a path by applying some -- heuristics.+--+-- @since 2.0.0 formatRequirement :: Path -> String formatRequirement (groups, requirement) = groups_ ++ requirement where@@ -91,6 +116,8 @@ ys -> concatMap (++ ", ") ys -- | A predicate that can be used to filter a spec tree.+--+-- @since 2.0.0 filterPredicate :: String -> Path -> Bool filterPredicate pattern path = pattern `isInfixOf` plain@@ -107,6 +134,8 @@ -- "ArithException\ndivide by zero" -- -- For `IOException`s the `IOErrorType` is included, as well.+--+-- @since 2.0.0 formatException :: SomeException -> String formatException err@(SomeException e) = case fromException err of Just ioe -> showType ioe ++ " of type " ++ showIOErrorType ioe ++ "\n" ++ show ioe@@ -137,5 +166,7 @@ -- | @safeTry@ evaluates given action and returns its result. If an exception -- occurs, the exception is returned instead. Unlike `try` it is agnostic to -- asynchronous exceptions.+--+-- @since 2.0.0 safeTry :: IO a -> IO (Either SomeException a) safeTry action = withAsync (action >>= evaluate) waitCatch
− hspec-core/vendor/Control/Concurrent/Async.hs
@@ -1,963 +0,0 @@-{-# LANGUAGE CPP, MagicHash, UnboxedTuples, RankNTypes,- ExistentialQuantification #-}-#if __GLASGOW_HASKELL__ >= 701-{-# LANGUAGE Trustworthy #-}-#endif-#if __GLASGOW_HASKELL__ < 710-{-# LANGUAGE DeriveDataTypeable #-}-#endif-{-# OPTIONS -Wall -fno-warn-implicit-prelude -fno-warn-unused-imports #-}---------------------------------------------------------------------------------- |--- 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 'forkIO' threads and @MVar@ directly.------ == High-level API------ @async@'s high-level API spawns /lexically scoped/ threads,--- ensuring the following key poperties that make it safer to use--- than using plain 'forkIO':------ 1. No exception is swallowed (waiting for results propagates exceptions).--- 2. No thread is leaked (left running unintentionally).------ (This is done using the 'Control.Exception.bracket' pattern to work in presence--- of synchornous and asynchronous exceptions.)------ __Most practical/production code should only use the high-level API__.------ 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' is a wrapper around a low-level 'forkIO' thread.------ The fundamental function to spawn threads with the high-level API is--- 'withAsync'.------ For example, to fetch two web pages at the same time, we could do--- this (assuming a suitable @getURL@ function):------ > withAsync (getURL url1) $ \a1 -> do--- > withAsync (getURL url2) $ \a2 -> do--- > page1 <- wait a1--- > page2 <- wait a2--- > ...------ where 'withAsync' 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 ensures property (1): No exception is swallowed.--- * If an exception bubbles up through a 'withAsync', then the 'Async'--- it spawned is 'cancel'ed. This ensures property (2): No thread is leaked.------ Often we do not care to work manually with 'Async' handles like--- @a1@ and @a2@. Instead, we want to express high-level objectives like--- performing two or more tasks concurrently, and waiting for one or all--- of them to finish.------ For example, the pattern of performing two IO actions concurrently and--- waiting for both 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 section __/High-level utilities/__ covers the most--- common high-level objectives, including:------ * Waiting for 2 results ('concurrently').--- * Waiting for many results ('mapConcurrently' / 'forConcurrently').--- * Waiting for the first of 2 results ('race').--- * Waiting for arbitrary nestings of "all of /N/" and "the first of /N/"--- results with the 'Concurrently' newtype and its 'Applicative' and--- 'Alternative' instances.------ Click here to scroll to that section:--- "Control.Concurrent.Async#high-level-utilities".------ == Low-level API------ Some use cases require parallelism that is not lexically scoped.------ For those, the low-level function 'async' can be used as a direct--- equivalent of 'forkIO':------ > -- Do NOT use this code in production, it has a flaw (explained below).--- > do--- > a1 <- async (getURL url1)--- > a2 <- async (getURL url2)--- > page1 <- wait a1--- > page2 <- wait a2--- > ...------ In contrast to 'withAsync', this code has a problem.------ It still fulfills property (1) in that an exception arising from--- @getUrl@ will be re-thrown by 'wait', but it does not fulfill--- property (2).--- Consider the case when the first 'wait' throws an exception; then the--- second 'wait' will not happen, and the second 'async' may be left--- running in the background, possibly indefinitely.------ 'withAsync' is like 'async', except that the 'Async' is--- automatically killed (using 'uninterruptibleCancel') if the--- enclosing IO operation returns before it has completed.--- Furthermore, 'withAsync' allows a tree of threads to be built, such--- that children are automatically killed if their parents die for any--- reason.------ If you need to use the low-level API, ensure that you gurantee--- property (2) by other means, such as 'link'ing asyncs that need--- to die together, and protecting against asynchronous exceptions--- using 'Control.Exception.bracket', 'Control.Exception.mask',--- or other functions from "Control.Exception".------ == Miscellaneous------ The 'Functor' instance can be used to change the result of an--- 'Async'. For example:------ > ghci> withAsync (return 3) (\a -> wait (fmap (+1) a))--- > 4------ === Resource exhaustion------ As with all concurrent programming, keep in mind that while--- Haskell's cooperative ("green") multithreading carries low overhead,--- spawning too many of them at the same time may lead to resource exhaustion--- (of memory, file descriptors, or other limited resources), given that the--- actions running in the threads consume these resources.---------------------------------------------------------------------------------module Control.Concurrent.Async (-- -- * Asynchronous actions- Async,-- -- * High-level API-- -- ** Spawning with automatic 'cancel'ation- withAsync, withAsyncBound, withAsyncOn, withAsyncWithUnmask,- withAsyncOnWithUnmask,-- -- ** Querying 'Async's- wait, poll, waitCatch, asyncThreadId,- cancel, uninterruptibleCancel, cancelWith, AsyncCancelled(..),-- -- ** #high-level-utilities# High-level utilities- race, race_,- concurrently, concurrently_,- mapConcurrently, forConcurrently,- mapConcurrently_, forConcurrently_,- replicateConcurrently, replicateConcurrently_,- Concurrently(..),- compareAsyncs,-- -- ** Specialised operations-- -- *** 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,-- -- * Low-level API-- -- ** Spawning (low-level API)- async, asyncBound, asyncOn, asyncWithUnmask, asyncOnWithUnmask,-- -- ** Linking- link, linkOnly, link2, link2Only, ExceptionInLinkedThread(..),-- ) where--import Control.Concurrent.STM.TMVar-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 Asyncs that may have different types by their 'ThreadId'.-compareAsyncs :: Async a -> Async b -> Ordering-compareAsyncs (Async t1 _) (Async t2 _) = compare t1 t2---- | Spawn an asynchronous action in a separate thread.------ Like for 'forkIO', the action may be left running unintentinally--- (see module-level documentation for details).------ __Use 'withAsync' style functions wherever you can instead!__-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 a) `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 = tryAgain . atomically . waitSTM- where- -- See: https://github.com/simonmar/async/issues/14- tryAgain f = f `catch` \BlockedIndefinitelyOnSTM -> f---- | 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 = tryAgain $ atomically (waitBothSTM left right)- where- -- See: https://github.com/simonmar/async/issues/14- tryAgain f = f `catch` \BlockedIndefinitelyOnSTM -> f---- | 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- showsPrec p (ExceptionInLinkedThread (Async t _) e) =- showParen (p >= 11) $- showString "ExceptionInLinkedThread " .- showsPrec 11 t .- showString " " .- showsPrec 11 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, wrapped in 'ExceptionInLinkedThread'.------ 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)---- | 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'.------ The supplied predicate determines which exceptions in the target--- thread should be propagated to the source thread.----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)---- | 'concurrently', but ignore the result values------ @since 2.1.1-concurrently_ :: IO a -> IO b -> IO ()--#define USE_ASYNC_VERSIONS 0--#if USE_ASYNC_VERSIONS--race left right =- withAsync left $ \a ->- withAsync right $ \b ->- waitEither a b--race_ left right = void $ race left right--concurrently left right =- withAsync left $ \a ->- withAsync right $ \b ->- waitBoth a b--concurrently_ left right = void $ concurrently left right--#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--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---#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"]------ Take into account that @async@ will try to immediately spawn a thread--- for each element of the @Traversable@, so running this on large--- inputs without care may lead to resource exhaustion (of memory,--- file descriptors, or other limited resources).-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;--- a concurrent equivalent of '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;--- a concurrent equivalent of 'forM_'.-forConcurrently_ :: F.Foldable f => f a -> (a -> IO b) -> IO ()-forConcurrently_ = flip mapConcurrently_---- | 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 (IO action) = IO $ \ s ->- case (fork# action s) of (# s1, tid #) -> (# s1, ThreadId tid #)--{-# INLINE rawForkOn #-}-rawForkOn :: Int -> IO () -> IO ThreadId-rawForkOn (I# cpu) (IO action) = IO $ \ s ->- case (forkOn# cpu action s) of (# s1, tid #) -> (# s1, ThreadId tid #)
− hspec-core/vendor/Data/Algorithm/Diff.hs
@@ -1,115 +0,0 @@-{-# LANGUAGE DeriveFunctor #-}-{-# OPTIONS_GHC -fno-warn-incomplete-uni-patterns #-}--------------------------------------------------------------------------------- |--- Module : Data.Algorithm.Diff--- Copyright : (c) Sterling Clover 2008-2011, Kevin Charter 2011--- License : BSD 3 Clause--- Maintainer : s.clover@gmail.com--- Stability : experimental--- Portability : portable------ This is an implementation of the O(ND) diff algorithm as described in--- \"An O(ND) Difference Algorithm and Its Variations (1986)\"--- <http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.4.6927>. It is O(mn) in space.--- The algorithm is the same one used by standared Unix diff.--------------------------------------------------------------------------------module Data.Algorithm.Diff- ( Diff(..)- -- * Comparing lists for differences- , getDiff- , getDiffBy-- -- * Finding chunks of differences- , getGroupedDiff- , getGroupedDiffBy- ) where--import Prelude hiding (pi)--import Data.Array--data DI = F | S | B deriving (Show, Eq)---- | A value is either from the 'First' list, the 'Second' or from 'Both'.--- 'Both' contains both the left and right values, in case you are using a form--- of equality that doesn't check all data (for example, if you are using a--- newtype to only perform equality on side of a tuple).-data Diff a = First a | Second a | Both a a deriving (Show, Eq, Functor)--data DL = DL {poi :: !Int, poj :: !Int, path::[DI]} deriving (Show, Eq)--instance Ord DL- where x <= y = if poi x == poi y- then poj x > poj y- else poi x <= poi y--canDiag :: (a -> a -> Bool) -> [a] -> [a] -> Int -> Int -> Int -> Int -> Bool-canDiag eq as bs lena lenb = \ i j ->- if i < lena && j < lenb then (arAs ! i) `eq` (arBs ! j) else False- where arAs = listArray (0,lena - 1) as- arBs = listArray (0,lenb - 1) bs--dstep :: (Int -> Int -> Bool) -> [DL] -> [DL]-dstep cd dls = hd:pairMaxes rst- where (hd:rst) = nextDLs dls- nextDLs [] = []- nextDLs (dl:rest) = dl':dl'':nextDLs rest- where dl' = addsnake cd $ dl {poi=poi dl + 1, path=(F : pdl)}- dl'' = addsnake cd $ dl {poj=poj dl + 1, path=(S : pdl)}- pdl = path dl- pairMaxes [] = []- pairMaxes [x] = [x]- pairMaxes (x:y:rest) = max x y:pairMaxes rest--addsnake :: (Int -> Int -> Bool) -> DL -> DL-addsnake cd dl- | cd pi pj = addsnake cd $- dl {poi = pi + 1, poj = pj + 1, path=(B : path dl)}- | otherwise = dl- where pi = poi dl; pj = poj dl--lcs :: (a -> a -> Bool) -> [a] -> [a] -> [DI]-lcs eq as bs = path . head . dropWhile (\dl -> poi dl /= lena || poj dl /= lenb) .- concat . iterate (dstep cd) . (:[]) . addsnake cd $- DL {poi=0,poj=0,path=[]}- where cd = canDiag eq as bs lena lenb- lena = length as; lenb = length bs---- | Takes two lists and returns a list of differences between them. This is--- 'getDiffBy' with '==' used as predicate.-getDiff :: (Eq t) => [t] -> [t] -> [Diff t]-getDiff = getDiffBy (==)---- | Takes two lists and returns a list of differences between them, grouped--- into chunks. This is 'getGroupedDiffBy' with '==' used as predicate.-getGroupedDiff :: (Eq t) => [t] -> [t] -> [Diff [t]]-getGroupedDiff = getGroupedDiffBy (==)---- | A form of 'getDiff' with no 'Eq' constraint. Instead, an equality predicate--- is taken as the first argument.-getDiffBy :: (t -> t -> Bool) -> [t] -> [t] -> [Diff t]-getDiffBy eq a b = markup a b . reverse $ lcs eq a b- where markup (x:xs) ys (F:ds) = First x : markup xs ys ds- markup xs (y:ys) (S:ds) = Second y : markup xs ys ds- markup (x:xs) (y:ys) (B:ds) = Both x y : markup xs ys ds- markup _ _ _ = []--getGroupedDiffBy :: (t -> t -> Bool) -> [t] -> [t] -> [Diff [t]]-getGroupedDiffBy eq a b = go $ getDiffBy eq a b- where go (First x : xs) = let (fs, rest) = goFirsts xs in First (x:fs) : go rest- go (Second x : xs) = let (fs, rest) = goSeconds xs in Second (x:fs) : go rest- go (Both x y : xs) = let (fs, rest) = goBoth xs- (fxs, fys) = unzip fs- in Both (x:fxs) (y:fys) : go rest- go [] = []-- goFirsts (First x : xs) = let (fs, rest) = goFirsts xs in (x:fs, rest)- goFirsts xs = ([],xs)-- goSeconds (Second x : xs) = let (fs, rest) = goSeconds xs in (x:fs, rest)- goSeconds xs = ([],xs)-- goBoth (Both x y : xs) = let (fs, rest) = goBoth xs in ((x,y):fs, rest)- goBoth xs = ([],xs)
− hspec-core/vendor/stm-2.5.0.1/Control/Concurrent/STM/TMVar.hs
@@ -1,168 +0,0 @@-{-# LANGUAGE CPP, DeriveDataTypeable, MagicHash, UnboxedTuples #-}--#if __GLASGOW_HASKELL__ >= 701-{-# LANGUAGE Trustworthy #-}-#endif--{-# OPTIONS -fno-warn-implicit-prelude #-}---------------------------------------------------------------------------------- |--- Module : Control.Concurrent.STM.TMVar--- Copyright : (c) The University of Glasgow 2004--- License : BSD-style (see the file libraries/base/LICENSE)------ Maintainer : libraries@haskell.org--- Stability : experimental--- Portability : non-portable (requires STM)------ TMVar: Transactional MVars, for use in the STM monad--- (GHC only)-----------------------------------------------------------------------------------module Control.Concurrent.STM.TMVar (-#ifdef __GLASGOW_HASKELL__- -- * TMVars- TMVar,- newTMVar,- newEmptyTMVar,- newTMVarIO,- newEmptyTMVarIO,- takeTMVar,- putTMVar,- readTMVar,- tryReadTMVar,- swapTMVar,- tryTakeTMVar,- tryPutTMVar,- isEmptyTMVar,- mkWeakTMVar-#endif- ) where--#ifdef __GLASGOW_HASKELL__-import GHC.Base-import GHC.Conc-import GHC.Weak--import Data.Typeable (Typeable)--newtype TMVar a = TMVar (TVar (Maybe a)) deriving (Eq, Typeable)-{- ^-A 'TMVar' is a synchronising variable, used-for communication between concurrent threads. It can be thought of-as a box, which may be empty or full.--}---- |Create a 'TMVar' which contains the supplied value.-newTMVar :: a -> STM (TMVar a)-newTMVar a = do- t <- newTVar (Just a)- return (TMVar t)---- |@IO@ version of 'newTMVar'. This is useful for creating top-level--- 'TMVar's using 'System.IO.Unsafe.unsafePerformIO', because using--- 'atomically' inside 'System.IO.Unsafe.unsafePerformIO' isn't--- possible.-newTMVarIO :: a -> IO (TMVar a)-newTMVarIO a = do- t <- newTVarIO (Just a)- return (TMVar t)---- |Create a 'TMVar' which is initially empty.-newEmptyTMVar :: STM (TMVar a)-newEmptyTMVar = do- t <- newTVar Nothing- return (TMVar t)---- |@IO@ version of 'newEmptyTMVar'. This is useful for creating top-level--- 'TMVar's using 'System.IO.Unsafe.unsafePerformIO', because using--- 'atomically' inside 'System.IO.Unsafe.unsafePerformIO' isn't--- possible.-newEmptyTMVarIO :: IO (TMVar a)-newEmptyTMVarIO = do- t <- newTVarIO Nothing- return (TMVar t)---- |Return the contents of the 'TMVar'. If the 'TMVar' is currently--- empty, the transaction will 'retry'. After a 'takeTMVar',--- the 'TMVar' is left empty.-takeTMVar :: TMVar a -> STM a-takeTMVar (TMVar t) = do- m <- readTVar t- case m of- Nothing -> retry- Just a -> do writeTVar t Nothing; return a---- | A version of 'takeTMVar' that does not 'retry'. The 'tryTakeTMVar'--- function returns 'Nothing' if the 'TMVar' was empty, or @'Just' a@ if--- the 'TMVar' was full with contents @a@. After 'tryTakeTMVar', the--- 'TMVar' is left empty.-tryTakeTMVar :: TMVar a -> STM (Maybe a)-tryTakeTMVar (TMVar t) = do- m <- readTVar t- case m of- Nothing -> return Nothing- Just a -> do writeTVar t Nothing; return (Just a)---- |Put a value into a 'TMVar'. If the 'TMVar' is currently full,--- 'putTMVar' will 'retry'.-putTMVar :: TMVar a -> a -> STM ()-putTMVar (TMVar t) a = do- m <- readTVar t- case m of- Nothing -> do writeTVar t (Just a); return ()- Just _ -> retry---- | A version of 'putTMVar' that does not 'retry'. The 'tryPutTMVar'--- function attempts to put the value @a@ into the 'TMVar', returning--- 'True' if it was successful, or 'False' otherwise.-tryPutTMVar :: TMVar a -> a -> STM Bool-tryPutTMVar (TMVar t) a = do- m <- readTVar t- case m of- Nothing -> do writeTVar t (Just a); return True- Just _ -> return False---- | This is a combination of 'takeTMVar' and 'putTMVar'; ie. it--- takes the value from the 'TMVar', puts it back, and also returns--- it.-readTMVar :: TMVar a -> STM a-readTMVar (TMVar t) = do- m <- readTVar t- case m of- Nothing -> retry- Just a -> return a---- | A version of 'readTMVar' which does not retry. Instead it--- returns @Nothing@ if no value is available.------ @since 2.3-tryReadTMVar :: TMVar a -> STM (Maybe a)-tryReadTMVar (TMVar t) = readTVar t---- |Swap the contents of a 'TMVar' for a new value.-swapTMVar :: TMVar a -> a -> STM a-swapTMVar (TMVar t) new = do- m <- readTVar t- case m of- Nothing -> retry- Just old -> do writeTVar t (Just new); return old---- |Check whether a given 'TMVar' is empty.-isEmptyTMVar :: TMVar a -> STM Bool-isEmptyTMVar (TMVar t) = do- m <- readTVar t- case m of- Nothing -> return True- Just _ -> return False---- | Make a 'Weak' pointer to a 'TMVar', using the second argument as--- a finalizer to run when the 'TMVar' is garbage-collected.------ @since 2.4.4-mkWeakTMVar :: TMVar a -> IO () -> IO (Weak (TMVar a))-mkWeakTMVar tmv@(TMVar (TVar t#)) (IO finalizer) = IO $ \s ->- case mkWeak# t# tmv finalizer s of (# s1, w #) -> (# s1, Weak w #)-#endif
hspec-discover/src/Test/Hspec/Discover/Config.hs view
@@ -1,6 +1,6 @@ -- | -- /NOTE:/ This module is not meant for public consumption. For user--- documentation look at http://hspec.github.io/hspec-discover.html.+-- documentation look at https://hspec.github.io/hspec-discover.html. module Test.Hspec.Discover.Config ( Config (..) , defaultConfig
hspec-discover/src/Test/Hspec/Discover/Run.hs view
@@ -3,7 +3,7 @@ -- | A preprocessor that finds and combines specs. -- -- /NOTE:/ This module is not meant for public consumption. For user--- documentation look at http://hspec.github.io/hspec-discover.html.+-- documentation look at https://hspec.github.io/hspec-discover.html. module Test.Hspec.Discover.Run ( run @@ -149,7 +149,7 @@ mkModule :: [String] -> String mkModule = intercalate "." . reverse --- See `Cabal.Distribution.ModuleName` (http://git.io/bj34)+-- See `Cabal.Distribution.ModuleName` (https://git.io/bj34) isValidModuleName :: String -> Bool isValidModuleName [] = False isValidModuleName (c:cs) = isUpper c && all isValidModuleChar cs
hspec-discover/src/Test/Hspec/Discover/Sort.hs view
@@ -1,6 +1,6 @@ -- | -- /NOTE:/ This module is not meant for public consumption. For user--- documentation look at http://hspec.github.io/hspec-discover.html.+-- documentation look at https://hspec.github.io/hspec-discover.html. module Test.Hspec.Discover.Sort ( sortNaturallyBy , NaturalSortKey
hspec-meta.cabal view
@@ -1,37 +1,38 @@ cabal-version: 1.12 --- This file has been generated from package.yaml by hpack version 0.35.0.+-- This file has been generated from package.yaml by hpack version 0.35.2. -- -- see: https://github.com/sol/hpack name: hspec-meta-version: 2.10.5+version: 2.11.2 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. category: Testing stability: experimental-homepage: http://hspec.github.io/+homepage: https://hspec.github.io/ bug-reports: https://github.com/hspec/hspec/issues maintainer: Simon Hengel <sol@typeful.net>-copyright: (c) 2011-2022 Simon Hengel,+copyright: (c) 2011-2023 Simon Hengel, (c) 2011-2012 Trystan Spangler, (c) 2011 Greg Weber license: MIT license-file: LICENSE build-type: Simple-extra-source-files:- version.yaml source-repository head type: git location: https://github.com/hspec/hspec+ subdir: hspec-meta library exposed-modules: Test.Hspec.Meta Test.Hspec.Discover other-modules:+ Control.Concurrent.Async+ Data.Algorithm.Diff Test.Hspec Test.Hspec.Formatters Test.Hspec.QuickCheck@@ -56,10 +57,11 @@ Test.Hspec.Core.Formatters.Internal Test.Hspec.Core.Formatters.Pretty Test.Hspec.Core.Formatters.Pretty.Parser- Test.Hspec.Core.Formatters.Pretty.Parser.Types+ Test.Hspec.Core.Formatters.Pretty.Parser.Parser Test.Hspec.Core.Formatters.Pretty.Unicode Test.Hspec.Core.Formatters.V1 Test.Hspec.Core.Formatters.V1.Free+ Test.Hspec.Core.Formatters.V1.Internal Test.Hspec.Core.Formatters.V1.Monad Test.Hspec.Core.Formatters.V2 Test.Hspec.Core.Hooks@@ -67,6 +69,7 @@ Test.Hspec.Core.QuickCheckUtil Test.Hspec.Core.Runner Test.Hspec.Core.Runner.Eval+ Test.Hspec.Core.Runner.JobQueue Test.Hspec.Core.Runner.PrintSlowSpecItems Test.Hspec.Core.Runner.Result Test.Hspec.Core.Shuffle@@ -75,44 +78,32 @@ Test.Hspec.Core.Timer Test.Hspec.Core.Tree Test.Hspec.Core.Util- Control.Concurrent.Async- Data.Algorithm.Diff- Test.HUnit- Test.HUnit.Base- Test.HUnit.Lang- Test.HUnit.Terminal- Test.HUnit.Text- Test.Hspec.Expectations- Test.Hspec.Expectations.Contrib- Test.Hspec.Expectations.Matcher Paths_hspec_meta hs-source-dirs: src+ vendor+ hspec/src hspec-core/src- hspec-core/vendor- vendor/HUnit-1.6.2.0/src/- vendor/hspec-expectations-0.8.2/src/ ghc-options: -Wall -fno-warn-incomplete-uni-patterns build-depends:- QuickCheck >=2.12- , ansi-terminal+ HUnit ==1.6.*+ , QuickCheck >=2.13.1+ , ansi-terminal >=0.6.2 , array- , base ==4.*- , call-stack- , clock+ , base >=4.8.2.0 && <5+ , call-stack >=0.2.0 , deepseq , directory , filepath- , quickcheck-io+ , haskell-lexer+ , hspec-expectations ==0.8.3.*+ , process+ , quickcheck-io >=0.2.0 , random- , setenv+ , tf-random , time , transformers >=0.2.2.0 default-language: Haskell2010- if impl(ghc >= 8.2.1)- build-depends:- ghc- , ghc-boot-th if impl(ghc >= 8.4.1) build-depends: stm >=2.2@@ -120,7 +111,7 @@ other-modules: Control.Concurrent.STM.TMVar hs-source-dirs:- hspec-core/vendor/stm-2.5.0.1/+ vendor/stm-2.5.0.1/ executable hspec-meta-discover main-is: hspec-discover.hs@@ -134,22 +125,29 @@ hspec-discover/driver ghc-options: -Wall -fno-warn-incomplete-uni-patterns build-depends:- QuickCheck >=2.12- , ansi-terminal+ HUnit ==1.6.*+ , QuickCheck >=2.13.1+ , ansi-terminal >=0.6.2 , array- , base ==4.*- , call-stack- , clock+ , base >=4.8.2.0 && <5+ , call-stack >=0.2.0 , deepseq , directory , filepath- , quickcheck-io+ , haskell-lexer+ , hspec-expectations ==0.8.3.*+ , process+ , quickcheck-io >=0.2.0 , random- , setenv+ , tf-random , time , transformers >=0.2.2.0 default-language: Haskell2010- if impl(ghc >= 8.2.1)+ if impl(ghc >= 8.4.1) build-depends:- ghc- , ghc-boot-th+ stm >=2.2+ else+ other-modules:+ Control.Concurrent.STM.TMVar+ hs-source-dirs:+ vendor/stm-2.5.0.1/
+ hspec/src/Test/Hspec.hs view
@@ -0,0 +1,96 @@+-- |+-- Stability: stable+--+-- Hspec is a testing framework for Haskell.+--+-- This is the library reference for Hspec.+-- The <https://hspec.github.io/ User's Manual> contains more in-depth+-- documentation.+module Test.Hspec (+-- * Types+ Spec+, SpecWith+, Arg+, Example++-- * Setting expectations+, module Test.Hspec.Expectations++-- * Defining a spec+, it+, specify+, describe+, context+, example+, parallel+, sequential+, runIO++-- * Pending spec items+-- |+-- During a test run a /pending/ spec item is:+--+-- 1. not executed+--+-- 1. reported as \"pending\"+, pending+, pendingWith+, xit+, xspecify+, xdescribe+, xcontext++-- * Focused spec items+-- |+-- During a test run, when a spec contains /focused/ spec items, all other spec+-- items are ignored.+, focus+, fit+, fspecify+, fdescribe+, fcontext++-- * Hooks+, ActionWith+, before+, before_+, beforeWith+, beforeAll+, beforeAll_+, beforeAllWith+, after+, after_+, afterAll+, afterAll_+, around+, around_+, aroundWith+, aroundAll+, aroundAll_+, aroundAllWith+, mapSubject+, ignoreSubject++-- * Running a spec+, hspec+) where++import Test.Hspec.Core.Spec+import Test.Hspec.Core.Hooks+import Test.Hspec.Runner+import Test.Hspec.Expectations++-- | @example@ is a type restricted version of `id`. It can be used to get better+-- error messages on type mismatches.+--+-- Compare e.g.+--+-- > it "exposes some behavior" $ example $ do+-- > putStrLn+--+-- with+--+-- > it "exposes some behavior" $ do+-- > putStrLn+example :: Expectation -> Expectation+example = id
+ hspec/src/Test/Hspec/Discover.hs view
@@ -0,0 +1,34 @@+{-# OPTIONS_GHC -fno-warn-deprecations #-}+{-# LANGUAGE FlexibleInstances #-}+module Test.Hspec.Discover {-# WARNING+ "This module is used by @hspec-discover@. It is not part of the public API and may change at any time."+ #-} (+ Spec+, hspec+, IsFormatter (..)+, hspecWithFormatter+, postProcessSpec+, describe+, module Prelude+) where++import Test.Hspec.Core.Spec+import Test.Hspec.Core.Runner+import Test.Hspec.Core.Formatters.V1++class IsFormatter a where+ toFormatter :: a -> IO Formatter++instance IsFormatter (IO Formatter) where+ toFormatter = id++instance IsFormatter Formatter where+ toFormatter = return++hspecWithFormatter :: IsFormatter a => a -> Spec -> IO ()+hspecWithFormatter formatter spec = do+ f <- toFormatter formatter+ hspecWith defaultConfig {configFormatter = Just f} spec++postProcessSpec :: FilePath -> Spec -> Spec+postProcessSpec _ = id
+ hspec/src/Test/Hspec/Formatters.hs view
@@ -0,0 +1,8 @@+{-# OPTIONS_GHC -fno-warn-deprecations #-}+-- |+-- Stability: deprecated+module Test.Hspec.Formatters+{-# DEPRECATED "Use [Test.Hspec.Api.Formatters.V1](https://hackage.haskell.org/package/hspec-api/docs/Test-Hspec-Api-Formatters-V1.html) instead." #-}+(module Test.Hspec.Core.Formatters.V1)+where+import Test.Hspec.Core.Formatters.V1
+ hspec/src/Test/Hspec/QuickCheck.hs view
@@ -0,0 +1,60 @@+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE ConstraintKinds #-}+module Test.Hspec.QuickCheck (+-- * Params+ modifyArgs+, modifyMaxSuccess+, modifyMaxDiscardRatio+, modifyMaxSize+, modifyMaxShrinks++-- * Shortcuts+, prop+, xprop+, fprop+) where++import Test.Hspec+import Test.QuickCheck+import Test.Hspec.Core.QuickCheck++-- |+-- > prop ".." $+-- > ..+--+-- is a shortcut for+--+-- @+-- `it` ".." $ `property` $+-- ..+-- @+prop :: (HasCallStack, Testable prop) => String -> prop -> Spec+prop s = it s . property+++-- |+-- > xprop ".." $+-- > ..+--+-- is a shortcut for+--+-- @+-- `xit` ".." $ `property` $+-- ..+-- @+xprop :: (HasCallStack, Testable prop) => String -> prop -> Spec+xprop s = xit s . property+++-- |+-- > fprop ".." $+-- > ..+--+-- is a shortcut for+--+-- @+-- `fit` ".." $ `property` $+-- ..+-- @+fprop :: (HasCallStack, Testable prop) => String -> prop -> Spec+fprop s = fit s . property
+ hspec/src/Test/Hspec/Runner.hs view
@@ -0,0 +1,2 @@+module Test.Hspec.Runner (module Test.Hspec.Core.Runner) where+import Test.Hspec.Core.Runner
− src/Test/Hspec.hs
@@ -1,95 +0,0 @@--- |--- Stability: stable------ Hspec is a testing framework for Haskell.------ This is the library reference for Hspec.--- The <http://hspec.github.io/ User's Manual> contains more in-depth--- documentation.-module Test.Hspec (--- * Types- Spec-, SpecWith-, Arg-, Example---- * Setting expectations-, module Test.Hspec.Expectations---- * Defining a spec-, it-, specify-, describe-, context-, example-, parallel-, runIO---- * Pending spec items--- |--- During a test run a /pending/ spec item is:------ 1. not executed------ 1. reported as \"pending\"-, pending-, pendingWith-, xit-, xspecify-, xdescribe-, xcontext---- * Focused spec items--- |--- During a test run, when a spec contains /focused/ spec items, all other spec--- items are ignored.-, focus-, fit-, fspecify-, fdescribe-, fcontext---- * Hooks-, ActionWith-, before-, before_-, beforeWith-, beforeAll-, beforeAll_-, beforeAllWith-, after-, after_-, afterAll-, afterAll_-, around-, around_-, aroundWith-, aroundAll-, aroundAll_-, aroundAllWith-, mapSubject-, ignoreSubject---- * Running a spec-, hspec-) where--import Test.Hspec.Core.Spec-import Test.Hspec.Core.Hooks-import Test.Hspec.Runner-import Test.Hspec.Expectations---- | @example@ is a type restricted version of `id`. It can be used to get better--- error messages on type mismatches.------ Compare e.g.------ > it "exposes some behavior" $ example $ do--- > putStrLn------ with------ > it "exposes some behavior" $ do--- > putStrLn-example :: Expectation -> Expectation-example = id
− src/Test/Hspec/Discover.hs
@@ -1,33 +0,0 @@-{-# LANGUAGE FlexibleInstances #-}-module Test.Hspec.Discover {-# WARNING- "This module is used by @hspec-discover@. It is not part of the public API and may change at any time."- #-} (- Spec-, hspec-, IsFormatter (..)-, hspecWithFormatter-, postProcessSpec-, describe-, module Prelude-) where--import Test.Hspec.Core.Spec-import Test.Hspec.Core.Runner-import Test.Hspec.Core.Formatters.V1--class IsFormatter a where- toFormatter :: a -> IO Formatter--instance IsFormatter (IO Formatter) where- toFormatter = id--instance IsFormatter Formatter where- toFormatter = return--hspecWithFormatter :: IsFormatter a => a -> Spec -> IO ()-hspecWithFormatter formatter spec = do- f <- toFormatter formatter- hspecWith defaultConfig {configFormatter = Just f} spec--postProcessSpec :: FilePath -> Spec -> Spec-postProcessSpec _ = id
− src/Test/Hspec/Formatters.hs
@@ -1,7 +0,0 @@--- |--- Deprecated\: Use "Test.Hspec.Core.Formatters.V1" instead.-module Test.Hspec.Formatters--- {-# DEPRECATED "Use \"Test.Hspec.Core.Formatters.V1\" instead." #-}-(module Test.Hspec.Core.Formatters.V1)-where-import Test.Hspec.Core.Formatters.V1
− src/Test/Hspec/QuickCheck.hs
@@ -1,28 +0,0 @@-{-# LANGUAGE FlexibleContexts #-}-{-# LANGUAGE ConstraintKinds #-}-module Test.Hspec.QuickCheck (--- * Params- modifyArgs-, modifyMaxSuccess-, modifyMaxDiscardRatio-, modifyMaxSize-, modifyMaxShrinks---- * Shortcuts-, prop-) where--import Test.Hspec-import Test.QuickCheck-import Test.Hspec.Core.QuickCheck---- |--- > prop ".." $--- > ..------ is a shortcut for------ > it ".." $ property $--- > ..-prop :: (HasCallStack, Testable prop) => String -> prop -> Spec-prop s = it s . property
− src/Test/Hspec/Runner.hs
@@ -1,2 +0,0 @@-module Test.Hspec.Runner (module Test.Hspec.Core.Runner) where-import Test.Hspec.Core.Runner
+ vendor/Control/Concurrent/Async.hs view
@@ -0,0 +1,963 @@+{-# LANGUAGE CPP, MagicHash, UnboxedTuples, RankNTypes,+ ExistentialQuantification #-}+#if __GLASGOW_HASKELL__ >= 701+{-# LANGUAGE Trustworthy #-}+#endif+#if __GLASGOW_HASKELL__ < 710+{-# LANGUAGE DeriveDataTypeable #-}+#endif+{-# OPTIONS -Wall -fno-warn-implicit-prelude -fno-warn-unused-imports #-}++-----------------------------------------------------------------------------+-- |+-- 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 'forkIO' threads and @MVar@ directly.+--+-- == High-level API+--+-- @async@'s high-level API spawns /lexically scoped/ threads,+-- ensuring the following key poperties that make it safer to use+-- than using plain 'forkIO':+--+-- 1. No exception is swallowed (waiting for results propagates exceptions).+-- 2. No thread is leaked (left running unintentionally).+--+-- (This is done using the 'Control.Exception.bracket' pattern to work in presence+-- of synchornous and asynchronous exceptions.)+--+-- __Most practical/production code should only use the high-level API__.+--+-- 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' is a wrapper around a low-level 'forkIO' thread.+--+-- The fundamental function to spawn threads with the high-level API is+-- 'withAsync'.+--+-- For example, to fetch two web pages at the same time, we could do+-- this (assuming a suitable @getURL@ function):+--+-- > withAsync (getURL url1) $ \a1 -> do+-- > withAsync (getURL url2) $ \a2 -> do+-- > page1 <- wait a1+-- > page2 <- wait a2+-- > ...+--+-- where 'withAsync' 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 ensures property (1): No exception is swallowed.+-- * If an exception bubbles up through a 'withAsync', then the 'Async'+-- it spawned is 'cancel'ed. This ensures property (2): No thread is leaked.+--+-- Often we do not care to work manually with 'Async' handles like+-- @a1@ and @a2@. Instead, we want to express high-level objectives like+-- performing two or more tasks concurrently, and waiting for one or all+-- of them to finish.+--+-- For example, the pattern of performing two IO actions concurrently and+-- waiting for both 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 section __/High-level utilities/__ covers the most+-- common high-level objectives, including:+--+-- * Waiting for 2 results ('concurrently').+-- * Waiting for many results ('mapConcurrently' / 'forConcurrently').+-- * Waiting for the first of 2 results ('race').+-- * Waiting for arbitrary nestings of "all of /N/" and "the first of /N/"+-- results with the 'Concurrently' newtype and its 'Applicative' and+-- 'Alternative' instances.+--+-- Click here to scroll to that section:+-- "Control.Concurrent.Async#high-level-utilities".+--+-- == Low-level API+--+-- Some use cases require parallelism that is not lexically scoped.+--+-- For those, the low-level function 'async' can be used as a direct+-- equivalent of 'forkIO':+--+-- > -- Do NOT use this code in production, it has a flaw (explained below).+-- > do+-- > a1 <- async (getURL url1)+-- > a2 <- async (getURL url2)+-- > page1 <- wait a1+-- > page2 <- wait a2+-- > ...+--+-- In contrast to 'withAsync', this code has a problem.+--+-- It still fulfills property (1) in that an exception arising from+-- @getUrl@ will be re-thrown by 'wait', but it does not fulfill+-- property (2).+-- Consider the case when the first 'wait' throws an exception; then the+-- second 'wait' will not happen, and the second 'async' may be left+-- running in the background, possibly indefinitely.+--+-- 'withAsync' is like 'async', except that the 'Async' is+-- automatically killed (using 'uninterruptibleCancel') if the+-- enclosing IO operation returns before it has completed.+-- Furthermore, 'withAsync' allows a tree of threads to be built, such+-- that children are automatically killed if their parents die for any+-- reason.+--+-- If you need to use the low-level API, ensure that you gurantee+-- property (2) by other means, such as 'link'ing asyncs that need+-- to die together, and protecting against asynchronous exceptions+-- using 'Control.Exception.bracket', 'Control.Exception.mask',+-- or other functions from "Control.Exception".+--+-- == Miscellaneous+--+-- The 'Functor' instance can be used to change the result of an+-- 'Async'. For example:+--+-- > ghci> withAsync (return 3) (\a -> wait (fmap (+1) a))+-- > 4+--+-- === Resource exhaustion+--+-- As with all concurrent programming, keep in mind that while+-- Haskell's cooperative ("green") multithreading carries low overhead,+-- spawning too many of them at the same time may lead to resource exhaustion+-- (of memory, file descriptors, or other limited resources), given that the+-- actions running in the threads consume these resources.++-----------------------------------------------------------------------------++module Control.Concurrent.Async (++ -- * Asynchronous actions+ Async,++ -- * High-level API++ -- ** Spawning with automatic 'cancel'ation+ withAsync, withAsyncBound, withAsyncOn, withAsyncWithUnmask,+ withAsyncOnWithUnmask,++ -- ** Querying 'Async's+ wait, poll, waitCatch, asyncThreadId,+ cancel, uninterruptibleCancel, cancelWith, AsyncCancelled(..),++ -- ** #high-level-utilities# High-level utilities+ race, race_,+ concurrently, concurrently_,+ mapConcurrently, forConcurrently,+ mapConcurrently_, forConcurrently_,+ replicateConcurrently, replicateConcurrently_,+ Concurrently(..),+ compareAsyncs,++ -- ** Specialised operations++ -- *** 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,++ -- * Low-level API++ -- ** Spawning (low-level API)+ async, asyncBound, asyncOn, asyncWithUnmask, asyncOnWithUnmask,++ -- ** Linking+ link, linkOnly, link2, link2Only, ExceptionInLinkedThread(..),++ ) where++import Control.Concurrent.STM.TMVar+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 Asyncs that may have different types by their 'ThreadId'.+compareAsyncs :: Async a -> Async b -> Ordering+compareAsyncs (Async t1 _) (Async t2 _) = compare t1 t2++-- | Spawn an asynchronous action in a separate thread.+--+-- Like for 'forkIO', the action may be left running unintentinally+-- (see module-level documentation for details).+--+-- __Use 'withAsync' style functions wherever you can instead!__+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 a) `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 = tryAgain . atomically . waitSTM+ where+ -- See: https://github.com/simonmar/async/issues/14+ tryAgain f = f `catch` \BlockedIndefinitelyOnSTM -> f++-- | 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 = tryAgain $ atomically (waitBothSTM left right)+ where+ -- See: https://github.com/simonmar/async/issues/14+ tryAgain f = f `catch` \BlockedIndefinitelyOnSTM -> f++-- | 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+ showsPrec p (ExceptionInLinkedThread (Async t _) e) =+ showParen (p >= 11) $+ showString "ExceptionInLinkedThread " .+ showsPrec 11 t .+ showString " " .+ showsPrec 11 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, wrapped in 'ExceptionInLinkedThread'.+--+-- 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)++-- | 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'.+--+-- The supplied predicate determines which exceptions in the target+-- thread should be propagated to the source thread.+--+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)++-- | 'concurrently', but ignore the result values+--+-- @since 2.1.1+concurrently_ :: IO a -> IO b -> IO ()++#define USE_ASYNC_VERSIONS 0++#if USE_ASYNC_VERSIONS++race left right =+ withAsync left $ \a ->+ withAsync right $ \b ->+ waitEither a b++race_ left right = void $ race left right++concurrently left right =+ withAsync left $ \a ->+ withAsync right $ \b ->+ waitBoth a b++concurrently_ left right = void $ concurrently left right++#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++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+++#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"]+--+-- Take into account that @async@ will try to immediately spawn a thread+-- for each element of the @Traversable@, so running this on large+-- inputs without care may lead to resource exhaustion (of memory,+-- file descriptors, or other limited resources).+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;+-- a concurrent equivalent of '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;+-- a concurrent equivalent of 'forM_'.+forConcurrently_ :: F.Foldable f => f a -> (a -> IO b) -> IO ()+forConcurrently_ = flip mapConcurrently_++-- | 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 (IO action) = IO $ \ s ->+ case (fork# action s) of (# s1, tid #) -> (# s1, ThreadId tid #)++{-# INLINE rawForkOn #-}+rawForkOn :: Int -> IO () -> IO ThreadId+rawForkOn (I# cpu) (IO action) = IO $ \ s ->+ case (forkOn# cpu action s) of (# s1, tid #) -> (# s1, ThreadId tid #)
+ vendor/Data/Algorithm/Diff.hs view
@@ -0,0 +1,115 @@+{-# LANGUAGE DeriveFunctor #-}+{-# OPTIONS_GHC -fno-warn-incomplete-uni-patterns #-}+-----------------------------------------------------------------------------+-- |+-- Module : Data.Algorithm.Diff+-- Copyright : (c) Sterling Clover 2008-2011, Kevin Charter 2011+-- License : BSD 3 Clause+-- Maintainer : s.clover@gmail.com+-- Stability : experimental+-- Portability : portable+--+-- This is an implementation of the O(ND) diff algorithm as described in+-- \"An O(ND) Difference Algorithm and Its Variations (1986)\"+-- <http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.4.6927>. It is O(mn) in space.+-- The algorithm is the same one used by standared Unix diff.+-----------------------------------------------------------------------------++module Data.Algorithm.Diff+ ( Diff(..)+ -- * Comparing lists for differences+ , getDiff+ , getDiffBy++ -- * Finding chunks of differences+ , getGroupedDiff+ , getGroupedDiffBy+ ) where++import Prelude hiding (pi)++import Data.Array++data DI = F | S | B deriving (Show, Eq)++-- | A value is either from the 'First' list, the 'Second' or from 'Both'.+-- 'Both' contains both the left and right values, in case you are using a form+-- of equality that doesn't check all data (for example, if you are using a+-- newtype to only perform equality on side of a tuple).+data Diff a = First a | Second a | Both a a deriving (Show, Eq, Functor)++data DL = DL {poi :: !Int, poj :: !Int, path::[DI]} deriving (Show, Eq)++instance Ord DL+ where x <= y = if poi x == poi y+ then poj x > poj y+ else poi x <= poi y++canDiag :: (a -> a -> Bool) -> [a] -> [a] -> Int -> Int -> Int -> Int -> Bool+canDiag eq as bs lena lenb = \ i j ->+ if i < lena && j < lenb then (arAs ! i) `eq` (arBs ! j) else False+ where arAs = listArray (0,lena - 1) as+ arBs = listArray (0,lenb - 1) bs++dstep :: (Int -> Int -> Bool) -> [DL] -> [DL]+dstep cd dls = hd:pairMaxes rst+ where (hd:rst) = nextDLs dls+ nextDLs [] = []+ nextDLs (dl:rest) = dl':dl'':nextDLs rest+ where dl' = addsnake cd $ dl {poi=poi dl + 1, path=(F : pdl)}+ dl'' = addsnake cd $ dl {poj=poj dl + 1, path=(S : pdl)}+ pdl = path dl+ pairMaxes [] = []+ pairMaxes [x] = [x]+ pairMaxes (x:y:rest) = max x y:pairMaxes rest++addsnake :: (Int -> Int -> Bool) -> DL -> DL+addsnake cd dl+ | cd pi pj = addsnake cd $+ dl {poi = pi + 1, poj = pj + 1, path=(B : path dl)}+ | otherwise = dl+ where pi = poi dl; pj = poj dl++lcs :: (a -> a -> Bool) -> [a] -> [a] -> [DI]+lcs eq as bs = path . head . dropWhile (\dl -> poi dl /= lena || poj dl /= lenb) .+ concat . iterate (dstep cd) . (:[]) . addsnake cd $+ DL {poi=0,poj=0,path=[]}+ where cd = canDiag eq as bs lena lenb+ lena = length as; lenb = length bs++-- | Takes two lists and returns a list of differences between them. This is+-- 'getDiffBy' with '==' used as predicate.+getDiff :: (Eq t) => [t] -> [t] -> [Diff t]+getDiff = getDiffBy (==)++-- | Takes two lists and returns a list of differences between them, grouped+-- into chunks. This is 'getGroupedDiffBy' with '==' used as predicate.+getGroupedDiff :: (Eq t) => [t] -> [t] -> [Diff [t]]+getGroupedDiff = getGroupedDiffBy (==)++-- | A form of 'getDiff' with no 'Eq' constraint. Instead, an equality predicate+-- is taken as the first argument.+getDiffBy :: (t -> t -> Bool) -> [t] -> [t] -> [Diff t]+getDiffBy eq a b = markup a b . reverse $ lcs eq a b+ where markup (x:xs) ys (F:ds) = First x : markup xs ys ds+ markup xs (y:ys) (S:ds) = Second y : markup xs ys ds+ markup (x:xs) (y:ys) (B:ds) = Both x y : markup xs ys ds+ markup _ _ _ = []++getGroupedDiffBy :: (t -> t -> Bool) -> [t] -> [t] -> [Diff [t]]+getGroupedDiffBy eq a b = go $ getDiffBy eq a b+ where go (First x : xs) = let (fs, rest) = goFirsts xs in First (x:fs) : go rest+ go (Second x : xs) = let (fs, rest) = goSeconds xs in Second (x:fs) : go rest+ go (Both x y : xs) = let (fs, rest) = goBoth xs+ (fxs, fys) = unzip fs+ in Both (x:fxs) (y:fys) : go rest+ go [] = []++ goFirsts (First x : xs) = let (fs, rest) = goFirsts xs in (x:fs, rest)+ goFirsts xs = ([],xs)++ goSeconds (Second x : xs) = let (fs, rest) = goSeconds xs in (x:fs, rest)+ goSeconds xs = ([],xs)++ goBoth (Both x y : xs) = let (fs, rest) = goBoth xs in ((x,y):fs, rest)+ goBoth xs = ([],xs)
− vendor/HUnit-1.6.2.0/src/Test/HUnit.hs
@@ -1,80 +0,0 @@--- | HUnit is a unit testing framework for Haskell, inspired by the JUnit tool--- for Java. This guide describes how to use HUnit, assuming you are familiar--- with Haskell, though not necessarily with JUnit.------ In the Haskell module where your tests will reside, import module--- @Test.HUnit@:------ @--- import Test.HUnit--- @------ Define test cases as appropriate:------ @--- test1 = TestCase (assertEqual "for (foo 3)," (1,2) (foo 3))--- test2 = TestCase (do (x,y) <- partA 3--- assertEqual "for the first result of partA," 5 x--- b <- partB y--- assertBool ("(partB " ++ show y ++ ") failed") b)--- @------ Name the test cases and group them together:------ @--- tests = TestList [TestLabel "test1" test1, TestLabel "test2" test2]--- @------ Run the tests as a group. At a Haskell interpreter prompt, apply the function--- @runTestTT@ to the collected tests. (The /TT/ suggests /T/ext orientation--- with output to the /T/erminal.)------ @--- \> runTestTT tests--- Cases: 2 Tried: 2 Errors: 0 Failures: 0--- \>--- @------ If the tests are proving their worth, you might see:------ @--- \> runTestTT tests--- ### Failure in: 0:test1--- for (foo 3),--- expected: (1,2)--- but got: (1,3)--- Cases: 2 Tried: 2 Errors: 0 Failures: 1--- \>--- @------ You can specify tests even more succinctly using operators and overloaded--- functions that HUnit provides:------ @--- tests = test [ "test1" ~: "(foo 3)" ~: (1,2) ~=? (foo 3),--- "test2" ~: do (x, y) <- partA 3--- assertEqual "for the first result of partA," 5 x--- partB y \@? "(partB " ++ show y ++ ") failed" ]--- @------ Assuming the same test failures as before, you would see:------ @--- \> runTestTT tests--- ### Failure in: 0:test1:(foo 3)--- expected: (1,2)--- but got: (1,3)--- Cases: 2 Tried: 2 Errors: 0 Failures: 1--- \>--- @--module Test.HUnit-(- module Test.HUnit.Base,- module Test.HUnit.Text-)-where--import Test.HUnit.Base-import Test.HUnit.Text-
− vendor/HUnit-1.6.2.0/src/Test/HUnit/Base.hs
@@ -1,361 +0,0 @@-{-# LANGUAGE CPP #-}-{-# LANGUAGE FlexibleContexts #-}--#if __GLASGOW_HASKELL__ >= 704-{-# LANGUAGE ConstraintKinds #-}-#define HasCallStack_ HasCallStack =>-#else-#define HasCallStack_-#endif---- | Basic definitions for the HUnit library.------ This module contains what you need to create assertions and test cases and--- combine them into test suites.------ This module also provides infrastructure for--- implementing test controllers (which are used to execute tests).--- See "Test.HUnit.Text" for a great example of how to implement a test--- controller.--module Test.HUnit.Base-(- -- ** Declaring tests- Test(..),- (~=?), (~?=), (~:), (~?),-- -- ** Making assertions- assertFailure, {- from Test.HUnit.Lang: -}- assertBool, assertEqual, assertString,- Assertion, {- from Test.HUnit.Lang: -}- (@=?), (@?=), (@?),-- -- ** Extending the assertion functionality- Assertable(..), ListAssertable(..),- AssertionPredicate, AssertionPredicable(..),- Testable(..),-- -- ** Test execution- -- $testExecutionNote- State(..), Counts(..),- Path, Node(..),- testCasePaths,- testCaseCount,- ReportStart, ReportProblem,- performTest-) where--import Control.Monad (unless, foldM)-import Data.CallStack----- Assertion Definition--- ====================--import Test.HUnit.Lang----- Conditional Assertion Functions--- ----------------------------------- | Asserts that the specified condition holds.-assertBool :: HasCallStack_- String -- ^ The message that is displayed if the assertion fails- -> Bool -- ^ The condition- -> Assertion-assertBool msg b = unless b (assertFailure msg)---- | Signals an assertion failure if a non-empty message (i.e., a message--- other than @\"\"@) is passed.-assertString :: HasCallStack_- String -- ^ The message that is displayed with the assertion failure- -> Assertion-assertString s = unless (null s) (assertFailure s)---- Overloaded `assert` Function--- -------------------------------- | Allows the extension of the assertion mechanism.------ Since an 'Assertion' can be a sequence of @Assertion@s and @IO@ actions,--- there is a fair amount of flexibility of what can be achieved. As a rule,--- the resulting @Assertion@ should be the body of a 'TestCase' or part of--- a @TestCase@; it should not be used to assert multiple, independent--- conditions.------ If more complex arrangements of assertions are needed, 'Test's and--- 'Testable' should be used.-class Assertable t- where assert :: HasCallStack_ t -> Assertion--instance Assertable ()- where assert = return--instance Assertable Bool- where assert = assertBool ""--instance (ListAssertable t) => Assertable [t]- where assert = listAssert--instance (Assertable t) => Assertable (IO t)- where assert = (>>= assert)---- | A specialized form of 'Assertable' to handle lists.-class ListAssertable t- where listAssert :: HasCallStack_ [t] -> Assertion--instance ListAssertable Char- where listAssert = assertString----- Overloaded `assertionPredicate` Function--- -------------------------------------------- | The result of an assertion that hasn't been evaluated yet.------ Most test cases follow the following steps:------ 1. Do some processing or an action.------ 2. Assert certain conditions.------ However, this flow is not always suitable. @AssertionPredicate@ allows for--- additional steps to be inserted without the initial action to be affected--- by side effects. Additionally, clean-up can be done before the test case--- has a chance to end. A potential work flow is:------ 1. Write data to a file.------ 2. Read data from a file, evaluate conditions.------ 3. Clean up the file.------ 4. Assert that the side effects of the read operation meet certain conditions.------ 5. Assert that the conditions evaluated in step 2 are met.-type AssertionPredicate = IO Bool---- | Used to signify that a data type can be converted to an assertion--- predicate.-class AssertionPredicable t- where assertionPredicate :: t -> AssertionPredicate--instance AssertionPredicable Bool- where assertionPredicate = return--instance (AssertionPredicable t) => AssertionPredicable (IO t)- where assertionPredicate = (>>= assertionPredicate)----- Assertion Construction Operators--- ----------------------------------infix 1 @?, @=?, @?=---- | Asserts that the condition obtained from the specified--- 'AssertionPredicable' holds.-(@?) :: HasCallStack_ AssertionPredicable t- => t -- ^ A value of which the asserted condition is predicated- -> String -- ^ A message that is displayed if the assertion fails- -> Assertion-predi @? msg = assertionPredicate predi >>= assertBool msg---- | Asserts that the specified actual value is equal to the expected value--- (with the expected value on the left-hand side).-(@=?) :: HasCallStack_ (Eq a, Show a)- => a -- ^ The expected value- -> a -- ^ The actual value- -> Assertion-expected @=? actual = assertEqual "" expected actual---- | Asserts that the specified actual value is equal to the expected value--- (with the actual value on the left-hand side).-(@?=) :: HasCallStack_ (Eq a, Show a)- => a -- ^ The actual value- -> a -- ^ The expected value- -> Assertion-actual @?= expected = assertEqual "" expected actual------ Test Definition--- ===============---- | The basic structure used to create an annotated tree of test cases.-data Test- -- | A single, independent test case composed.- = TestCase Assertion- -- | A set of @Test@s sharing the same level in the hierarchy.- | TestList [Test]- -- | A name or description for a subtree of the @Test@s.- | TestLabel String Test--instance Show Test where- showsPrec _ (TestCase _) = showString "TestCase _"- showsPrec _ (TestList ts) = showString "TestList " . showList ts- showsPrec p (TestLabel l t) = showString "TestLabel " . showString l- . showChar ' ' . showsPrec p t---- Overloaded `test` Function--- ------------------------------ | Provides a way to convert data into a @Test@ or set of @Test@.-class Testable t- where test :: HasCallStack_ t -> Test--instance Testable Test- where test = id--instance (Assertable t) => Testable (IO t)- where test = TestCase . assert--instance (Testable t) => Testable [t]- where test = TestList . map test----- Test Construction Operators--- -----------------------------infix 1 ~?, ~=?, ~?=-infixr 0 ~:---- | Creates a test case resulting from asserting the condition obtained--- from the specified 'AssertionPredicable'.-(~?) :: HasCallStack_ AssertionPredicable t- => t -- ^ A value of which the asserted condition is predicated- -> String -- ^ A message that is displayed on test failure- -> Test-predi ~? msg = TestCase (predi @? msg)---- | Shorthand for a test case that asserts equality (with the expected--- value on the left-hand side, and the actual value on the right-hand--- side).-(~=?) :: HasCallStack_ (Eq a, Show a)- => a -- ^ The expected value- -> a -- ^ The actual value- -> Test-expected ~=? actual = TestCase (expected @=? actual)---- | Shorthand for a test case that asserts equality (with the actual--- value on the left-hand side, and the expected value on the right-hand--- side).-(~?=) :: HasCallStack_ (Eq a, Show a)- => a -- ^ The actual value- -> a -- ^ The expected value- -> Test-actual ~?= expected = TestCase (actual @?= expected)---- | Creates a test from the specified 'Testable', with the specified--- label attached to it.------ Since 'Test' is @Testable@, this can be used as a shorthand way of attaching--- a 'TestLabel' to one or more tests.-(~:) :: HasCallStack_ Testable t => String -> t -> Test-label ~: t = TestLabel label (test t)------ Test Execution--- ==============---- $testExecutionNote--- Note: the rest of the functionality in this module is intended for--- implementors of test controllers. If you just want to run your tests cases,--- simply use a test controller, such as the text-based controller in--- "Test.HUnit.Text".---- | A data structure that hold the results of tests that have been performed--- up until this point.-data Counts = Counts { cases, tried, errors, failures :: Int }- deriving (Eq, Show, Read)---- | Keeps track of the remaining tests and the results of the performed tests.--- As each test is performed, the path is removed and the counts are--- updated as appropriate.-data State = State { path :: Path, counts :: Counts }- deriving (Eq, Show, Read)---- | Report generator for reporting the start of a test run.-type ReportStart us = State -> us -> IO us---- | Report generator for reporting problems that have occurred during--- a test run. Problems may be errors or assertion failures.-type ReportProblem us = Maybe SrcLoc -> String -> State -> us -> IO us---- | Uniquely describes the location of a test within a test hierarchy.--- Node order is from test case to root.-type Path = [Node]---- | Composed into 'Path's.-data Node = ListItem Int | Label String- deriving (Eq, Show, Read)---- | Determines the paths for all 'TestCase's in a tree of @Test@s.-testCasePaths :: Test -> [Path]-testCasePaths t0 = tcp t0 []- where tcp (TestCase _) p = [p]- tcp (TestList ts) p =- concat [ tcp t (ListItem n : p) | (t,n) <- zip ts [0..] ]- tcp (TestLabel l t) p = tcp t (Label l : p)---- | Counts the number of 'TestCase's in a tree of @Test@s.-testCaseCount :: Test -> Int-testCaseCount (TestCase _) = 1-testCaseCount (TestList ts) = sum (map testCaseCount ts)-testCaseCount (TestLabel _ t) = testCaseCount t---- | Performs a test run with the specified report generators.------ This handles the actual running of the tests. Most developers will want--- to use @HUnit.Text.runTestTT@ instead. A developer could use this function--- to execute tests via another IO system, such as a GUI, or to output the--- results in a different manner (e.g., upload XML-formatted results to a--- webservice).------ Note that the counts in a start report do not include the test case--- being started, whereas the counts in a problem report do include the--- test case just finished. The principle is that the counts are sampled--- only between test case executions. As a result, the number of test--- case successes always equals the difference of test cases tried and--- the sum of test case errors and failures.-performTest :: ReportStart us -- ^ report generator for the test run start- -> ReportProblem us -- ^ report generator for errors during the test run- -> ReportProblem us -- ^ report generator for assertion failures during the test run- -> us- -> Test -- ^ the test to be executed- -> IO (Counts, us)-performTest reportStart reportError reportFailure initialUs initialT = do- (ss', us') <- pt initState initialUs initialT- unless (null (path ss')) $ error "performTest: Final path is nonnull"- return (counts ss', us')- where- initState = State{ path = [], counts = initCounts }- initCounts = Counts{ cases = testCaseCount initialT, tried = 0,- errors = 0, failures = 0}-- pt ss us (TestCase a) = do- us' <- reportStart ss us- r <- performTestCase a- case r of- Success -> do- return (ss', us')- Failure loc m -> do- usF <- reportFailure loc m ssF us'- return (ssF, usF)- Error loc m -> do- usE <- reportError loc m ssE us'- return (ssE, usE)- where c@Counts{ tried = n } = counts ss- ss' = ss{ counts = c{ tried = n + 1 } }- ssF = ss{ counts = c{ tried = n + 1, failures = failures c + 1 } }- ssE = ss{ counts = c{ tried = n + 1, errors = errors c + 1 } }-- pt ss us (TestList ts) = foldM f (ss, us) (zip ts [0..])- where f (ss', us') (t, n) = withNode (ListItem n) ss' us' t-- pt ss us (TestLabel label t) = withNode (Label label) ss us t-- withNode node ss0 us0 t = do (ss2, us1) <- pt ss1 us0 t- return (ss2{ path = path0 }, us1)- where path0 = path ss0- ss1 = ss0{ path = node : path0 }
− vendor/HUnit-1.6.2.0/src/Test/HUnit/Lang.hs
@@ -1,104 +0,0 @@-{-# LANGUAGE CPP #-}-{-# LANGUAGE DeriveDataTypeable #-}-{-# LANGUAGE FlexibleContexts #-}--#if __GLASGOW_HASKELL__ >= 704-{-# LANGUAGE ConstraintKinds #-}-#define HasCallStack_ HasCallStack =>-#else-#define HasCallStack_-#endif--module Test.HUnit.Lang (- Assertion,- assertFailure,- assertEqual,-- Result (..),- performTestCase,--- * Internals--- |--- /Note:/ This is not part of the public API! It is exposed so that you can--- tinker with the internals of HUnit, but do not expect it to be stable!- HUnitFailure (..),- FailureReason (..),- formatFailureReason-) where--import Control.DeepSeq-import Control.Exception as E-import Control.Monad-import Data.List-import Data.Typeable-import Data.CallStack---- | When an assertion is evaluated, it will output a message if and only if the--- assertion fails.------ Test cases are composed of a sequence of one or more assertions.-type Assertion = IO ()--data HUnitFailure = HUnitFailure (Maybe SrcLoc) FailureReason- deriving (Eq, Show, Typeable)--instance Exception HUnitFailure--data FailureReason = Reason String | ExpectedButGot (Maybe String) String String- deriving (Eq, Show, Typeable)--location :: HasCallStack_ Maybe SrcLoc-location = case reverse callStack of- (_, loc) : _ -> Just loc- [] -> Nothing---- | Unconditionally signals that a failure has occurred.-assertFailure ::- HasCallStack_- String -- ^ A message that is displayed with the assertion failure- -> IO a-assertFailure msg = msg `deepseq` E.throwIO (HUnitFailure location $ Reason msg)---- | Asserts that the specified actual value is equal to the expected value.--- The output message will contain the prefix, the expected value, and the--- actual value.------ If the prefix is the empty string (i.e., @\"\"@), then the prefix is omitted--- and only the expected and actual values are output.-assertEqual :: HasCallStack_ (Eq a, Show a)- => String -- ^ The message prefix- -> a -- ^ The expected value- -> a -- ^ The actual value- -> Assertion-assertEqual preface expected actual =- unless (actual == expected) $ do- (prefaceMsg `deepseq` expectedMsg `deepseq` actualMsg `deepseq` E.throwIO (HUnitFailure location $ ExpectedButGot prefaceMsg expectedMsg actualMsg))- where- prefaceMsg- | null preface = Nothing- | otherwise = Just preface- expectedMsg = show expected- actualMsg = show actual--formatFailureReason :: FailureReason -> String-formatFailureReason (Reason reason) = reason-formatFailureReason (ExpectedButGot preface expected actual) = intercalate "\n" . maybe id (:) preface $ ["expected: " ++ expected, " but got: " ++ actual]--data Result = Success | Failure (Maybe SrcLoc) String | Error (Maybe SrcLoc) String- deriving (Eq, Show)---- | Performs a single test case.-performTestCase :: Assertion -- ^ an assertion to be made during the test case run- -> IO Result-performTestCase action =- (action >> return Success)- `E.catches`- [E.Handler (\(HUnitFailure loc reason) -> return $ Failure loc (formatFailureReason reason)),-- -- Re-throw AsyncException, otherwise execution will not terminate on- -- SIGINT (ctrl-c). Currently, all AsyncExceptions are being thrown- -- because it's thought that none of them will be encountered during- -- normal HUnit operation. If you encounter an example where this- -- is not the case, please email the maintainer.- E.Handler (\e -> throw (e :: E.AsyncException)),-- E.Handler (\e -> return $ Error Nothing $ show (e :: E.SomeException))]
− vendor/HUnit-1.6.2.0/src/Test/HUnit/Terminal.hs
@@ -1,42 +0,0 @@--- | This module handles the complexities of writing information to the--- terminal, including modifying text in place.--module Test.HUnit.Terminal (- terminalAppearance- ) where--import Data.Char (isPrint)----- | Simplifies the input string by interpreting @\\r@ and @\\b@ characters--- specially so that the result string has the same final (or /terminal/,--- pun intended) appearance as would the input string when written to a--- terminal that overwrites character positions following carriage--- returns and backspaces.--terminalAppearance :: String -> String-terminalAppearance str = ta id "" "" str---- | The helper function @ta@ takes an accumulating @ShowS@-style function--- that holds /committed/ lines of text, a (reversed) list of characters--- on the current line /before/ the cursor, a (normal) list of characters--- on the current line /after/ the cursor, and the remaining input.--ta- :: ([Char] -> t) -- ^ An accumulating @ShowS@-style function- -- that holds /committed/ lines of text- -> [Char] -- ^ A (reversed) list of characters- -- on the current line /before/ the cursor- -> [Char] -- ^ A (normal) list of characters- -- on the current line /after/ the cursor- -> [Char] -- ^ The remaining input- -> t-ta f bs as ('\n':cs) = ta (\t -> f (reverse bs ++ as ++ '\n' : t)) "" "" cs-ta f bs as ('\r':cs) = ta f "" (reverse bs ++ as) cs-ta f (b:bs) as ('\b':cs) = ta f bs (b:as) cs-ta _ "" _ ('\b': _) = error "'\\b' at beginning of line"-ta f bs as (c:cs)- | not (isPrint c) = error "invalid nonprinting character"- | null as = ta f (c:bs) "" cs- | otherwise = ta f (c:bs) (tail as) cs-ta f bs as "" = f (reverse bs ++ as)
− vendor/HUnit-1.6.2.0/src/Test/HUnit/Text.hs
@@ -1,152 +0,0 @@--- | Text-based test controller for running HUnit tests and reporting--- results as text, usually to a terminal.--module Test.HUnit.Text-(- PutText(..),- putTextToHandle, putTextToShowS,- runTestText,- showPath, showCounts,- runTestTT,- runTestTTAndExit-)-where--import Test.HUnit.Base--import Data.CallStack-import Control.Monad (when)-import System.IO (Handle, stderr, hPutStr, hPutStrLn)-import System.Exit (exitSuccess, exitFailure)----- | As the general text-based test controller ('runTestText') executes a--- test, it reports each test case start, error, and failure by--- constructing a string and passing it to the function embodied in a--- 'PutText'. A report string is known as a \"line\", although it includes--- no line terminator; the function in a 'PutText' is responsible for--- terminating lines appropriately. Besides the line, the function--- receives a flag indicating the intended \"persistence\" of the line:--- 'True' indicates that the line should be part of the final overall--- report; 'False' indicates that the line merely indicates progress of--- the test execution. Each progress line shows the current values of--- the cumulative test execution counts; a final, persistent line shows--- the final count values.------ The 'PutText' function is also passed, and returns, an arbitrary state--- value (called 'st' here). The initial state value is given in the--- 'PutText'; the final value is returned by 'runTestText'.--data PutText st = PutText (String -> Bool -> st -> IO st) st----- | Two reporting schemes are defined here. @putTextToHandle@ writes--- report lines to a given handle. 'putTextToShowS' accumulates--- persistent lines for return as a whole by 'runTestText'.------ @putTextToHandle@ writes persistent lines to the given handle,--- following each by a newline character. In addition, if the given flag--- is @True@, it writes progress lines to the handle as well. A progress--- line is written with no line termination, so that it can be--- overwritten by the next report line. As overwriting involves writing--- carriage return and blank characters, its proper effect is usually--- only obtained on terminal devices.--putTextToHandle- :: Handle- -> Bool -- ^ Write progress lines to handle?- -> PutText Int-putTextToHandle handle showProgress = PutText put initCnt- where- initCnt = if showProgress then 0 else -1- put line pers (-1) = do when pers (hPutStrLn handle line); return (-1)- put line True cnt = do hPutStrLn handle (erase cnt ++ line); return 0- put line False _ = do hPutStr handle ('\r' : line); return (length line)- -- The "erasing" strategy with a single '\r' relies on the fact that the- -- lengths of successive summary lines are monotonically nondecreasing.- erase cnt = if cnt == 0 then "" else "\r" ++ replicate cnt ' ' ++ "\r"----- | Accumulates persistent lines (dropping progess lines) for return by--- 'runTestText'. The accumulated lines are represented by a--- @'ShowS' ('String' -> 'String')@ function whose first argument is the--- string to be appended to the accumulated report lines.--putTextToShowS :: PutText ShowS-putTextToShowS = PutText put id- where put line pers f = return (if pers then acc f line else f)- acc f line rest = f (line ++ '\n' : rest)----- | Executes a test, processing each report line according to the given--- reporting scheme. The reporting scheme's state is threaded through calls--- to the reporting scheme's function and finally returned, along with final--- count values.--runTestText :: PutText st -> Test -> IO (Counts, st)-runTestText (PutText put us0) t = do- (counts', us1) <- performTest reportStart reportError reportFailure us0 t- us2 <- put (showCounts counts') True us1- return (counts', us2)- where- reportStart ss us = put (showCounts (counts ss)) False us- reportError = reportProblem "Error:" "Error in: "- reportFailure = reportProblem "Failure:" "Failure in: "- reportProblem p0 p1 loc msg ss us = put line True us- where line = "### " ++ kind ++ path' ++ "\n" ++ formatLocation loc ++ msg- kind = if null path' then p0 else p1- path' = showPath (path ss)--formatLocation :: Maybe SrcLoc -> String-formatLocation Nothing = ""-formatLocation (Just loc) = srcLocFile loc ++ ":" ++ show (srcLocStartLine loc) ++ "\n"---- | Converts test execution counts to a string.--showCounts :: Counts -> String-showCounts Counts{ cases = cases', tried = tried',- errors = errors', failures = failures' } =- "Cases: " ++ show cases' ++ " Tried: " ++ show tried' ++- " Errors: " ++ show errors' ++ " Failures: " ++ show failures'----- | Converts a test case path to a string, separating adjacent elements by--- the colon (\':\'). An element of the path is quoted (as with 'show') when--- there is potential ambiguity.--showPath :: Path -> String-showPath [] = ""-showPath nodes = foldl1 f (map showNode nodes)- where f b a = a ++ ":" ++ b- showNode (ListItem n) = show n- showNode (Label label) = safe label (show label)- safe s ss = if ':' `elem` s || "\"" ++ s ++ "\"" /= ss then ss else s----- | Provides the \"standard\" text-based test controller. Reporting is made to--- standard error, and progress reports are included. For possible--- programmatic use, the final counts are returned.------ The \"TT\" in the name suggests \"Text-based reporting to the Terminal\".--runTestTT :: Test -> IO Counts-runTestTT t = do (counts', 0) <- runTestText (putTextToHandle stderr True) t- return counts'---- | Convenience wrapper for 'runTestTT'.--- Simply runs 'runTestTT' and then exits back to the OS,--- using 'exitSuccess' if there were no errors or failures,--- or 'exitFailure' if there were. For example:------ > tests :: Test--- > tests = ...--- >--- > main :: IO ()--- > main = runTestTTAndExit tests--runTestTTAndExit :: Test -> IO ()-runTestTTAndExit tests = do- c <- runTestTT tests- if (errors c == 0) && (failures c == 0)- then exitSuccess- else exitFailure
− vendor/hspec-expectations-0.8.2/src/Test/Hspec/Expectations.hs
@@ -1,189 +0,0 @@-{-# LANGUAGE CPP #-}-{-# LANGUAGE ConstraintKinds #-}-{-# LANGUAGE KindSignatures #-}-{-# LANGUAGE ImplicitParams #-}--- |--- Introductory documentation: <https://github.com/sol/hspec-expectations#readme>-module Test.Hspec.Expectations (---- * Setting expectations- Expectation-, expectationFailure-, shouldBe-, shouldSatisfy-, shouldStartWith-, shouldEndWith-, shouldContain-, shouldMatchList-, shouldReturn--, shouldNotBe-, shouldNotSatisfy-, shouldNotContain-, shouldNotReturn---- * Expecting exceptions-, shouldThrow---- ** Selecting exceptions-, Selector---- ** Predefined type-based selectors--- |--- There are predefined selectors for some standard exceptions. Each selector--- is just @const True@ with an appropriate type.-, anyException-, anyErrorCall-, anyIOException-, anyArithException---- ** Combinators for defining value-based selectors--- |--- Some exceptions (most prominently `ErrorCall`) have no `Eq` instance.--- Selecting a specific value would require pattern matching.------ For such exceptions, combinators that construct selectors are provided.--- Each combinator corresponds to a constructor; it takes the same arguments,--- and has the same name (but starting with a lower-case letter).-, errorCall---- * Re-exports-, HasCallStack-) where--import qualified Test.HUnit-import Test.HUnit ((@?=))-import Control.Exception-import Data.Typeable-import Data.List--import Control.Monad (unless)--import Test.Hspec.Expectations.Matcher--import Data.CallStack (HasCallStack)--type Expectation = Test.HUnit.Assertion--expectationFailure :: HasCallStack => String -> Expectation-expectationFailure = Test.HUnit.assertFailure--expectTrue :: HasCallStack => String -> Bool -> Expectation-expectTrue msg b = unless b (expectationFailure msg)--infix 1 `shouldBe`, `shouldSatisfy`, `shouldStartWith`, `shouldEndWith`, `shouldContain`, `shouldMatchList`, `shouldReturn`, `shouldThrow`-infix 1 `shouldNotBe`, `shouldNotSatisfy`, `shouldNotContain`, `shouldNotReturn`---- |--- @actual \`shouldBe\` expected@ sets the expectation that @actual@ is equal--- to @expected@.-shouldBe :: (HasCallStack, Show a, Eq a) => a -> a -> Expectation-actual `shouldBe` expected = actual @?= expected---- |--- @v \`shouldSatisfy\` p@ sets the expectation that @p v@ is @True@.-shouldSatisfy :: (HasCallStack, Show a) => a -> (a -> Bool) -> Expectation-v `shouldSatisfy` p = expectTrue ("predicate failed on: " ++ show v) (p v)--compareWith :: (HasCallStack, Show a) => (a -> a -> Bool) -> String -> a -> a -> Expectation-compareWith comparator errorDesc result expected = expectTrue errorMsg (comparator expected result)- where- errorMsg = show result ++ " " ++ errorDesc ++ " " ++ show expected---- |--- @list \`shouldStartWith\` prefix@ sets the expectation that @list@ starts with @prefix@,-shouldStartWith :: (HasCallStack, Show a, Eq a) => [a] -> [a] -> Expectation-shouldStartWith = compareWith isPrefixOf "does not start with"---- |--- @list \`shouldEndWith\` suffix@ sets the expectation that @list@ ends with @suffix@,-shouldEndWith :: (HasCallStack, Show a, Eq a) => [a] -> [a] -> Expectation-shouldEndWith = compareWith isSuffixOf "does not end with"---- |--- @list \`shouldContain\` sublist@ sets the expectation that @sublist@ is contained,--- wholly and intact, anywhere in @list@.-shouldContain :: (HasCallStack, Show a, Eq a) => [a] -> [a] -> Expectation-shouldContain = compareWith isInfixOf "does not contain"---- |--- @xs \`shouldMatchList\` ys@ sets the expectation that @xs@ has the same--- elements that @ys@ has, possibly in another order-shouldMatchList :: (HasCallStack, Show a, Eq a) => [a] -> [a] -> Expectation-xs `shouldMatchList` ys = maybe (return ()) expectationFailure (matchList xs ys)---- |--- @action \`shouldReturn\` expected@ sets the expectation that @action@--- returns @expected@.-shouldReturn :: (HasCallStack, Show a, Eq a) => IO a -> a -> Expectation-action `shouldReturn` expected = action >>= (`shouldBe` expected)---- |--- @actual \`shouldNotBe\` notExpected@ sets the expectation that @actual@ is not--- equal to @notExpected@-shouldNotBe :: (HasCallStack, Show a, Eq a) => a -> a -> Expectation-actual `shouldNotBe` notExpected = expectTrue ("not expected: " ++ show actual) (actual /= notExpected)---- |--- @v \`shouldNotSatisfy\` p@ sets the expectation that @p v@ is @False@.-shouldNotSatisfy :: (HasCallStack, Show a) => a -> (a -> Bool) -> Expectation-v `shouldNotSatisfy` p = expectTrue ("predicate succeeded on: " ++ show v) ((not . p) v)---- |--- @list \`shouldNotContain\` sublist@ sets the expectation that @sublist@ is not--- contained anywhere in @list@.-shouldNotContain :: (HasCallStack, Show a, Eq a) => [a] -> [a] -> Expectation-list `shouldNotContain` sublist = expectTrue errorMsg ((not . isInfixOf sublist) list)- where- errorMsg = show list ++ " does contain " ++ show sublist---- |--- @action \`shouldNotReturn\` notExpected@ sets the expectation that @action@--- does not return @notExpected@.-shouldNotReturn :: (HasCallStack, Show a, Eq a) => IO a -> a -> Expectation-action `shouldNotReturn` notExpected = action >>= (`shouldNotBe` notExpected)---- |--- A @Selector@ is a predicate; it can simultaneously constrain the type and--- value of an exception.-type Selector a = (a -> Bool)---- |--- @action \`shouldThrow\` selector@ sets the expectation that @action@ throws--- an exception. The precise nature of the expected exception is described--- with a 'Selector'.-shouldThrow :: (HasCallStack, Exception e) => IO a -> Selector e -> Expectation-action `shouldThrow` p = do- r <- try action- case r of- Right _ ->- expectationFailure $- "did not get expected exception: " ++ exceptionType- Left e ->- (`expectTrue` p e) $- "predicate failed on expected exception: " ++ exceptionType ++ " (" ++ show e ++ ")"- where- -- a string repsentation of the expected exception's type- exceptionType = (show . typeOf . instanceOf) p- where- instanceOf :: Selector a -> a- instanceOf _ = error "Test.Hspec.Expectations.shouldThrow: broken Typeable instance"--anyException :: Selector SomeException-anyException = const True--anyErrorCall :: Selector ErrorCall-anyErrorCall = const True--errorCall :: String -> Selector ErrorCall-#if MIN_VERSION_base(4,9,0)-errorCall s (ErrorCallWithLocation msg _) = s == msg-#else-errorCall s (ErrorCall msg) = s == msg-#endif--anyIOException :: Selector IOException-anyIOException = const True--anyArithException :: Selector ArithException-anyArithException = const True
− vendor/hspec-expectations-0.8.2/src/Test/Hspec/Expectations/Contrib.hs
@@ -1,26 +0,0 @@-{-# LANGUAGE CPP #-}--- |--- Experimental combinators, that may become part of the main distribution, if--- they turn out to be useful for a wider audience.-module Test.Hspec.Expectations.Contrib (--- * Predicates--- | (useful in combination with `shouldSatisfy`)- isLeft-, isRight-) where---#if MIN_VERSION_base(4,7,0)-import Data.Either-#else--isLeft :: Either a b -> Bool-{-# DEPRECATED isLeft "use Data.Either.Compat.isLeft from package base-compat instead" #-}-isLeft (Left _) = True-isLeft (Right _) = False--isRight :: Either a b -> Bool-{-# DEPRECATED isRight "use Data.Either.Compat.isRight from package base-compat instead" #-}-isRight (Left _) = False-isRight (Right _) = True-#endif
− vendor/hspec-expectations-0.8.2/src/Test/Hspec/Expectations/Matcher.hs
@@ -1,26 +0,0 @@-module Test.Hspec.Expectations.Matcher (matchList) where--import Prelude hiding (showList)-import Data.List--matchList :: (Show a, Eq a) => [a] -> [a] -> Maybe String-xs `matchList` ys- | null extra && null missing = Nothing- | otherwise = Just (err "")- where- extra = xs \\ ys- missing = ys \\ xs-- msgAndList msg zs = showString msg . showList zs . showString "\n"- optMsgList msg zs = if null zs then id else msgAndList msg zs-- err :: ShowS- err =- showString "Actual list is not a permutation of expected list!\n"- . msgAndList " expected list contains: " ys- . msgAndList " actual list contains: " xs- . optMsgList " the missing elements are: " missing- . optMsgList " the extra elements are: " extra--showList :: Show a => [a] -> ShowS-showList xs = showChar '[' . foldr (.) (showChar ']') (intersperse (showString ", ") $ map shows xs)
+ vendor/stm-2.5.0.1/Control/Concurrent/STM/TMVar.hs view
@@ -0,0 +1,168 @@+{-# LANGUAGE CPP, DeriveDataTypeable, MagicHash, UnboxedTuples #-}++#if __GLASGOW_HASKELL__ >= 701+{-# LANGUAGE Trustworthy #-}+#endif++{-# OPTIONS -fno-warn-implicit-prelude #-}++-----------------------------------------------------------------------------+-- |+-- Module : Control.Concurrent.STM.TMVar+-- Copyright : (c) The University of Glasgow 2004+-- License : BSD-style (see the file libraries/base/LICENSE)+--+-- Maintainer : libraries@haskell.org+-- Stability : experimental+-- Portability : non-portable (requires STM)+--+-- TMVar: Transactional MVars, for use in the STM monad+-- (GHC only)+--+-----------------------------------------------------------------------------++module Control.Concurrent.STM.TMVar (+#ifdef __GLASGOW_HASKELL__+ -- * TMVars+ TMVar,+ newTMVar,+ newEmptyTMVar,+ newTMVarIO,+ newEmptyTMVarIO,+ takeTMVar,+ putTMVar,+ readTMVar,+ tryReadTMVar,+ swapTMVar,+ tryTakeTMVar,+ tryPutTMVar,+ isEmptyTMVar,+ mkWeakTMVar+#endif+ ) where++#ifdef __GLASGOW_HASKELL__+import GHC.Base+import GHC.Conc+import GHC.Weak++import Data.Typeable (Typeable)++newtype TMVar a = TMVar (TVar (Maybe a)) deriving (Eq, Typeable)+{- ^+A 'TMVar' is a synchronising variable, used+for communication between concurrent threads. It can be thought of+as a box, which may be empty or full.+-}++-- |Create a 'TMVar' which contains the supplied value.+newTMVar :: a -> STM (TMVar a)+newTMVar a = do+ t <- newTVar (Just a)+ return (TMVar t)++-- |@IO@ version of 'newTMVar'. This is useful for creating top-level+-- 'TMVar's using 'System.IO.Unsafe.unsafePerformIO', because using+-- 'atomically' inside 'System.IO.Unsafe.unsafePerformIO' isn't+-- possible.+newTMVarIO :: a -> IO (TMVar a)+newTMVarIO a = do+ t <- newTVarIO (Just a)+ return (TMVar t)++-- |Create a 'TMVar' which is initially empty.+newEmptyTMVar :: STM (TMVar a)+newEmptyTMVar = do+ t <- newTVar Nothing+ return (TMVar t)++-- |@IO@ version of 'newEmptyTMVar'. This is useful for creating top-level+-- 'TMVar's using 'System.IO.Unsafe.unsafePerformIO', because using+-- 'atomically' inside 'System.IO.Unsafe.unsafePerformIO' isn't+-- possible.+newEmptyTMVarIO :: IO (TMVar a)+newEmptyTMVarIO = do+ t <- newTVarIO Nothing+ return (TMVar t)++-- |Return the contents of the 'TMVar'. If the 'TMVar' is currently+-- empty, the transaction will 'retry'. After a 'takeTMVar',+-- the 'TMVar' is left empty.+takeTMVar :: TMVar a -> STM a+takeTMVar (TMVar t) = do+ m <- readTVar t+ case m of+ Nothing -> retry+ Just a -> do writeTVar t Nothing; return a++-- | A version of 'takeTMVar' that does not 'retry'. The 'tryTakeTMVar'+-- function returns 'Nothing' if the 'TMVar' was empty, or @'Just' a@ if+-- the 'TMVar' was full with contents @a@. After 'tryTakeTMVar', the+-- 'TMVar' is left empty.+tryTakeTMVar :: TMVar a -> STM (Maybe a)+tryTakeTMVar (TMVar t) = do+ m <- readTVar t+ case m of+ Nothing -> return Nothing+ Just a -> do writeTVar t Nothing; return (Just a)++-- |Put a value into a 'TMVar'. If the 'TMVar' is currently full,+-- 'putTMVar' will 'retry'.+putTMVar :: TMVar a -> a -> STM ()+putTMVar (TMVar t) a = do+ m <- readTVar t+ case m of+ Nothing -> do writeTVar t (Just a); return ()+ Just _ -> retry++-- | A version of 'putTMVar' that does not 'retry'. The 'tryPutTMVar'+-- function attempts to put the value @a@ into the 'TMVar', returning+-- 'True' if it was successful, or 'False' otherwise.+tryPutTMVar :: TMVar a -> a -> STM Bool+tryPutTMVar (TMVar t) a = do+ m <- readTVar t+ case m of+ Nothing -> do writeTVar t (Just a); return True+ Just _ -> return False++-- | This is a combination of 'takeTMVar' and 'putTMVar'; ie. it+-- takes the value from the 'TMVar', puts it back, and also returns+-- it.+readTMVar :: TMVar a -> STM a+readTMVar (TMVar t) = do+ m <- readTVar t+ case m of+ Nothing -> retry+ Just a -> return a++-- | A version of 'readTMVar' which does not retry. Instead it+-- returns @Nothing@ if no value is available.+--+-- @since 2.3+tryReadTMVar :: TMVar a -> STM (Maybe a)+tryReadTMVar (TMVar t) = readTVar t++-- |Swap the contents of a 'TMVar' for a new value.+swapTMVar :: TMVar a -> a -> STM a+swapTMVar (TMVar t) new = do+ m <- readTVar t+ case m of+ Nothing -> retry+ Just old -> do writeTVar t (Just new); return old++-- |Check whether a given 'TMVar' is empty.+isEmptyTMVar :: TMVar a -> STM Bool+isEmptyTMVar (TMVar t) = do+ m <- readTVar t+ case m of+ Nothing -> return True+ Just _ -> return False++-- | Make a 'Weak' pointer to a 'TMVar', using the second argument as+-- a finalizer to run when the 'TMVar' is garbage-collected.+--+-- @since 2.4.4+mkWeakTMVar :: TMVar a -> IO () -> IO (Weak (TMVar a))+mkWeakTMVar tmv@(TMVar (TVar t#)) (IO finalizer) = IO $ \s ->+ case mkWeak# t# tmv finalizer s of (# s1, w #) -> (# s1, Weak w #)+#endif
− version.yaml
@@ -1,1 +0,0 @@-&version 2.10.5