hspec-core (empty) → 2.0.0
raw patch · 34 files changed
+3439/−0 lines, 34 filesdep +HUnitdep +QuickCheckdep +ansi-terminalsetup-changed
Dependencies added: HUnit, QuickCheck, ansi-terminal, async, base, deepseq, ghc-paths, hspec-expectations, hspec-meta, process, quickcheck-io, random, setenv, silently, tf-random, time, transformers
Files
- LICENSE +21/−0
- Setup.lhs +3/−0
- hspec-core.cabal +106/−0
- src/Test/Hspec/Compat.hs +83/−0
- src/Test/Hspec/Config.hs +82/−0
- src/Test/Hspec/Core/Example.hs +121/−0
- src/Test/Hspec/Core/Formatters.hs +216/−0
- src/Test/Hspec/Core/Formatters/Internal.hs +262/−0
- src/Test/Hspec/Core/Hooks.hs +74/−0
- src/Test/Hspec/Core/QuickCheck.hs +37/−0
- src/Test/Hspec/Core/QuickCheckUtil.hs +60/−0
- src/Test/Hspec/Core/Runner.hs +174/−0
- src/Test/Hspec/Core/Runner/Eval.hs +157/−0
- src/Test/Hspec/Core/Spec.hs +70/−0
- src/Test/Hspec/Core/Spec/Monad.hs +65/−0
- src/Test/Hspec/Core/Tree.hs +103/−0
- src/Test/Hspec/Core/Util.hs +132/−0
- src/Test/Hspec/FailureReport.hs +36/−0
- src/Test/Hspec/Options.hs +178/−0
- src/Test/Hspec/Timer.hs +14/−0
- test/Helper.hs +89/−0
- test/Mock.hs +15/−0
- test/Spec.hs +1/−0
- test/Test/Hspec/CompatSpec.hs +33/−0
- test/Test/Hspec/Core/ExampleSpec.hs +136/−0
- test/Test/Hspec/Core/FormattersSpec.hs +227/−0
- test/Test/Hspec/Core/HooksSpec.hs +230/−0
- test/Test/Hspec/Core/QuickCheckUtilSpec.hs +31/−0
- test/Test/Hspec/Core/RunnerSpec.hs +399/−0
- test/Test/Hspec/Core/SpecSpec.hs +69/−0
- test/Test/Hspec/Core/UtilSpec.hs +103/−0
- test/Test/Hspec/FailureReportSpec.hs +39/−0
- test/Test/Hspec/OptionsSpec.hs +44/−0
- test/Test/Hspec/TimerSpec.hs +29/−0
+ LICENSE view
@@ -0,0 +1,21 @@+Copyright (c) 2011-2014 Simon Hengel <sol@typeful.net>+Copyright (c) 2011-2012 Trystan Spangler <trystan.s@comcast.net>+Copyright (c) 2011-2011 Greg Weber <greg@gregweber.info>++Permission is hereby granted, free of charge, to any person obtaining a copy+of this software and associated documentation files (the "Software"), to deal+in the Software without restriction, including without limitation the rights+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell+copies of the Software, and to permit persons to whom the Software is+furnished to do so, subject to the following conditions:++The above copyright notice and this permission notice shall be included in+all copies or substantial portions of the Software.++THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN+THE SOFTWARE.
+ Setup.lhs view
@@ -0,0 +1,3 @@+#!/usr/bin/env runhaskell+> import Distribution.Simple+> main = defaultMain
+ hspec-core.cabal view
@@ -0,0 +1,106 @@+name: hspec-core+version: 2.0.0+license: MIT+license-file: LICENSE+copyright: (c) 2011-2014 Simon Hengel,+ (c) 2011-2012 Trystan Spangler,+ (c) 2011 Greg Weber+maintainer: Simon Hengel <sol@typeful.net>+build-type: Simple+cabal-version: >= 1.10+category: Testing+stability: experimental+bug-reports: https://github.com/hspec/hspec/issues+homepage: http://hspec.github.io/+synopsis: A Testing Framework for Haskell+description: This package exposes internal types and functions that can be+ used to extend Hspec's functionality.++source-repository head+ type: git+ location: https://github.com/hspec/hspec+ subdir: hspec-core++library+ ghc-options:+ -Wall+ hs-source-dirs:+ src+ build-depends:+ base >= 4.3 && < 5+ , random+ , tf-random+ , setenv+ , ansi-terminal >= 0.5+ , time+ , transformers >= 0.2.2.0+ , deepseq+ , HUnit >= 1.2.5+ , QuickCheck >= 2.5.1+ , quickcheck-io+ , hspec-expectations+ , async >= 2+ exposed-modules:+ Test.Hspec.Core.Spec+ Test.Hspec.Core.Hooks+ Test.Hspec.Core.Runner+ Test.Hspec.Core.Formatters+ Test.Hspec.Core.QuickCheck+ Test.Hspec.Core.Util+ other-modules:+ Test.Hspec.Compat+ Test.Hspec.Core.Example+ Test.Hspec.Core.Tree+ Test.Hspec.Core.Spec.Monad+ Test.Hspec.Core.QuickCheckUtil+ Test.Hspec.Config+ Test.Hspec.Options+ Test.Hspec.FailureReport+ Test.Hspec.Core.Runner.Eval+ Test.Hspec.Core.Formatters.Internal+ Test.Hspec.Timer+ default-language: Haskell2010++test-suite spec+ type:+ exitcode-stdio-1.0+ hs-source-dirs:+ src, test+ main-is:+ Spec.hs+ other-modules:+ Helper+ Mock+ Test.Hspec.CompatSpec+ Test.Hspec.Core.ExampleSpec+ Test.Hspec.Core.FormattersSpec+ Test.Hspec.Core.HooksSpec+ Test.Hspec.Core.QuickCheckUtilSpec+ Test.Hspec.Core.RunnerSpec+ Test.Hspec.Core.SpecSpec+ Test.Hspec.Core.UtilSpec+ Test.Hspec.FailureReportSpec+ Test.Hspec.OptionsSpec+ Test.Hspec.TimerSpec+ ghc-options:+ -Wall+ build-depends:+ base == 4.*+ , random+ , tf-random+ , setenv+ , ansi-terminal+ , time+ , transformers+ , deepseq+ , HUnit+ , QuickCheck+ , quickcheck-io+ , hspec-expectations+ , async++ , hspec-meta >= 1.12+ , silently >= 1.2.4+ , process+ , ghc-paths+ default-language: Haskell2010
+ src/Test/Hspec/Compat.hs view
@@ -0,0 +1,83 @@+{-# LANGUAGE CPP #-}+module Test.Hspec.Compat (+ showType+, showFullType+, readMaybe+, lookupEnv+, module Data.IORef+#if !MIN_VERSION_base(4,6,0)+, modifyIORef'+#endif+) where++#if !MIN_VERSION_base(4,3,0)+import Control.Monad.Trans.Error () -- for Monad (Either e)+#endif++import Data.Typeable (Typeable, typeOf, typeRepTyCon)+import Text.Read+import Data.IORef+import System.Environment++#if MIN_VERSION_base(4,4,0)+import Data.Typeable.Internal (tyConModule, tyConName)+#endif++#if !MIN_VERSION_base(4,6,0)+import qualified Text.ParserCombinators.ReadP as P+#endif++#if !MIN_VERSION_base(4,6,0)+-- |Strict version of 'modifyIORef'+modifyIORef' :: IORef a -> (a -> a) -> IO ()+modifyIORef' ref f = do+ x <- readIORef ref+ let x' = f x+ x' `seq` writeIORef ref x'++-- | Parse a string using the 'Read' instance.+-- Succeeds if there is exactly one valid result.+-- A 'Left' value indicates a parse error.+readEither :: Read a => String -> Either String a+readEither s =+ case [ x | (x,"") <- readPrec_to_S read' minPrec s ] of+ [x] -> Right x+ [] -> Left "Prelude.read: no parse"+ _ -> Left "Prelude.read: ambiguous parse"+ where+ read' =+ do x <- readPrec+ lift P.skipSpaces+ return x++-- | Parse a string using the 'Read' instance.+-- Succeeds if there is exactly one valid result.+readMaybe :: Read a => String -> Maybe a+readMaybe s = case readEither s of+ Left _ -> Nothing+ Right a -> Just a++-- | Return the value of the environment variable @var@, or @Nothing@ if+-- there is no such value.+--+-- For POSIX users, this is equivalent to 'System.Posix.Env.getEnv'.+lookupEnv :: String -> IO (Maybe String)+lookupEnv k = lookup k `fmap` getEnvironment+#endif++showType :: Typeable a => a -> String+showType a = let t = typeRepTyCon (typeOf a) in+#if MIN_VERSION_base(4,4,0)+ show t+#else+ (reverse . takeWhile (/= '.') . reverse . show) t+#endif+++showFullType :: Typeable a => a -> String+showFullType a = let t = typeRepTyCon (typeOf a) in+#if MIN_VERSION_base(4,4,0)+ tyConModule t ++ "." ++ tyConName t+#else+ show t+#endif
+ src/Test/Hspec/Config.hs view
@@ -0,0 +1,82 @@+module Test.Hspec.Config (+ Config (..)+, ColorMode(..)+, defaultConfig+, getConfig+, configAddFilter+, configQuickCheckArgs+) where++import Control.Applicative+import System.IO+import System.Exit+import qualified Test.QuickCheck as QC++import Test.Hspec.Core.Util+import Test.Hspec.Options+import Test.Hspec.FailureReport+import Test.Hspec.Core.QuickCheckUtil (mkGen)++-- | Add a filter predicate to config. If there is already a filter predicate,+-- then combine them with `||`.+configAddFilter :: (Path -> Bool) -> Config -> Config+configAddFilter p1 c = c {+ configFilterPredicate = Just p1 `filterOr` configFilterPredicate c+ }++mkConfig :: Maybe FailureReport -> Config -> Config+mkConfig mFailureReport opts = opts {+ configFilterPredicate = matchFilter `filterOr` rerunFilter+ , configQuickCheckSeed = mSeed+ , configQuickCheckMaxSuccess = mMaxSuccess+ , configQuickCheckMaxDiscardRatio = mMaxDiscardRatio+ , configQuickCheckMaxSize = mMaxSize+ }+ where++ mSeed = configQuickCheckSeed opts <|> (failureReportSeed <$> mFailureReport)+ mMaxSuccess = configQuickCheckMaxSuccess opts <|> (failureReportMaxSuccess <$> mFailureReport)+ mMaxSize = configQuickCheckMaxSize opts <|> (failureReportMaxSize <$> mFailureReport)+ mMaxDiscardRatio = configQuickCheckMaxDiscardRatio opts <|> (failureReportMaxDiscardRatio <$> mFailureReport)++ matchFilter = configFilterPredicate opts++ rerunFilter = flip elem . failureReportPaths <$> mFailureReport++configQuickCheckArgs :: Config -> QC.Args+configQuickCheckArgs c = qcArgs+ where+ qcArgs = (+ maybe id setSeed (configQuickCheckSeed c)+ . maybe id setMaxDiscardRatio (configQuickCheckMaxDiscardRatio c)+ . maybe id setMaxSize (configQuickCheckMaxSize c)+ . maybe id setMaxSuccess (configQuickCheckMaxSuccess c)) QC.stdArgs++ setMaxSuccess :: Int -> QC.Args -> QC.Args+ setMaxSuccess n args = args {QC.maxSuccess = n}++ setMaxSize :: Int -> QC.Args -> QC.Args+ setMaxSize n args = args {QC.maxSize = n}++ setMaxDiscardRatio :: Int -> QC.Args -> QC.Args+ setMaxDiscardRatio n args = args {QC.maxDiscardRatio = n}++ setSeed :: Integer -> QC.Args -> QC.Args+ setSeed n args = args {QC.replay = Just (mkGen (fromIntegral n), 0)}++getConfig :: Config -> String -> [String] -> IO Config+getConfig opts_ prog args = do+ case parseOptions opts_ prog args of+ Left (err, msg) -> exitWithMessage err msg+ Right opts -> do+ r <- if configRerun opts then readFailureReport else return Nothing+ return (mkConfig r opts)++exitWithMessage :: ExitCode -> String -> IO a+exitWithMessage err msg = do+ hPutStr h msg+ exitWith err+ where+ h = case err of+ ExitSuccess -> stdout+ _ -> stderr
+ src/Test/Hspec/Core/Example.hs view
@@ -0,0 +1,121 @@+{-# LANGUAGE CPP, TypeFamilies, FlexibleInstances, TypeSynonymInstances, DeriveDataTypeable #-}+module Test.Hspec.Core.Example (+ Example (..)+, Params (..)+, ActionWith+, Progress+, ProgressCallback+, Result (..)+) where++import Data.Maybe (fromMaybe)+import Data.List (isPrefixOf)+import Test.HUnit.Lang (HUnitFailure(..))+import qualified Control.Exception as E+import Data.Typeable (Typeable)+import qualified Test.QuickCheck as QC+import Test.Hspec.Expectations (Expectation)++import qualified Test.QuickCheck.State as QC+import qualified Test.QuickCheck.Property as QCP+import qualified Test.QuickCheck.IO ()++import Test.Hspec.Core.QuickCheckUtil+import Test.Hspec.Core.Util+import Test.Hspec.Compat++-- | A type class for examples+class Example e where+ type Arg e+#if __GLASGOW_HASKELL__ >= 704+ type Arg e = ()+#endif+ evaluateExample :: e -> Params -> (ActionWith (Arg e) -> IO ()) -> ProgressCallback -> IO Result++data Params = Params {+ paramsQuickCheckArgs :: QC.Args+, paramsSmallCheckDepth :: Int+} deriving (Show)++type Progress = (Int, Int)+type ProgressCallback = Progress -> IO ()++-- | An `IO` action that expects an argument of type @a@+type ActionWith a = a -> IO ()++-- | The result of running an example+data Result = Success | Pending (Maybe String) | Fail String+ deriving (Eq, Show, Read, Typeable)++instance E.Exception Result++instance Example Bool where+ type Arg Bool = ()+ evaluateExample b _ _ _ = if b then return Success else return (Fail "")++instance Example Expectation where+ type Arg Expectation = ()+ evaluateExample e = evaluateExample (\() -> e)++instance Example (a -> Expectation) where+ type Arg (a -> Expectation) = a+ evaluateExample e _ action _ = (action e >> return Success) `E.catches` [+ E.Handler (\(HUnitFailure err) -> return (Fail err))+ , E.Handler (return :: Result -> IO Result)+ ]++instance Example Result where+ type Arg Result = ()+ evaluateExample r _ _ _ = return r++instance Example QC.Property where+ type Arg QC.Property = ()+ evaluateExample e = evaluateExample (\() -> e)++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)+ return $+ case r of+ QC.Success {} -> Success+ QC.Failure {QC.output = m} -> fromMaybe (Fail $ sanitizeFailureMessage r) (parsePending m)+ QC.GaveUp {QC.numTests = n} -> Fail ("Gave up after " ++ pluralize n "test" )+ QC.NoExpectedFailure {} -> Fail ("No expected failure")+ where+ qcProgressCallback = QCP.PostTest QCP.NotCounterexample $+ \st _ -> progressCallback (QC.numSuccessTests st, QC.maxSuccessTests st)++ sanitizeFailureMessage :: QC.Result -> String+ sanitizeFailureMessage r = let m = QC.output r in strip $+#if MIN_VERSION_QuickCheck(2,7,0)+ case QC.theException r of+ Just e -> let numbers = formatNumbers r in+ "uncaught exception: " ++ formatException e ++ " " ++ numbers ++ "\n" ++ case lines m of+ x:xs | x == (exceptionPrefix ++ show e ++ "' " ++ numbers ++ ": ") -> unlines xs+ _ -> m+ Nothing ->+#endif+ (addFalsifiable . stripFailed) m++ addFalsifiable :: String -> String+ addFalsifiable m+ | "(after " `isPrefixOf` m = "Falsifiable " ++ m+ | otherwise = m++ stripFailed :: String -> String+ stripFailed m+ | prefix `isPrefixOf` m = drop n m+ | otherwise = m+ where+ prefix = "*** Failed! "+ n = length prefix++ parsePending :: String -> Maybe Result+ parsePending m+ | exceptionPrefix `isPrefixOf` m = (readMaybe . takeWhile (/= '\'') . drop n) m+ | otherwise = Nothing+ where+ n = length exceptionPrefix++ exceptionPrefix = "*** Failed! Exception: '"
+ src/Test/Hspec/Core/Formatters.hs view
@@ -0,0 +1,216 @@+-- |+-- Stability: experimental+--+-- This module contains formatters that can be used with+-- `Test.Hspec.Runner.hspecWith`.+module Test.Hspec.Core.Formatters (++-- * Formatters+ silent+, specdoc+, progress+, failed_examples++-- * Implementing a custom Formatter+-- |+-- A formatter is a set of actions. Each action is evaluated when a certain+-- situation is encountered during a test run.+--+-- Actions live in the `FormatM` monad. It provides access to the runner state+-- and primitives for appending to the generated report.+, Formatter (..)+, FormatM++-- ** Accessing the runner state+, getSuccessCount+, getPendingCount+, getFailCount+, getTotalCount++, FailureRecord (..)+, getFailMessages+, usedSeed++, getCPUTime+, getRealTime++-- ** Appending to the gerenated report+, write+, writeLine+, newParagraph++-- ** Dealing with colors+, withInfoColor+, withSuccessColor+, withPendingColor+, withFailColor++-- ** Helpers+, formatException+) where++import Data.Maybe+import Test.Hspec.Core.Util+import Test.Hspec.Core.Spec (Location(..), LocationAccuracy(..))+import Text.Printf+import Control.Monad (when, unless)+import Data.Foldable (forM_)+import Control.Applicative+import System.IO (hPutStr, hFlush)++-- We use an explicit import list for "Test.Hspec.Formatters.Internal", to make+-- sure, that we only use the public API to implement formatters.+--+-- Everything imported here has to be re-exported, so that users can implement+-- their own formatters.+import Test.Hspec.Core.Formatters.Internal (+ Formatter (..)+ , FormatM++ , getSuccessCount+ , getPendingCount+ , getFailCount+ , getTotalCount++ , FailureRecord (..)+ , getFailMessages+ , usedSeed++ , getCPUTime+ , getRealTime++ , write+ , writeLine+ , newParagraph++ , withInfoColor+ , withSuccessColor+ , withPendingColor+ , withFailColor+ )++silent :: Formatter+silent = Formatter {+ headerFormatter = return ()+, exampleGroupStarted = \_ _ -> return ()+, exampleGroupDone = return ()+, exampleProgress = \_ _ _ -> return ()+, exampleSucceeded = \_ -> return ()+, exampleFailed = \_ _ -> return ()+, examplePending = \_ _ -> return ()+, failedFormatter = return ()+, footerFormatter = return ()+}+++specdoc :: Formatter+specdoc = silent {++ headerFormatter = do+ writeLine ""++, exampleGroupStarted = \nesting name -> do+ writeLine (indentationFor nesting ++ name)++, exampleProgress = \h _ p -> do+ hPutStr h (formatProgress p)+ hFlush h++, exampleSucceeded = \(nesting, requirement) -> withSuccessColor $ do+ writeLine $ indentationFor nesting ++ requirement++, exampleFailed = \(nesting, requirement) _ -> withFailColor $ do+ n <- getFailCount+ writeLine $ indentationFor nesting ++ requirement ++ " FAILED [" ++ show n ++ "]"++, examplePending = \(nesting, requirement) reason -> withPendingColor $ do+ writeLine $ indentationFor nesting ++ requirement ++ "\n # PENDING: " ++ fromMaybe "No reason given" reason++, failedFormatter = defaultFailedFormatter++, footerFormatter = defaultFooter+} where+ indentationFor nesting = replicate (length nesting * 2) ' '+ formatProgress (current, total)+ | total == 0 = show current ++ "\r"+ | otherwise = show current ++ "/" ++ show total ++ "\r"+++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++ forM_ (zip [1..] failures) $ \x -> do+ formatFailure x+ writeLine ""++ when (hasBestEffortLocations failures) $ do+ withInfoColor $ writeLine "Source locations marked with \"best-effort\" are calculated heuristically and may be incorrect."+ writeLine ""++ unless (null failures) $ do+ write "Randomized with seed " >> usedSeed >>= writeLine . show+ writeLine ""+ where+ hasBestEffortLocations :: [FailureRecord] -> Bool+ hasBestEffortLocations = any p+ where+ p :: FailureRecord -> Bool+ p failure = (locationAccuracy <$> failureRecordLocation failure) == Just BestEffort++ formatFailure :: (Int, FailureRecord) -> FormatM ()+ formatFailure (n, FailureRecord mLoc path reason) = do+ write (show n ++ ") ")+ writeLine (formatRequirement path)+ withFailColor $ do+ unless (null err) $ do+ writeLine err+ forM_ mLoc $ \loc -> do+ writeLine ""+ withInfoColor $ writeLine (formatLoc loc)+ where+ err = either (("uncaught exception: " ++) . formatException) id reason+ formatLoc (Location file line _column accuracy) = "# " ++ file ++ ":" ++ show line ++ bestEffortMarking+ where+ bestEffortMarking = case accuracy of+ ExactLocation -> ""+ BestEffort -> " (best-effort)"++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 c | fails /= 0 = withFailColor+ | pending /= 0 = withPendingColor+ | otherwise = withSuccessColor+ c $ do+ write $ pluralize total "example"+ write (", " ++ pluralize fails "failure")+ unless (pending == 0) $+ write (", " ++ show pending ++ " pending")+ writeLine ""
+ src/Test/Hspec/Core/Formatters/Internal.hs view
@@ -0,0 +1,262 @@+{-# LANGUAGE GeneralizedNewtypeDeriving #-}+module Test.Hspec.Core.Formatters.Internal (++-- * Public API+ Formatter (..)+, FormatM++, getSuccessCount+, getPendingCount+, getFailCount+, getTotalCount++, FailureRecord (..)+, getFailMessages+, usedSeed++, getCPUTime+, getRealTime++, write+, writeLine+, newParagraph++, withInfoColor+, withSuccessColor+, withPendingColor+, withFailColor++-- * Functions for internal use+, runFormatM+, increaseSuccessCount+, increasePendingCount+, increaseFailCount+, addFailMessage+, finally_+) where++import qualified System.IO as IO+import System.IO (Handle)+import Control.Monad+import Control.Applicative+import Control.Exception (SomeException, AsyncException(..), bracket_, try, throwIO)+import System.Console.ANSI+import Control.Monad.Trans.State hiding (gets, modify)+import Control.Monad.IO.Class+import qualified System.CPUTime as CPUTime+import Data.Time.Clock.POSIX (POSIXTime, getPOSIXTime)++import Test.Hspec.Core.Util (Path)+import Test.Hspec.Compat+import Test.Hspec.Core.Spec (Progress, Location)++-- | A lifted version of `Control.Monad.Trans.State.gets`+gets :: (FormatterState -> a) -> FormatM a+gets f = FormatM $ do+ f <$> (get >>= liftIO . readIORef)++-- | A lifted version of `Control.Monad.Trans.State.modify`+modify :: (FormatterState -> FormatterState) -> FormatM ()+modify f = FormatM $ do+ get >>= liftIO . (`modifyIORef'` f)++data FormatterState = FormatterState {+ stateHandle :: Handle+, stateUseColor :: Bool+, produceHTML :: Bool+, successCount :: Int+, pendingCount :: Int+, failCount :: Int+, failMessages :: [FailureRecord]+, stateUsedSeed :: Integer+, cpuStartTime :: Maybe Integer+, startTime :: POSIXTime+}++-- | The random seed that is used for QuickCheck.+usedSeed :: FormatM Integer+usedSeed = gets stateUsedSeed++-- | The total number of examples encountered so far.+totalCount :: FormatterState -> Int+totalCount s = successCount s + pendingCount s + failCount s++-- NOTE: We use an IORef here, so that the state persists when UserInterrupt is+-- thrown.+newtype FormatM a = FormatM (StateT (IORef FormatterState) IO a)+ deriving (Functor, Applicative, Monad, MonadIO)++runFormatM :: Bool -> Bool -> Bool -> Integer -> Handle -> FormatM a -> IO a+runFormatM useColor produceHTML_ printCpuTime seed handle (FormatM action) = do+ time <- getPOSIXTime+ cpuTime <- if printCpuTime then Just <$> CPUTime.getCPUTime else pure Nothing+ st <- newIORef (FormatterState handle useColor produceHTML_ 0 0 0 [] seed cpuTime time)+ evalStateT action st++-- | Increase the counter for successful examples+increaseSuccessCount :: FormatM ()+increaseSuccessCount = modify $ \s -> s {successCount = succ $ successCount s}++-- | Increase the counter for pending examples+increasePendingCount :: FormatM ()+increasePendingCount = modify $ \s -> s {pendingCount = succ $ pendingCount s}++-- | Increase the counter for failed examples+increaseFailCount :: FormatM ()+increaseFailCount = modify $ \s -> s {failCount = succ $ failCount s}++-- | Get the number of successful examples encountered so far.+getSuccessCount :: FormatM Int+getSuccessCount = gets successCount++-- | Get the number of pending examples encountered so far.+getPendingCount :: FormatM Int+getPendingCount = gets pendingCount++-- | Get the number of failed examples encountered so far.+getFailCount :: FormatM Int+getFailCount = gets failCount++-- | Get the total number of examples encountered so far.+getTotalCount :: FormatM Int+getTotalCount = gets totalCount++-- | Append to the list of accumulated failure messages.+addFailMessage :: Maybe Location -> Path -> Either SomeException String -> FormatM ()+addFailMessage loc p m = modify $ \s -> s {failMessages = FailureRecord loc p m : failMessages s}++-- | Get the list of accumulated failure messages.+getFailMessages :: FormatM [FailureRecord]+getFailMessages = reverse `fmap` gets failMessages++data FailureRecord = FailureRecord {+ failureRecordLocation :: Maybe Location+, failureRecordPath :: Path+, failureRecordMessage :: Either SomeException String+}++data Formatter = Formatter {++ headerFormatter :: FormatM ()++-- | evaluated before each test group+--+-- The given number indicates the position within the parent group.+, exampleGroupStarted :: [String] -> String -> FormatM ()++, exampleGroupDone :: FormatM ()++-- | used to notify the progress of the currently evaluated example+--+-- /Note/: This is only called when interactive/color mode.+, exampleProgress :: Handle -> Path -> Progress -> IO ()++-- | evaluated after each successful example+, exampleSucceeded :: Path -> FormatM ()++-- | evaluated after each failed example+, exampleFailed :: Path -> Either SomeException String -> FormatM ()++-- | evaluated after each pending example+, examplePending :: Path -> Maybe String -> FormatM ()++-- | evaluated after a test run+, failedFormatter :: FormatM ()++-- | evaluated after `failuresFormatter`+, footerFormatter :: FormatM ()+}+++-- | Append an empty line to the report.+--+-- Calling this multiple times has the same effect as calling it once.+newParagraph :: FormatM ()+newParagraph = writeLine ""+{-# DEPRECATED newParagraph "use @writeLine \"\"@ instead" #-}++-- | Append some output to the report.+write :: String -> FormatM ()+write s = do+ h <- gets stateHandle+ liftIO $ IO.hPutStr h s++-- | The same as `write`, but adds a newline character.+writeLine :: String -> FormatM ()+writeLine s = write s >> write "\n"++-- | Set output color to red, run given action, and finally restore the default+-- color.+withFailColor :: FormatM a -> FormatM a+withFailColor = withColor (SetColor Foreground Dull Red) "hspec-failure"++-- | Set output color to green, run given action, and finally restore the+-- default color.+withSuccessColor :: FormatM a -> FormatM a+withSuccessColor = withColor (SetColor Foreground Dull Green) "hspec-success"++-- | Set output color to yellow, run given action, and finally restore the+-- default color.+withPendingColor :: FormatM a -> FormatM a+withPendingColor = withColor (SetColor Foreground Dull Yellow) "hspec-pending"++-- | Set output color to cyan, run given action, and finally restore the+-- default color.+withInfoColor :: FormatM a -> FormatM a+withInfoColor = withColor (SetColor Foreground Dull Cyan) "hspec-info"++-- | Set a color, run an action, and finally reset colors.+withColor :: SGR -> String -> FormatM a -> FormatM a+withColor color cls action = do+ r <- gets produceHTML+ (if r then htmlSpan cls else withColor_ color) action++htmlSpan :: String -> FormatM a -> FormatM a+htmlSpan cls action = write ("<span class=\"" ++ cls ++ "\">") *> action <* write "</span>"++withColor_ :: SGR -> FormatM a -> FormatM a+withColor_ color (FormatM action) = do+ useColor <- gets stateUseColor+ h <- gets stateHandle++ FormatM . StateT $ \st -> do+ bracket_++ -- set color+ (when useColor $ hSetSGR h [color])++ -- reset colors+ (when useColor $ hSetSGR h [Reset])++ -- run action+ (runStateT action st)++-- |+-- @finally_ actionA actionB@ runs @actionA@ and then @actionB@. @actionB@ is+-- run even when a `UserInterrupt` occurs during @actionA@.+finally_ :: FormatM () -> FormatM () -> FormatM ()+finally_ (FormatM actionA) (FormatM actionB) = FormatM . StateT $ \st -> do+ r <- try (execStateT actionA st)+ case r of+ Left e -> do+ when (e == UserInterrupt) $+ runStateT actionB st >> return ()+ throwIO e+ Right st_ -> do+ runStateT actionB st_++-- | Get the used CPU time since the test run has been started.+getCPUTime :: FormatM (Maybe Double)+getCPUTime = do+ t1 <- liftIO CPUTime.getCPUTime+ mt0 <- gets cpuStartTime+ return $ toSeconds <$> ((-) <$> pure t1 <*> mt0)+ where+ toSeconds x = fromIntegral x / (10.0 ^ (12 :: Integer))++-- | Get the passed real time since the test run has been started.+getRealTime :: FormatM Double+getRealTime = do+ t1 <- liftIO getPOSIXTime+ t0 <- gets startTime+ return (realToFrac $ t1 - t0)
+ src/Test/Hspec/Core/Hooks.hs view
@@ -0,0 +1,74 @@+-- | Stability: provisional+module Test.Hspec.Core.Hooks (+ before+, beforeWith+, beforeAll+, after+, after_+, afterAll+, afterAll_+, around+, around_+, aroundWith+) where++import Control.Exception (finally)+import Control.Concurrent.MVar++import Test.Hspec.Core.Spec++-- | Run a custom action before every spec item.+before :: IO a -> SpecWith a -> Spec+before action = around (action >>=)++-- | Run a custom action before every spec item.+beforeWith :: (b -> IO a) -> SpecWith a -> SpecWith b+beforeWith action = aroundWith $ \e x -> action x >>= e++-- | Run a custom action before the first spec item.+beforeAll :: IO a -> SpecWith a -> Spec+beforeAll action spec = do+ mvar <- runIO (newMVar Nothing)+ let action_ = memoize mvar action+ before action_ spec++memoize :: MVar (Maybe a) -> IO a -> IO a+memoize mvar action = modifyMVar mvar $ \ma -> case ma of+ Just a -> return (ma, a)+ Nothing -> do+ a <- action+ return (Just a, a)++-- | Run a custom action after every spec item.+after :: ActionWith a -> SpecWith a -> SpecWith a+after action = aroundWith $ \e x -> e x `finally` action x++-- | Run a custom action after every spec item.+after_ :: IO () -> Spec -> Spec+after_ action = after $ \() -> action++-- | Run a custom action before and/or after every spec item.+around :: (ActionWith a -> IO ()) -> SpecWith a -> Spec+around action = aroundWith $ \e () -> action e++-- | Run a custom action after the last spec item.+afterAll :: ActionWith a -> SpecWith a -> SpecWith a+afterAll action spec = runIO (runSpecM spec) >>= fromSpecList . return . NodeWithCleanup action++-- | Run a custom action after the last spec item.+afterAll_ :: IO () -> Spec -> Spec+afterAll_ action = afterAll (\() -> action)++-- | Run a custom action before and/or after every spec item.+around_ :: (IO () -> IO ()) -> Spec -> Spec+around_ action = around $ action . ($ ())++-- | Run a custom action before and/or after every spec item.+aroundWith :: (ActionWith a -> ActionWith b) -> SpecWith a -> SpecWith b+aroundWith action = mapAround (. action)++mapAround :: ((ActionWith b -> IO ()) -> ActionWith a -> IO ()) -> SpecWith a -> SpecWith b+mapAround f = mapSpecItem (untangle f) $ \i@Item{itemExample = e} -> i{itemExample = (. f) . e}++untangle :: ((ActionWith b -> IO ()) -> ActionWith a -> IO ()) -> ActionWith a -> ActionWith b+untangle f g = \b -> f ($ b) g
+ src/Test/Hspec/Core/QuickCheck.hs view
@@ -0,0 +1,37 @@+-- | Stability: provisional+module Test.Hspec.Core.QuickCheck (+ modifyMaxSuccess+, modifyMaxDiscardRatio+, modifyMaxSize++) where++import Test.QuickCheck+import Test.Hspec.Core.Spec++-- | Use a modified `maxSuccess` for given spec.+modifyMaxSuccess :: (Int -> Int) -> SpecWith a -> SpecWith a+modifyMaxSuccess = modifyArgs . modify+ where+ modify :: (Int -> Int) -> Args -> Args+ modify f args = args {maxSuccess = f (maxSuccess args)}++-- | Use a modified `maxDiscardRatio` for given spec.+modifyMaxDiscardRatio :: (Int -> Int) -> SpecWith a -> SpecWith a+modifyMaxDiscardRatio = modifyArgs . modify+ where+ modify :: (Int -> Int) -> Args -> Args+ modify f args = args {maxDiscardRatio = f (maxDiscardRatio args)}++-- | Use a modified `maxSize` for given spec.+modifyMaxSize :: (Int -> Int) -> SpecWith a -> SpecWith a+modifyMaxSize = modifyArgs . modify+ where+ modify :: (Int -> Int) -> Args -> Args+ modify f args = args {maxSize = f (maxSize args)}++modifyArgs :: (Args -> Args) -> SpecWith a -> SpecWith a+modifyArgs = modifyParams . modify+ where+ modify :: (Args -> Args) -> Params -> Params+ modify f p = p {paramsQuickCheckArgs = f (paramsQuickCheckArgs p)}
+ src/Test/Hspec/Core/QuickCheckUtil.hs view
@@ -0,0 +1,60 @@+{-# LANGUAGE CPP #-}+module Test.Hspec.Core.QuickCheckUtil where++import Control.Applicative+import Data.Int+import Data.IORef+import Test.QuickCheck hiding (Result(..))+import Test.QuickCheck as QC+import Test.QuickCheck.Property hiding (Result(..))+import Test.QuickCheck.Gen+import qualified Test.QuickCheck.Property as QCP+import Test.QuickCheck.IO ()+++#if MIN_VERSION_QuickCheck(2,7,0)+import Test.QuickCheck.Random+#endif++import System.Random++import Test.Hspec.Core.Util++aroundProperty :: ((a -> IO ()) -> IO ()) -> (a -> Property) -> Property+#if MIN_VERSION_QuickCheck(2,7,0)+aroundProperty action p = MkProperty . MkGen $ \r n -> aroundProp action $ \a -> (unGen . unProperty $ p a) r n+#else+aroundProperty action p = MkGen $ \r n -> aroundProp action $ \a -> (unGen $ p a) r n+#endif++aroundProp :: ((a -> IO ()) -> IO ()) -> (a -> Prop) -> Prop+aroundProp action p = MkProp $ aroundRose action (\a -> unProp $ p a)++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++formatNumbers :: Result -> String+formatNumbers r = "(after " ++ pluralize (numTests r) "test" ++ shrinks ++ ")"+ where+ shrinks+ | 0 < numShrinks r = " and " ++ pluralize (numShrinks r) "shrink"+ | otherwise = ""++newSeed :: IO Int+newSeed = fst . randomR (0, fromIntegral (maxBound :: Int32)) <$>+#if MIN_VERSION_QuickCheck(2,7,0)+ newQCGen+#else+ newStdGen+#endif++#if MIN_VERSION_QuickCheck(2,7,0)+mkGen :: Int -> QCGen+mkGen = mkQCGen+#else+mkGen :: Int -> StdGen+mkGen = mkStdGen+#endif
+ src/Test/Hspec/Core/Runner.hs view
@@ -0,0 +1,174 @@+-- |+-- Stability: provisional+module Test.Hspec.Core.Runner (+-- * Running a spec+ hspec+, hspecWith+, hspecResult+, hspecWithResult++-- * Types+, Summary (..)+, Config (..)+, ColorMode (..)+, Path+, defaultConfig+, configAddFilter+) where++import Control.Monad+import Control.Applicative+import Data.Monoid+import Data.Maybe+import System.IO+import System.Environment (getProgName, getArgs, withArgs)+import System.Exit+import qualified Control.Exception as E++import System.Console.ANSI (hHideCursor, hShowCursor)+import qualified Test.QuickCheck as QC+import Control.Monad.IO.Class (liftIO)++import Test.Hspec.Compat (lookupEnv)+import Test.Hspec.Core.Util (Path)+import Test.Hspec.Core.Spec+import Test.Hspec.Config+import Test.Hspec.Core.Formatters+import Test.Hspec.Core.Formatters.Internal+import Test.Hspec.FailureReport+import Test.Hspec.Core.QuickCheckUtil++import Test.Hspec.Core.Runner.Eval++-- | Filter specs by given predicate.+--+-- The predicate takes a list of "describe" labels and a "requirement".+filterSpecs :: Config -> [SpecTree a] -> [SpecTree a]+filterSpecs c = go []+ where+ p :: Path -> Bool+ p = fromMaybe (const True) (configFilterPredicate c)++ go :: [String] -> [SpecTree a] -> [SpecTree a]+ go groups = mapMaybe (goSpec groups)++ goSpecs :: [String] -> [SpecTree a] -> ([SpecTree a] -> b) -> Maybe b+ goSpecs groups specs ctor = case go groups specs of+ [] -> Nothing+ xs -> Just (ctor xs)++ goSpec :: [String] -> SpecTree a -> Maybe (SpecTree a)+ goSpec groups spec = case spec of+ Leaf item -> guard (p (groups, itemRequirement item)) >> return spec+ Node group specs -> goSpecs (groups ++ [group]) specs (Node group)+ NodeWithCleanup action specs -> goSpecs groups specs (NodeWithCleanup action)++applyDryRun :: Config -> [SpecTree ()] -> [SpecTree ()]+applyDryRun c+ | configDryRun c = map (removeCleanup . fmap markSuccess)+ | otherwise = id+ where+ markSuccess :: Item () -> Item ()+ markSuccess item = item {itemExample = evaluateExample Success}++ removeCleanup :: SpecTree () -> SpecTree ()+ removeCleanup spec = case spec of+ Node x xs -> Node x (map removeCleanup xs)+ NodeWithCleanup _ xs -> NodeWithCleanup (\() -> return ()) (map removeCleanup xs)+ leaf@(Leaf _) -> leaf++-- | Run given spec and write a report to `stdout`.+-- Exit with `exitFailure` if at least one spec item fails.+hspec :: Spec -> IO ()+hspec = hspecWith defaultConfig++-- Add a seed to given config if there is none. That way the same seed is used+-- for all properties. This helps with --seed and --rerun.+ensureSeed :: Config -> IO Config+ensureSeed c = case configQuickCheckSeed c of+ Nothing -> do+ seed <- newSeed+ return c {configQuickCheckSeed = Just (fromIntegral seed)}+ _ -> return c++-- | Run given spec with custom options.+-- This is similar to `hspec`, but more flexible.+hspecWith :: Config -> Spec -> IO ()+hspecWith conf spec = do+ r <- hspecWithResult conf spec+ unless (summaryFailures r == 0) exitFailure++-- | Run given spec and returns a summary of the test run.+--+-- /Note/: `hspecResult` does not exit with `exitFailure` on failing spec+-- items. If you need this, you have to check the `Summary` yourself and act+-- accordingly.+hspecResult :: Spec -> IO Summary+hspecResult = hspecWithResult defaultConfig++-- | Run given spec with custom options and returns a summary of the test run.+--+-- /Note/: `hspecWithResult` does not exit with `exitFailure` on failing spec+-- items. If you need this, you have to check the `Summary` yourself and act+-- accordingly.+hspecWithResult :: Config -> Spec -> IO Summary+hspecWithResult conf spec = do+ prog <- getProgName+ args <- getArgs+ c <- getConfig conf prog args >>= ensureSeed+ withArgs [] {- do not leak command-line arguments to examples -} $ withHandle c $ \h -> do+ let formatter = fromMaybe specdoc (configFormatter c)+ seed = (fromJust . configQuickCheckSeed) c+ qcArgs = configQuickCheckArgs c++ useColor <- doesUseColor h c++ filteredSpec <- filterSpecs c . applyDryRun c <$> runSpecM spec++ withHiddenCursor useColor h $+ runFormatM useColor (configHtmlOutput c) (configPrintCpuTime c) seed h $ do+ runFormatter useColor h c formatter filteredSpec `finally_` do+ failedFormatter formatter++ footerFormatter formatter++ -- dump failure report+ xs <- map failureRecordPath <$> getFailMessages+ liftIO $ writeFailureReport FailureReport {+ failureReportSeed = seed+ , failureReportMaxSuccess = QC.maxSuccess qcArgs+ , failureReportMaxSize = QC.maxSize qcArgs+ , failureReportMaxDiscardRatio = QC.maxDiscardRatio qcArgs+ , failureReportPaths = xs+ }++ Summary <$> getTotalCount <*> getFailCount+ where+ withHiddenCursor :: Bool -> Handle -> IO a -> IO a+ withHiddenCursor useColor h+ | useColor = E.bracket_ (hHideCursor h) (hShowCursor h)+ | otherwise = id++ doesUseColor :: Handle -> Config -> IO Bool+ doesUseColor h c = case configColorMode c of+ ColorAuto -> (&&) <$> hIsTerminalDevice h <*> (not <$> isDumb)+ ColorNever -> return False+ ColorAlways -> return True++ withHandle :: Config -> (Handle -> IO a) -> IO a+ withHandle c action = case configOutputFile c of+ Left h -> action h+ Right path -> withFile path WriteMode action++isDumb :: IO Bool+isDumb = maybe False (== "dumb") <$> lookupEnv "TERM"++-- | Summary of a test run.+data Summary = Summary {+ summaryExamples :: Int+, summaryFailures :: Int+} deriving (Eq, Show)++instance Monoid Summary where+ mempty = Summary 0 0+ (Summary x1 x2) `mappend` (Summary y1 y2) = Summary (x1 + y1) (x2 + y2)
+ src/Test/Hspec/Core/Runner/Eval.hs view
@@ -0,0 +1,157 @@+module Test.Hspec.Core.Runner.Eval (runFormatter) where++import Control.Applicative+import Control.Monad+import qualified Control.Exception as E+import Control.Concurrent+import System.IO (Handle)++import Control.Monad.IO.Class (liftIO)+import Control.DeepSeq (deepseq)+import Data.Time.Clock.POSIX++import Test.Hspec.Core.Util+import Test.Hspec.Core.Spec+import Test.Hspec.Config+import Test.Hspec.Core.Formatters+import Test.Hspec.Core.Formatters.Internal+import Test.Hspec.Timer++type EvalTree = Tree (ActionWith ()) (String, Maybe Location, ProgressCallback -> FormatResult -> IO (FormatM ()))++-- | Evaluate all examples of a given spec and produce a report.+runFormatter :: Bool -> Handle -> Config -> Formatter -> [SpecTree ()] -> FormatM ()+runFormatter useColor h c formatter specs = do+ headerFormatter formatter+ chan <- liftIO newChan+ reportProgress <- liftIO mkReportProgress+ run chan reportProgress c formatter (toEvalTree specs)+ where+ mkReportProgress :: IO (Path -> Progress -> IO ())+ mkReportProgress+ | useColor = every 0.05 $ exampleProgress formatter h+ | otherwise = return $ \_ _ -> return ()++ toEvalTree :: [SpecTree ()] -> [EvalTree]+ toEvalTree = map (fmap f)+ where+ f :: Item () -> (String, Maybe Location, ProgressCallback -> FormatResult -> IO (FormatM ()))+ f (Item requirement loc isParallelizable e) = (requirement, loc, parallelize isParallelizable $ e params ($ ()))++ params :: Params+ params = Params (configQuickCheckArgs c) (configSmallCheckDepth c)++-- | Execute given action at most every specified number of seconds.+every :: POSIXTime -> (a -> b -> IO ()) -> IO (a -> b -> IO ())+every seconds action = do+ timer <- newTimer seconds+ return $ \a b -> do+ r <- timer+ when r (action a b)++type FormatResult = Either E.SomeException Result -> FormatM ()++parallelize :: Bool -> (ProgressCallback -> IO Result) -> ProgressCallback -> FormatResult -> IO (FormatM ())+parallelize isParallelizable e+ | isParallelizable = runParallel e+ | otherwise = runSequentially e++runSequentially :: (ProgressCallback -> IO Result) -> ProgressCallback -> FormatResult -> IO (FormatM ())+runSequentially e reportProgress formatResult = return $ do+ result <- liftIO $ evalExample (e reportProgress)+ formatResult result++data Report = ReportProgress Progress | ReportResult (Either E.SomeException Result)++runParallel :: (ProgressCallback -> IO Result) -> ProgressCallback -> FormatResult -> IO (FormatM ())+runParallel e reportProgress formatResult = do+ mvar <- newEmptyMVar+ _ <- forkIO $ do+ let progressCallback = replaceMVar mvar . ReportProgress+ result <- evalExample (e progressCallback)+ replaceMVar mvar (ReportResult result)+ return $ evalReport mvar+ where+ evalReport :: MVar Report -> FormatM ()+ evalReport mvar = do+ r <- liftIO (takeMVar mvar)+ case r of+ ReportProgress p -> do+ liftIO $ reportProgress p+ evalReport mvar+ ReportResult result -> formatResult result++ replaceMVar :: MVar a -> a -> IO ()+ replaceMVar mvar p = tryTakeMVar mvar >> putMVar mvar p++evalExample :: IO Result -> IO (Either E.SomeException Result)+evalExample e = safeTry $ forceResult <$> e+ where+ forceResult :: Result -> Result+ forceResult r = case r of+ Success -> r+ Pending m -> m `deepseq` r+ Fail m -> m `deepseq` r++data Message = Done | Run (FormatM ())++run :: Chan Message -> (Path -> ProgressCallback) -> Config -> Formatter -> [EvalTree] -> FormatM ()+run chan reportProgress_ c formatter specs = do+ liftIO $ do+ forM_ specs (queueSpec [])+ writeChan chan Done+ processMessages (readChan chan) (configFastFail c)+ where+ defer :: FormatM () -> IO ()+ defer = writeChan chan . Run++ runCleanup :: IO () -> Path -> FormatM ()+ runCleanup action path = do+ r <- liftIO $ safeTry action+ either (failed Nothing path . Left) return r++ queueSpec :: [String] -> EvalTree -> IO ()+ queueSpec rGroups (Node group xs) = do+ defer (exampleGroupStarted formatter (reverse rGroups) group)+ forM_ xs (queueSpec (group : rGroups))+ defer (exampleGroupDone formatter)+ queueSpec rGroups (NodeWithCleanup action xs) = do+ forM_ xs (queueSpec rGroups)+ defer (runCleanup (action ()) (reverse rGroups, "afterAll-hook"))+ queueSpec rGroups (Leaf e) =+ queueExample (reverse rGroups) e++ queueExample :: [String] -> (String, Maybe Location, ProgressCallback -> FormatResult -> IO (FormatM ())) -> IO ()+ queueExample groups (requirement, loc, e) = e reportProgress formatResult >>= defer+ where+ path :: Path+ path = (groups, requirement)++ reportProgress = reportProgress_ path++ formatResult :: Either E.SomeException Result -> FormatM ()+ formatResult result = do+ case result of+ Right Success -> do+ increaseSuccessCount+ exampleSucceeded formatter path+ Right (Pending reason) -> do+ increasePendingCount+ examplePending formatter path reason+ Right (Fail err) -> failed loc path (Right err)+ Left err -> failed loc path (Left err)++ failed loc path err = do+ increaseFailCount+ addFailMessage loc path err+ exampleFailed formatter path err++processMessages :: IO Message -> Bool -> FormatM ()+processMessages getMessage fastFail = go+ where+ go = liftIO getMessage >>= \m -> case m of+ Run action -> do+ action+ fails <- getFailCount+ unless (fastFail && fails /= 0) go+ Done -> return ()
+ src/Test/Hspec/Core/Spec.hs view
@@ -0,0 +1,70 @@+-- |+-- Stability: unstable+--+-- This module provides access to Hspec's internals. It is less stable than+-- other parts of the API. For most users @Test.Hspec@ is more suitable!+module Test.Hspec.Core.Spec (++-- * Defining a spec+ describe+, it+, pending+, pendingWith+, parallel++-- * The @SpecM@ monad+, module Test.Hspec.Core.Spec.Monad++-- * A type class for examples+, module Test.Hspec.Core.Example++-- * Internal representation of a spec tree+, module Test.Hspec.Core.Tree+) where++import qualified Control.Exception as E+import Test.Hspec.Expectations (Expectation)++import Test.Hspec.Core.Example+import Test.Hspec.Core.Tree+import Test.Hspec.Core.Spec.Monad++-- | The @describe@ function combines a list of specs into a larger spec.+describe :: String -> SpecWith a -> SpecWith a+describe label spec = runIO (runSpecM spec) >>= fromSpecList . return . specGroup label++-- | The @it@ function creates a spec item.+--+-- A spec item consists of:+--+-- * a textual description of a desired behavior+--+-- * an example for that behavior+--+-- > describe "absolute" $ do+-- > it "returns a positive number when given a negative number" $+-- > absolute (-1) == 1+it :: Example a => String -> a -> SpecWith (Arg a)+it label action = fromSpecList [specItem label action]++-- | `parallel` marks all spec items of the given spec to be safe for parallel+-- evaluation.+parallel :: SpecWith a -> SpecWith a+parallel = mapSpecItem_ $ \item -> item {itemIsParallelizable = True}++-- | `pending` can be used to indicate that an example is /pending/.+--+-- If you want to textually specify a behavior but do not have an example yet,+-- use this:+--+-- > describe "fancyFormatter" $ do+-- > it "can format text in a way that everyone likes" $+-- > pending+pending :: Expectation+pending = E.throwIO (Pending Nothing)++-- |+-- `pendingWith` is similar to `pending`, but it takes an additional string+-- argument that can be used to specify the reason for why it's pending.+pendingWith :: String -> Expectation+pendingWith = E.throwIO . Pending . Just
+ src/Test/Hspec/Core/Spec/Monad.hs view
@@ -0,0 +1,65 @@+{-# LANGUAGE GeneralizedNewtypeDeriving #-}+module Test.Hspec.Core.Spec.Monad (+ Spec+, SpecWith+, SpecM (..)+, runSpecM+, fromSpecList+, runIO++, mapSpecTree+, mapSpecItem+, mapSpecItem_+, modifyParams+) where++import Control.Applicative+import Control.Monad.Trans.Writer+import Control.Monad.IO.Class (liftIO)++import Test.Hspec.Core.Example+import Test.Hspec.Core.Tree++type Spec = SpecWith ()++type SpecWith a = SpecM a ()++-- | A writer monad for `SpecTree` forests+newtype SpecM a r = SpecM (WriterT [SpecTree a] IO r)+ deriving (Functor, Applicative, Monad)++-- | Convert a `Spec` to a forest of `SpecTree`s.+runSpecM :: SpecWith a -> IO [SpecTree a]+runSpecM (SpecM specs) = execWriterT specs++-- | Create a `Spec` from a forest of `SpecTree`s.+fromSpecList :: [SpecTree a] -> SpecWith a+fromSpecList = SpecM . tell++-- | Run an IO action while constructing the spec tree.+--+-- `SpecM` is a monad to construct a spec tree, without executing any spec+-- items. @runIO@ allows you to run IO actions during this construction phase.+-- The IO action is always run when the spec tree is constructed (e.g. even+-- when @--dry-run@ is specified).+-- If you do not need the result of the IO action to construct the spec tree,+-- `Test.Hspec.Core.Hooks.beforeAll` may be more suitable for your use case.+runIO :: IO r -> SpecM a r+runIO = SpecM . liftIO++mapSpecTree :: (SpecTree a -> SpecTree b) -> SpecWith a -> SpecWith b+mapSpecTree f spec = runIO (runSpecM spec) >>= fromSpecList . map f++mapSpecItem :: (ActionWith a -> ActionWith b) -> (Item a -> Item b) -> SpecWith a -> SpecWith b+mapSpecItem g f = mapSpecTree go+ where+ go spec = case spec of+ Node d xs -> Node d (map go xs)+ NodeWithCleanup cleanup xs -> NodeWithCleanup (g cleanup) (map go xs)+ Leaf item -> Leaf (f item)++mapSpecItem_ :: (Item a -> Item a) -> SpecWith a -> SpecWith a+mapSpecItem_ = mapSpecItem id++modifyParams :: (Params -> Params) -> SpecWith a -> SpecWith a+modifyParams f = mapSpecItem_ $ \item -> item {itemExample = \p -> (itemExample item) (f p)}
+ src/Test/Hspec/Core/Tree.hs view
@@ -0,0 +1,103 @@+{-# LANGUAGE DeriveFunctor #-}+-- |+-- Stability: unstable+module Test.Hspec.Core.Tree (+ SpecTree+, Tree (..)+, Item (..)+, Location (..)+, LocationAccuracy (..)+, specGroup+, specItem+) where++import Control.Applicative+import Data.Foldable+import Data.Traversable+import Data.Monoid++import Test.Hspec.Core.Example++-- | Internal tree data structure+data Tree c a =+ Node String [Tree c a]+ | NodeWithCleanup c [Tree c a]+ | Leaf a+ deriving Functor++instance Foldable (Tree c) where -- Note: GHC 7.0.1 fails to derive this instance+ foldMap = go+ where+ go :: Monoid m => (a -> m) -> Tree c a -> m+ go f t = case t of+ Node _ xs -> foldMap (foldMap f) xs+ NodeWithCleanup _ xs -> foldMap (foldMap f) xs+ Leaf x -> f x++instance Traversable (Tree c) where -- Note: GHC 7.0.1 fails to derive this instance+ sequenceA = go+ where+ go :: Applicative f => Tree c (f a) -> f (Tree c a)+ go t = case t of+ Node label xs -> Node label <$> sequenceA (map go xs)+ NodeWithCleanup action xs -> NodeWithCleanup action <$> sequenceA (map go xs)+ Leaf a -> Leaf <$> a++-- | A tree is used to represent a spec internally. The tree is parametrize+-- over the type of cleanup actions and the type of the actual spec items.+type SpecTree a = Tree (ActionWith a) (Item a)++-- |+-- @Item@ is used to represent spec items internally. A spec item consists of:+--+-- * a textual description of a desired behavior+-- * an example for that behavior+-- * additional meta information+--+-- Everything that is an instance of the `Example` type class can be used as an+-- example, including QuickCheck properties, Hspec expectations and HUnit+-- assertions.+data Item a = Item {+ -- | Textual description of behavior+ itemRequirement :: String+ -- | Source location of the spec item+, itemLocation :: Maybe Location+ -- | A flag that indicates whether it is safe to evaluate this spec item in+ -- parallel with other spec items+, itemIsParallelizable :: Bool+ -- | Example for behavior+, itemExample :: Params -> (ActionWith a -> IO ()) -> ProgressCallback -> IO Result+}++-- | @Location@ is used to represent source locations.+data Location = Location {+ locationFile :: String+, locationLine :: Int+, locationColumn :: Int+, locationAccuracy :: LocationAccuracy+} deriving (Eq, Show)++-- | A marker for source locations+data LocationAccuracy =+ -- | The source location is accurate+ ExactLocation |+ -- | The source location was determined on a best-effort basis and my be+ -- wrong or inaccurate+ BestEffort+ deriving (Eq, Show)++-- | The @specGroup@ function combines a list of specs into a larger spec.+specGroup :: String -> [SpecTree a] -> SpecTree a+specGroup s = Node msg+ where+ msg+ | null s = "(no description given)"+ | otherwise = s++-- | The @specItem@ function creates a spec item.+specItem :: Example a => String -> a -> SpecTree (Arg a)+specItem s e = Leaf $ Item requirement Nothing False (evaluateExample e)+ where+ requirement+ | null s = "(unspecified behavior)"+ | otherwise = s
+ src/Test/Hspec/Core/Util.hs view
@@ -0,0 +1,132 @@+-- | Stability: unstable+module Test.Hspec.Core.Util (+-- * String functions+ pluralize+, strip+, lineBreaksAt++-- * Working with paths+, Path+, formatRequirement+, filterPredicate++-- * Working with exception+, safeTry+, formatException+) where++import Data.List+import Data.Char (isSpace)+import GHC.IO.Exception+import Control.Exception+import Control.Concurrent.Async++import Test.Hspec.Compat (showType)++-- |+-- @pluralize count singular@ pluralizes the given @singular@ word unless given+-- @count@ is 1.+--+-- Examples:+--+-- >>> pluralize 0 "example"+-- "0 examples"+--+-- >>> pluralize 1 "example"+-- "1 example"+--+-- >>> pluralize 2 "example"+-- "2 examples"+pluralize :: Int -> String -> String+pluralize 1 s = "1 " ++ s+pluralize n s = show n ++ " " ++ s ++ "s"++-- | Strip leading and trailing whitespace+strip :: String -> String+strip = dropWhile isSpace . reverse . dropWhile isSpace . reverse++-- |+-- ensure that lines are not longer then given `n`, insert line breaks at word+-- boundaries+lineBreaksAt :: Int -> String -> [String]+lineBreaksAt n input = case words input of+ [] -> []+ x:xs -> go (x, xs)+ where+ go :: (String, [String]) -> [String]+ go c = case c of+ (s, []) -> [s]+ (s, y:ys) -> let r = s ++ " " ++ y in+ if length r <= n+ then go (r, ys)+ else s : go (y, ys)++-- |+-- A `Path` represents the location of an example within the spec tree.+--+-- It consists of a list of group descriptions and a requirement description.+type Path = ([String], String)++-- |+-- Try to create a proper English sentence from a path by applying some+-- heuristics.+formatRequirement :: Path -> String+formatRequirement (groups, requirement) = groups_ ++ requirement+ where+ groups_ = case break (any isSpace) groups of+ ([], ys) -> join ys+ (xs, ys) -> join (intercalate "." xs : ys)++ join xs = case xs of+ [x] -> x ++ " "+ ys -> concatMap (++ ", ") ys++-- | A predicate that can be used to filter a spec tree.+filterPredicate :: String -> Path -> Bool+filterPredicate pattern path@(groups, requirement) =+ pattern `isInfixOf` plain+ || pattern `isInfixOf` formatted+ where+ plain = intercalate "/" (groups ++ [requirement])+ formatted = formatRequirement path++-- | The function `formatException` converts an exception to a string.+--+-- This is different from `show`. The type of the exception is included, e.g.:+--+-- >>> formatException (toException DivideByZero)+-- "ArithException (divide by zero)"+--+-- For `IOException`s the `IOErrorType` is included, as well.+formatException :: SomeException -> String+formatException err@(SomeException e) = case fromException err of+ Just ioe -> showType ioe ++ " of type " ++ showIOErrorType ioe ++ " (" ++ show ioe ++ ")"+ Nothing -> showType e ++ " (" ++ show e ++ ")"+ where+ showIOErrorType :: IOException -> String+ showIOErrorType ioe = case ioe_type ioe of+ AlreadyExists -> "AlreadyExists"+ NoSuchThing -> "NoSuchThing"+ ResourceBusy -> "ResourceBusy"+ ResourceExhausted -> "ResourceExhausted"+ EOF -> "EOF"+ IllegalOperation -> "IllegalOperation"+ PermissionDenied -> "PermissionDenied"+ UserError -> "UserError"+ UnsatisfiedConstraints -> "UnsatisfiedConstraints"+ SystemError -> "SystemError"+ ProtocolError -> "ProtocolError"+ OtherError -> "OtherError"+ InvalidArgument -> "InvalidArgument"+ InappropriateType -> "InappropriateType"+ HardwareFault -> "HardwareFault"+ UnsupportedOperation -> "UnsupportedOperation"+ TimeExpired -> "TimeExpired"+ ResourceVanished -> "ResourceVanished"+ Interrupted -> "Interrupted"++-- | @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.+safeTry :: IO a -> IO (Either SomeException a)+safeTry action = withAsync (action >>= evaluate) waitCatch
+ src/Test/Hspec/FailureReport.hs view
@@ -0,0 +1,36 @@+module Test.Hspec.FailureReport (+ FailureReport (..)+, writeFailureReport+, readFailureReport+) where++import System.IO+import System.SetEnv+import Test.Hspec.Compat+import Test.Hspec.Core.Util (Path, safeTry)++data FailureReport = FailureReport {+ failureReportSeed :: Integer+, failureReportMaxSuccess :: Int+, failureReportMaxSize :: Int+, failureReportMaxDiscardRatio :: Int+, failureReportPaths :: [Path]+} deriving (Eq, Show, Read)++writeFailureReport :: FailureReport -> IO ()+writeFailureReport x = do+ -- on Windows this can throw an exception when the input is too large, hence+ -- we use `safeTry` here+ safeTry (setEnv "HSPEC_FAILURES" $ show x) >>= either onError return+ where+ onError err = do+ hPutStrLn stderr ("WARNING: Could not write environment variable HSPEC_FAILURES (" ++ show err ++ ")")++readFailureReport :: IO (Maybe FailureReport)+readFailureReport = do+ mx <- lookupEnv "HSPEC_FAILURES"+ case mx >>= readMaybe of+ Nothing -> do+ hPutStrLn stderr "WARNING: Could not read environment variable HSPEC_FAILURES; `--rerun' is ignored!"+ return Nothing+ x -> return x
+ src/Test/Hspec/Options.hs view
@@ -0,0 +1,178 @@+module Test.Hspec.Options (+ Config(..)+, ColorMode (..)+, defaultConfig+, filterOr+, parseOptions+) where++import Control.Applicative+import Data.List+import System.IO+import System.Exit+import System.Console.GetOpt++import Test.Hspec.Core.Formatters+import Test.Hspec.Compat+import Test.Hspec.Core.Util++data Config = Config {+ configDryRun :: Bool+, configPrintCpuTime :: Bool+, configFastFail :: Bool++-- |+-- A predicate that is used to filter the spec before it is run. Only examples+-- that satisfy the predicate are run.+, configRerun :: Bool+, configFilterPredicate :: Maybe (Path -> Bool)+, configQuickCheckSeed :: Maybe Integer+, configQuickCheckMaxSuccess :: Maybe Int+, configQuickCheckMaxDiscardRatio :: Maybe Int+, configQuickCheckMaxSize :: Maybe Int+, configSmallCheckDepth :: Int+, configColorMode :: ColorMode+, configFormatter :: Maybe Formatter+, configHtmlOutput :: Bool+, configOutputFile :: Either Handle FilePath+}++defaultConfig :: Config+defaultConfig = Config {+ configDryRun = False+, configPrintCpuTime = False+, configFastFail = False+, configRerun = False+, configFilterPredicate = Nothing+, configQuickCheckSeed = Nothing+, configQuickCheckMaxSuccess = Nothing+, configQuickCheckMaxDiscardRatio = Nothing+, configQuickCheckMaxSize = Nothing+, configSmallCheckDepth = 5+, configColorMode = ColorAuto+, configFormatter = Nothing+, configHtmlOutput = False+, configOutputFile = Left stdout+}++filterOr :: Maybe (Path -> Bool) -> Maybe (Path -> Bool) -> Maybe (Path -> Bool)+filterOr p1_ p2_ = case (p1_, p2_) of+ (Just p1, Just p2) -> Just $ \path -> p1 path || p2 path+ _ -> p1_ <|> p2_++addMatch :: String -> Config -> Config+addMatch s c = c {configFilterPredicate = Just (filterPredicate s) `filterOr` configFilterPredicate c}++setDepth :: Int -> Config -> Config+setDepth n c = c {configSmallCheckDepth = n}++setMaxSuccess :: Int -> Config -> Config+setMaxSuccess n c = c {configQuickCheckMaxSuccess = Just n}++setMaxSize :: Int -> Config -> Config+setMaxSize n c = c {configQuickCheckMaxSize = Just n}++setMaxDiscardRatio :: Int -> Config -> Config+setMaxDiscardRatio n c = c {configQuickCheckMaxDiscardRatio = Just n}++setSeed :: Integer -> Config -> Config+setSeed n c = c {configQuickCheckSeed = Just n}++data ColorMode = ColorAuto | ColorNever | ColorAlways+ deriving (Eq, Show)++formatters :: [(String, Formatter)]+formatters = [+ ("specdoc", specdoc)+ , ("progress", progress)+ , ("failed-examples", failed_examples)+ , ("silent", silent)+ ]++formatHelp :: String+formatHelp = unlines (addLineBreaks "use a custom formatter; this can be one of:" ++ map ((" " ++) . fst) formatters)++type Result = Either NoConfig Config++data NoConfig = Help | InvalidArgument String String++data Arg a = Arg {+ _argumentName :: String+, _argumentParser :: String -> Maybe a+, _argumentSetter :: a -> Config -> Config+}++mkOption :: [Char] -> String -> Arg a -> String -> OptDescr (Result -> Result)+mkOption shortcut name (Arg argName parser setter) help = Option shortcut [name] (ReqArg arg argName) help+ where+ arg :: String -> Result -> Result+ arg input x = x >>= \c -> case parser input of+ Just n -> Right (setter n c)+ Nothing -> Left (InvalidArgument name input)++addLineBreaks :: String -> [String]+addLineBreaks = lineBreaksAt 44++options :: [OptDescr (Result -> Result)]+options = [+ Option [] ["help"] (NoArg (const $ Left Help)) (h "display this help and exit")+ , mkOption "m" "match" (Arg "PATTERN" return addMatch) (h "only run examples that match given PATTERN")+ , Option [] ["color"] (NoArg setColor) (h "colorize the output")+ , Option [] ["no-color"] (NoArg setNoColor) (h "do not colorize the output")+ , mkOption "f" "format" (Arg "FORMATTER" readFormatter setFormatter) formatHelp+ , mkOption "o" "out" (Arg "FILE" return setOutputFile) (h "write output to a file instead of STDOUT")+ , mkOption [] "depth" (Arg "N" readMaybe setDepth) (h "maximum depth of generated test values for SmallCheck properties")+ , mkOption "a" "qc-max-success" (Arg "N" readMaybe setMaxSuccess) (h "maximum number of successful tests before a QuickCheck property succeeds")+ , mkOption "" "qc-max-size" (Arg "N" readMaybe setMaxSize) (h "size to use for the biggest test cases")+ , mkOption "" "qc-max-discard" (Arg "N" readMaybe setMaxDiscardRatio) (h "maximum number of discarded tests per successful test before giving up")+ , mkOption [] "seed" (Arg "N" readMaybe setSeed) (h "used seed for QuickCheck properties")+ , Option [] ["print-cpu-time"] (NoArg setPrintCpuTime) (h "include used CPU time in summary")+ , Option [] ["dry-run"] (NoArg setDryRun) (h "pretend that everything passed; don't verify anything")+ , Option [] ["fail-fast"] (NoArg setFastFail) (h "abort on first failure")+ , Option "r" ["rerun"] (NoArg setRerun) (h "rerun all examples that failed in the previously test run (only works in GHCi)")+ ]+ where+ h = unlines . addLineBreaks++ readFormatter :: String -> Maybe Formatter+ readFormatter = (`lookup` formatters)++ setFormatter :: Formatter -> Config -> Config+ setFormatter f c = c {configFormatter = Just f}++ setOutputFile :: String -> Config -> Config+ setOutputFile file c = c {configOutputFile = Right file}++ setPrintCpuTime x = x >>= \c -> return c {configPrintCpuTime = True}+ setDryRun x = x >>= \c -> return c {configDryRun = True}+ setFastFail x = x >>= \c -> return c {configFastFail = True}+ setRerun x = x >>= \c -> return c {configRerun = True}+ setNoColor x = x >>= \c -> return c {configColorMode = ColorNever}+ setColor x = x >>= \c -> return c {configColorMode = ColorAlways}++undocumentedOptions :: [OptDescr (Result -> Result)]+undocumentedOptions = [+ -- for compatibility with test-framework+ mkOption [] "maximum-generated-tests" (Arg "NUMBER" readMaybe setMaxSuccess) "how many automated tests something like QuickCheck should try, by default"++ -- undocumented for now, as we probably want to change this to produce a+ -- standalone HTML report in the future+ , Option [] ["html"] (NoArg setHtml) "produce HTML output"++ -- now a noop+ , Option "v" ["verbose"] (NoArg id) "do not suppress output to stdout when evaluating examples"+ ]+ where+ setHtml :: Result -> Result+ setHtml x = x >>= \c -> return c {configHtmlOutput = True}++parseOptions :: Config -> String -> [String] -> Either (ExitCode, String) Config+parseOptions c prog args = case getOpt Permute (options ++ undocumentedOptions) args of+ (opts, [], []) -> case foldl' (flip id) (Right c) opts of+ Left Help -> Left (ExitSuccess, usageInfo ("Usage: " ++ prog ++ " [OPTION]...\n\nOPTIONS") options)+ Left (InvalidArgument flag value) -> tryHelp ("invalid argument `" ++ value ++ "' for `--" ++ flag ++ "'\n")+ Right x -> Right x+ (_, _, err:_) -> tryHelp err+ (_, arg:_, _) -> tryHelp ("unexpected argument `" ++ arg ++ "'\n")+ where+ tryHelp msg = Left (ExitFailure 1, prog ++ ": " ++ msg ++ "Try `" ++ prog ++ " --help' for more information.\n")
+ src/Test/Hspec/Timer.hs view
@@ -0,0 +1,14 @@+module Test.Hspec.Timer where++import Data.IORef+import Data.Time.Clock.POSIX++newTimer :: POSIXTime -> IO (IO Bool)+newTimer delay = do+ ref <- getPOSIXTime >>= newIORef+ return $ do+ t0 <- readIORef ref+ t1 <- getPOSIXTime+ if delay < t1 - t0+ then writeIORef ref t1 >> return True+ else return False
+ test/Helper.hs view
@@ -0,0 +1,89 @@+module Helper (+ module Test.Hspec.Meta+, module Test.QuickCheck+, module Control.Applicative+, module System.IO.Silently+, sleep+, timeout+, defaultParams+, noOpProgressCallback+, captureLines+, normalizeSummary++, ignoreExitCode+, ignoreUserInterrupt+, throwException++, shouldStartWith+, shouldEndWith++, shouldUseArgs+) where++import Data.List+import Data.Char+import Data.IORef+import Control.Monad+import Control.Applicative+import System.Environment (withArgs)+import System.Exit+import Control.Concurrent+import qualified Control.Exception as E+import qualified System.Timeout as System+import Data.Time.Clock.POSIX+import System.IO.Silently++import Test.Hspec.Meta+import Test.QuickCheck hiding (Result(..))++import qualified Test.Hspec.Core.Spec as H+import qualified Test.Hspec.Core.Runner as H+import Test.Hspec.Core.QuickCheckUtil (mkGen)++throwException :: IO ()+throwException = E.throwIO (E.ErrorCall "foobar")++ignoreExitCode :: IO () -> IO ()+ignoreExitCode action = action `E.catch` \e -> let _ = e :: ExitCode in return ()++ignoreUserInterrupt :: IO () -> IO ()+ignoreUserInterrupt action = E.catchJust (guard . (== E.UserInterrupt)) action return++captureLines :: IO a -> IO [String]+captureLines = fmap lines . capture_++shouldStartWith :: (Eq a, Show a) => [a] -> [a] -> Expectation+x `shouldStartWith` y = x `shouldSatisfy` isPrefixOf y++shouldEndWith :: (Eq a, Show a) => [a] -> [a] -> Expectation+x `shouldEndWith` y = x `shouldSatisfy` isSuffixOf y++-- replace times in summary with zeroes+normalizeSummary :: [String] -> [String]+normalizeSummary = map f+ where+ f x | "Finished in " `isPrefixOf` x = map g x+ | otherwise = x+ g x | isNumber x = '0'+ | otherwise = x++defaultParams :: H.Params+defaultParams = H.Params stdArgs {replay = Just (mkGen 23, 0), maxSuccess = 1000} (H.configSmallCheckDepth H.defaultConfig)++noOpProgressCallback :: H.ProgressCallback+noOpProgressCallback _ = return ()++sleep :: POSIXTime -> IO ()+sleep = threadDelay . floor . (* 1000000)++timeout :: POSIXTime -> IO a -> IO (Maybe a)+timeout = System.timeout . floor . (* 1000000)++shouldUseArgs :: [String] -> (Args -> Bool) -> Expectation+shouldUseArgs args p = do+ spy <- newIORef (H.paramsQuickCheckArgs defaultParams)+ let interceptArgs item = item {H.itemExample = \params action progressCallback -> writeIORef spy (H.paramsQuickCheckArgs params) >> H.itemExample item params action progressCallback}+ spec = H.mapSpecItem_ interceptArgs $+ H.it "foo" False+ (silence . ignoreExitCode . withArgs args . H.hspec) spec+ readIORef spy >>= (`shouldSatisfy` p)
+ test/Mock.hs view
@@ -0,0 +1,15 @@+module Mock where++import Control.Applicative+import Data.IORef++newtype Mock = Mock (IORef Int)++newMock :: IO Mock+newMock = Mock <$> newIORef 0++mockAction :: Mock -> IO ()+mockAction (Mock ref) = modifyIORef ref succ++mockCounter :: Mock -> IO Int+mockCounter (Mock ref) = readIORef ref
+ test/Spec.hs view
@@ -0,0 +1,1 @@+{-# OPTIONS_GHC -F -pgmF hspec-meta-discover #-}
+ test/Test/Hspec/CompatSpec.hs view
@@ -0,0 +1,33 @@+{-# LANGUAGE DeriveDataTypeable #-}+module Test.Hspec.CompatSpec (main, spec) where++import Helper+import System.SetEnv++import Test.Hspec.Compat+import Data.Typeable++data SomeType = SomeType+ deriving Typeable++main :: IO ()+main = hspec spec++spec :: Spec+spec = do+ describe "showType" $ do+ it "shows unqualified name of type" $ do+ showType SomeType `shouldBe` "SomeType"++ describe "showFullType (currently unused)" $ do+ it "shows fully qualified name of type" $ do+ showFullType SomeType `shouldBe` "Test.Hspec.CompatSpec.SomeType"++ describe "lookupEnv" $ do+ it "returns value of specified environment variable" $ do+ setEnv "FOO" "bar"+ lookupEnv "FOO" `shouldReturn` Just "bar"++ it "returns Nothing if specified environment variable is not set" $ do+ unsetEnv "FOO"+ lookupEnv "FOO" `shouldReturn` Nothing
+ test/Test/Hspec/Core/ExampleSpec.hs view
@@ -0,0 +1,136 @@+{-# LANGUAGE CPP, ScopedTypeVariables, TypeFamilies #-}+module Test.Hspec.Core.ExampleSpec (main, spec) where++import Helper+import Mock+import Data.List+import Data.IORef++import qualified Test.Hspec.Core.Example as H+import qualified Test.Hspec.Core.Spec as H+import qualified Test.Hspec.Core.Runner as H++main :: IO ()+main = hspec spec++evaluateExample :: (H.Example e, H.Arg e ~ ()) => e -> IO H.Result+evaluateExample e = H.evaluateExample e defaultParams ($ ()) noOpProgressCallback++evaluateExampleWith :: (H.Example e, H.Arg e ~ ()) => (IO () -> IO ()) -> e -> IO H.Result+evaluateExampleWith action e = H.evaluateExample e defaultParams (action . ($ ())) noOpProgressCallback++spec :: Spec+spec = do+ describe "evaluateExample" $ do+ context "for Bool" $ do+ it "returns Success on True" $ do+ evaluateExample True `shouldReturn` H.Success++ it "returns Fail on False" $ do+ evaluateExample False `shouldReturn` H.Fail ""++ it "propagates exceptions" $ do+ evaluateExample (error "foobar" :: Bool) `shouldThrow` errorCall "foobar"++ context "for Expectation" $ do+ it "returns Success if all expectations hold" $ do+ evaluateExample (23 `shouldBe` (23 :: Int)) `shouldReturn` H.Success++ it "returns Fail if an expectation does not hold" $ do+ evaluateExample (23 `shouldBe` (42 :: Int)) `shouldReturn` H.Fail "expected: 42\n but got: 23"++ it "propagates exceptions" $ do+ evaluateExample (error "foobar" :: Expectation) `shouldThrow` errorCall "foobar"++ it "runs provided action around expectation" $ do+ ref <- newIORef (0 :: Int)+ let action :: IO () -> IO ()+ action e = do+ n <- readIORef ref+ e+ readIORef ref `shouldReturn` succ n+ modifyIORef ref succ+ evaluateExampleWith action (modifyIORef ref succ) `shouldReturn` H.Success+ readIORef ref `shouldReturn` 2++ context "when used with `pending`" $ do+ it "returns Pending" $ do+ evaluateExample (H.pending) `shouldReturn` H.Pending Nothing++ context "when used with `pendingWith`" $ do+ it "includes the optional reason" $ do+ evaluateExample (H.pendingWith "foo") `shouldReturn` H.Pending (Just "foo")++ context "for Property" $ do+ it "returns Success if property holds" $ do+ evaluateExample (property $ \n -> n == (n :: Int)) `shouldReturn` H.Success++ it "returns Fail if property does not hold" $ do+ H.Fail _ <- evaluateExample $ property $ \n -> n /= (n :: Int)+ return ()++ it "shows what falsified it" $ do+ H.Fail r <- evaluateExample $ property $ \ (x :: Int) (y :: Int) -> (x == 0 && y == 1) ==> False+ r `shouldBe` intercalate "\n" [+ "Falsifiable (after 1 test): "+ , "0"+ , "1"+ ]++ it "runs provided action around each single check of the property" $ do+ ref <- newIORef (0 :: Int)+ let action :: IO () -> IO ()+ action e = do+ n <- readIORef ref+ e+ readIORef ref `shouldReturn` succ n+ modifyIORef ref succ+ H.Success <- evaluateExampleWith action (property $ modifyIORef ref succ)+ readIORef ref `shouldReturn` 2000++ it "pretty-prints exceptions" $ do+ H.Fail r <- evaluateExample $ property (\ (x :: Int) -> (x == 0) ==> (error "foobar" :: Bool))+ r `shouldBe` intercalate "\n" [+#if MIN_VERSION_QuickCheck(2,7,0)+ "uncaught exception: ErrorCall (foobar) (after 1 test)"+#else+ "Exception: 'foobar' (after 1 test): "+#endif+ , "0"+ ]++ context "when used with shouldBe" $ do+ it "shows what falsified it" $ do+ H.Fail r <- evaluateExample $ property $ \ (x :: Int) (y :: Int) -> (x == 0 && y == 1) ==> 23 `shouldBe` (42 :: Int)+ r `shouldBe` intercalate "\n" [+ "Falsifiable (after 1 test): "+ , "expected: 42"+ , " but got: 23"+ , "0"+ , "1"+ ]++ context "when used with `pending`" $ do+ it "returns Pending" $ do+ evaluateExample (property H.pending) `shouldReturn` H.Pending Nothing++ context "when used with `pendingWith`" $ do+ it "includes the optional reason" $ do+ evaluateExample (property $ H.pendingWith "foo") `shouldReturn` H.Pending (Just "foo")++ describe "Expectation" $ do+ context "as a QuickCheck property" $ do+ it "can be quantified" $ do+ e <- newMock+ silence . H.hspec $ do+ H.it "some behavior" $ property $ \xs -> do+ mockAction e+ (reverse . reverse) xs `shouldBe` (xs :: [Int])+ mockCounter e `shouldReturn` 100++ it "can be used with expectations/HUnit assertions" $ do+ silence . H.hspecResult $ do+ H.describe "readIO" $ do+ H.it "is inverse to show" $ property $ \x -> do+ (readIO . show) x `shouldReturn` (x :: Int)+ `shouldReturn` H.Summary 1 0
+ test/Test/Hspec/Core/FormattersSpec.hs view
@@ -0,0 +1,227 @@+{-# LANGUAGE CPP #-}+module Test.Hspec.Core.FormattersSpec (main, spec) where++import Helper++import qualified Test.Hspec.Core.Spec as H+import qualified Test.Hspec.Core.Runner as H+import qualified Test.Hspec.Core.Formatters as H++#ifndef mingw32_HOST_OS+import System.Console.ANSI+#endif++main :: IO ()+main = hspec spec++testSpec :: H.Spec+testSpec = do+ H.describe "Example" $ do+ H.it "success" (H.Success)+ H.it "fail 1" (H.Fail "fail message")+ H.it "pending" (H.pendingWith "pending message")+ H.it "fail 2" (H.Fail "")+ H.it "exceptions" (undefined :: H.Result)+ H.it "fail 3" (H.Fail "")++spec :: Spec+spec = do+ describe "silent" $ do+ let runSpec = fmap fst . capture . H.hspecWithResult H.defaultConfig {H.configFormatter = Just H.silent}+ it "produces no output" $ do+ runSpec testSpec `shouldReturn` ""++ describe "failed_examples" $ do+ failed_examplesSpec H.failed_examples++ describe "progress" $ do+ let runSpec = captureLines . H.hspecWithResult H.defaultConfig {H.configFormatter = Just H.progress}++ it "produces '..F...FF.F' style output" $ do+ r <- runSpec testSpec+ head r `shouldBe` ".F.FFF"++ context "same as failed_examples" $ do+ failed_examplesSpec H.progress++ describe "specdoc" $ do+ let runSpec = captureLines . H.hspecWithResult H.defaultConfig {H.configFormatter = Just H.specdoc}++ it "displays a header for each thing being described" $ do+ _:x:_ <- runSpec testSpec+ x `shouldBe` "Example"++ it "displays one row for each behavior" $ do+ r <- runSpec $ do+ H.describe "List as a Monoid" $ do+ H.describe "mappend" $ do+ H.it "is associative" True+ H.describe "mempty" $ do+ H.it "is a left identity" True+ H.it "is a right identity" True+ H.describe "Maybe as a Monoid" $ do+ H.describe "mappend" $ do+ H.it "is associative" True+ H.describe "mempty" $ do+ H.it "is a left identity" True+ H.it "is a right identity" True+ normalizeSummary r `shouldBe` [+ ""+ , "List as a Monoid"+ , " mappend"+ , " is associative"+ , " mempty"+ , " is a left identity"+ , " is a right identity"+ , "Maybe as a Monoid"+ , " mappend"+ , " is associative"+ , " mempty"+ , " is a left identity"+ , " is a right identity"+ , ""+ , "Finished in 0.0000 seconds"+ , "6 examples, 0 failures"+ ]++ it "outputs an empty line at the beginning (even for non-nested specs)" $ do+ r <- runSpec $ do+ H.it "example 1" True+ H.it "example 2" True+ normalizeSummary r `shouldBe` [+ ""+ , "example 1"+ , "example 2"+ , ""+ , "Finished in 0.0000 seconds"+ , "2 examples, 0 failures"+ ]++ it "displays a row for each successfull, failed, or pending example" $ do+ r <- runSpec testSpec+ r `shouldSatisfy` any (== " fail 1 FAILED [1]")+ r `shouldSatisfy` any (== " success")++ it "displays a '#' with an additional message for pending examples" $ do+ r <- runSpec testSpec+ r `shouldSatisfy` any (== " # PENDING: pending message")++ context "with an empty group" $ do+ it "omits that group from the report" $ do+ r <- runSpec $ do+ H.describe "foo" $ do+ H.it "example 1" True+ H.describe "bar" $ do+ return ()+ H.describe "baz" $ do+ H.it "example 2" True++ normalizeSummary r `shouldBe` [+ ""+ , "foo"+ , " example 1"+ , "baz"+ , " example 2"+ , ""+ , "Finished in 0.0000 seconds"+ , "2 examples, 0 failures"+ ]++ context "same as failed_examples" $ do+ failed_examplesSpec H.progress++failed_examplesSpec :: H.Formatter -> Spec+failed_examplesSpec formatter = do+ let runSpec = captureLines . H.hspecWithResult H.defaultConfig {H.configFormatter = Just formatter}++ it "summarizes the time it takes to finish" $ do+ r <- runSpec (return ())+ normalizeSummary r `shouldSatisfy` any (== "Finished in 0.0000 seconds")++ context "displays a detailed list of failures" $ do+ it "prints all requirements that are not met" $ do+ r <- runSpec testSpec+ r `shouldSatisfy` any (== "1) Example fail 1")++ it "prints the exception type for requirements that fail due to an uncaught exception" $ do+ r <- runSpec $ do+ H.it "foobar" (undefined :: Bool)+ r `shouldContain` [+ "1) foobar"+ , "uncaught exception: ErrorCall (Prelude.undefined)"+ ]++ it "prints all descriptions when a nested requirement fails" $ do+ r <- runSpec $+ H.describe "foo" $ do+ H.describe "bar" $ do+ H.it "baz" False+ r `shouldSatisfy` any (== "1) foo.bar baz")+++ context "when a failed example has a source location" $ do+ let bestEffortExplanation = "Source locations marked with \"best-effort\" are calculated heuristically and may be incorrect."++ context "when source location is exact" $ do+ it "includes that source locations" $ do+ let loc = H.Location "test/FooSpec.hs" 23 0 H.ExactLocation+ addLoc e = e {H.itemLocation = Just loc}+ r <- runSpec $ H.mapSpecItem_ addLoc $ do+ H.it "foo" False+ r `shouldSatisfy` any (== "# test/FooSpec.hs:23")++ it "does not include 'best-effort' explanation" $ do+ let loc = H.Location "test/FooSpec.hs" 23 0 H.ExactLocation+ addLoc e = e {H.itemLocation = Just loc}+ r <- runSpec $ H.mapSpecItem_ addLoc $ do+ H.it "foo" False+ r `shouldSatisfy` all (/= bestEffortExplanation)++ context "when source location is best-effort" $ do+ it "marks that source location as 'best-effort'" $ do+ let loc = H.Location "test/FooSpec.hs" 23 0 H.BestEffort+ addLoc e = e {H.itemLocation = Just loc}+ r <- runSpec $ H.mapSpecItem_ addLoc $ do+ H.it "foo" False+ r `shouldSatisfy` any (== "# test/FooSpec.hs:23 (best-effort)")++ it "includes 'best-effort' explanation" $ do+ let loc = H.Location "test/FooSpec.hs" 23 0 H.BestEffort+ addLoc e = e {H.itemLocation = Just loc}+ r <- runSpec $ H.mapSpecItem_ addLoc $ do+ H.it "foo" False+ r `shouldSatisfy` any (== bestEffortExplanation)++ it "summarizes the number of examples and failures" $ do+ r <- runSpec testSpec+ r `shouldSatisfy` any (== "6 examples, 4 failures, 1 pending")++ -- Windows has no support for ANSI escape codes. The Console API is used for+ -- colorized output, hence the following tests do not work on Windows.+#ifndef mingw32_HOST_OS+ it "shows summary in green if there are no failures" $ do+ r <- captureLines $ H.hspecWithResult H.defaultConfig {H.configColorMode = H.ColorAlways} $ do+ H.it "foobar" True+ r `shouldSatisfy` any (== (green ++ "1 example, 0 failures" ++ reset))++ it "shows summary in yellow if there are pending examples" $ do+ r <- captureLines $ H.hspecWithResult H.defaultConfig {H.configColorMode = H.ColorAlways} $ do+ H.it "foobar" H.pending+ r `shouldSatisfy` any (== (yellow ++ "1 example, 0 failures, 1 pending" ++ reset))++ it "shows summary in red if there are failures" $ do+ r <- captureLines $ H.hspecWithResult H.defaultConfig {H.configColorMode = H.ColorAlways} $ do+ H.it "foobar" False+ r `shouldSatisfy` any (== (red ++ "1 example, 1 failure" ++ reset))++ it "shows summary in red if there are both failures and pending examples" $ do+ r <- captureLines $ H.hspecWithResult H.defaultConfig {H.configColorMode = H.ColorAlways} $ do+ H.it "foo" False+ H.it "bar" H.pending+ r `shouldSatisfy` any (== (red ++ "2 examples, 1 failure, 1 pending" ++ reset))+ where+ green = setSGRCode [SetColor Foreground Dull Green]+ yellow = setSGRCode [SetColor Foreground Dull Yellow]+ red = setSGRCode [SetColor Foreground Dull Red]+ reset = setSGRCode [Reset]+#endif
+ test/Test/Hspec/Core/HooksSpec.hs view
@@ -0,0 +1,230 @@+module Test.Hspec.Core.HooksSpec (main, spec) where++import Helper+import Data.IORef++import qualified Test.Hspec.Core.Spec as H+import qualified Test.Hspec.Core.Runner as H++import qualified Test.Hspec.Core.Hooks as H++main :: IO ()+main = hspec spec++spec :: Spec+spec = do+ describe "before" $ do+ it "runs an action before every spec item" $ do+ ref <- newIORef ([] :: [String])+ let append n = modifyIORef ref (++ return n)+ silence $ H.hspec $ H.before (append "before") $ do+ H.it "foo" $ do+ append "foo"+ H.it "bar" $ do+ append "bar"+ readIORef ref `shouldReturn` [+ "before"+ , "foo"+ , "before"+ , "bar"+ ]++ context "when used multiple times" $ do+ it "is evaluated outside in" $ do+ ref <- newIORef ([] :: [String])+ let append n = modifyIORef ref (++ return n)+ silence $ H.hspec $ H.before (append "before outer") $ H.before (append "before inner") $ do+ H.it "foo" $ do+ append "foo"+ readIORef ref `shouldReturn` [+ "before outer"+ , "before inner"+ , "foo"+ ]++ context "when used with an action that returns a value" $ do+ it "passes that value to the spec item" $ do+ property $ \n -> do+ silence $ H.hspec $ H.before (return n) $ do+ H.it "foo" $ \m -> do+ m `shouldBe` (n :: Int)++ context "when used with a QuickCheck property" $ do+ it "runs action before every check of the property" $ do+ ref <- newIORef ([] :: [String])+ let append n = modifyIORef ref (++ return n)+ silence $ H.hspec $ H.before (append "before") $ do+ H.it "foo" $ property $ append "foo"+ readIORef ref `shouldReturn` (take 200 . cycle) ["before", "foo"]++ describe "beforeWith" $ do+ it "runs an action before every spec item" $ do+ let action :: Int -> IO String+ action n = return (show n)+ property $ \n -> do+ silence $ H.hspec $ H.before (return n) $ H.beforeWith action $ do+ H.it "foo" $ (`shouldBe` show n)++ describe "beforeAll" $ do+ it "runs an action before the first spec item" $ do+ ref <- newIORef []+ let append n = modifyIORef ref (++ return n)+ silence $ H.hspec $ H.beforeAll (append "beforeAll") $ do+ H.it "foo" $ do+ append "foo"+ H.it "bar" $ do+ append "bar"+ readIORef ref `shouldReturn` [+ "beforeAll"+ , "foo"+ , "bar"+ ]++ context "when used with an empty list of examples" $ do+ it "does not run specified action" $ do+ ref <- newIORef []+ let append n = modifyIORef ref (++ return n)+ silence $ H.hspec $ H.beforeAll (append "beforeAll") $ do+ return ()+ readIORef ref `shouldReturn` []++ context "when used with an action that returns a value" $ do+ it "passes that value to the spec item" $ do+ property $ \n -> do+ silence $ H.hspec $ H.beforeAll (return n) $ do+ H.it "foo" $ \m -> do+ m `shouldBe` (n :: Int)++ describe "after" $ do+ it "must be used with a before action" $ do+ ref <- newIORef ([] :: [String])+ let append n = modifyIORef ref (++ return n)+ silence $ H.hspec $ H.before (return "from before") $ H.after append $ do+ H.it "foo" $ \_ -> do+ append "foo"+ readIORef ref `shouldReturn` [+ "foo"+ , "from before"+ ]++ describe "after_" $ do+ it "runs an action after every spec item" $ do+ ref <- newIORef ([] :: [String])+ let append n = modifyIORef ref (++ return n)+ silence $ H.hspec $ H.after_ (append "after") $ do+ H.it "foo" $ do+ append "foo"+ H.it "bar" $ do+ append "bar"+ readIORef ref `shouldReturn` [+ "foo"+ , "after"+ , "bar"+ , "after"+ ]++ it "guarantees that action is run" $ do+ ref <- newIORef ([] :: [String])+ let append n = modifyIORef ref (++ return n)+ silence . ignoreExitCode $ H.hspec $ H.after_ (append "after") $ do+ H.it "foo" $ do+ ioError $ userError "foo" :: IO ()+ append "foo"+ readIORef ref `shouldReturn` ["after"]++ context "when used multiple times" $ do+ it "is evaluated inside out" $ do+ ref <- newIORef ([] :: [String])+ let append n = modifyIORef ref (++ return n)+ silence $ H.hspec $ H.after_ (append "after outer") $ H.after_ (append "after inner") $ do+ H.it "foo" $ do+ append "foo"+ readIORef ref `shouldReturn` [+ "foo"+ , "after inner"+ , "after outer"+ ]++ describe "afterAll_" $ do+ it "runs an action after the last spec item" $ do+ ref <- newIORef []+ let append n = modifyIORef ref (++ return n)+ silence $ H.hspec $ H.afterAll_ (append "afterAll") $ do+ H.it "foo" $ do+ append "foo"+ H.it "bar" $ do+ append "bar"+ readIORef ref `shouldReturn` [+ "foo"+ , "bar"+ , "afterAll"+ ]++ context "when used with an empty list of examples" $ do+ it "does not run specified action" $ do+ ref <- newIORef []+ let append n = modifyIORef ref (++ return n)+ silence $ H.hspec $ H.afterAll_ (append "afterAll") $ do+ return ()+ readIORef ref `shouldReturn` []++ context "when action throws an exception" $ do+ it "reports a failure" $ do+ r <- runSpec $ do+ H.afterAll_ throwException $ do+ H.it "foo" True+ r `shouldSatisfy` any (== "afterAll-hook FAILED [1]")++ describe "around" $ do+ it "wraps every spec item with an action" $ do+ property $ \n -> do+ silence $ H.hspec $ H.around ($ n) $ do+ H.it "foo" $ \m -> do+ m `shouldBe` (n :: Int)++ describe "around_" $ do+ it "wraps every spec item with an action" $ do+ ref <- newIORef ([] :: [String])+ let append n = modifyIORef ref (++ return n)+ action e = append "before" >> e >> append "after"+ silence $ H.hspec $ H.around_ action $ do+ H.it "foo" $ do+ append "foo"+ H.it "bar" $ do+ append "bar"+ readIORef ref `shouldReturn` [+ "before"+ , "foo"+ , "after"+ , "before"+ , "bar"+ , "after"+ ]++ context "when used multiple times" $ do+ it "is evaluated outside in" $ do+ ref <- newIORef ([] :: [String])+ let append n = modifyIORef ref (++ return n)+ actionOuter e = append "before outer" >> e >> append "after outer"+ actionInner e = append "before inner" >> e >> append "after inner"+ silence $ H.hspec $ H.around_ actionOuter $ H.around_ actionInner $ do+ H.it "foo" $ do+ append "foo"+ readIORef ref `shouldReturn` [+ "before outer"+ , "before inner"+ , "foo"+ , "after inner"+ , "after outer"+ ]++ describe "aroundWith" $ do+ it "wraps every spec item with an action" $ do+ let action :: H.ActionWith String -> H.ActionWith Int+ action e n = e (show n)+ property $ \n -> do+ silence $ H.hspec $ H.before (return n) $ H.aroundWith action $ do+ H.it "foo" $ (`shouldBe` show n)+ where+ runSpec :: H.Spec -> IO [String]+ runSpec = captureLines . H.hspecResult
+ test/Test/Hspec/Core/QuickCheckUtilSpec.hs view
@@ -0,0 +1,31 @@+{-# LANGUAGE CPP #-}+{-# OPTIONS_GHC -fno-warn-missing-fields #-}+module Test.Hspec.Core.QuickCheckUtilSpec (main, spec) where++import Helper++import Test.QuickCheck+import Test.Hspec.Core.QuickCheckUtil++main :: IO ()+main = hspec spec++spec :: Spec+spec = do+ describe "formatNumbers" $ do+ it "includes number of tests" $ do+ formatNumbers (failure 1 0) `shouldBe` "(after 1 test)"++ it "pluralizes number of tests" $ do+ formatNumbers (failure 3 0) `shouldBe` "(after 3 tests)"++ it "includes number of shrinks" $ do+ formatNumbers (failure 3 1) `shouldBe` "(after 3 tests and 1 shrink)"++ it "pluralizes number of shrinks" $ do+ formatNumbers (failure 3 3) `shouldBe` "(after 3 tests and 3 shrinks)"+ where+ failure tests shrinks = Failure {+ numTests = tests+ , numShrinks = shrinks+ }
+ test/Test/Hspec/Core/RunnerSpec.hs view
@@ -0,0 +1,399 @@+module Test.Hspec.Core.RunnerSpec (main, spec) where++import Helper+import System.IO (stderr)+import Control.Monad+import System.Environment (withArgs, withProgName, getArgs)+import System.Exit+import Control.Concurrent+import qualified Control.Exception as E+import Mock+import System.SetEnv+import Test.Hspec.Compat++import Test.Hspec.FailureReport (FailureReport(..))+import qualified Test.Hspec.Core.Spec as H+import qualified Test.Hspec.Core.Runner as H+import qualified Test.Hspec.Core.Formatters as H (silent)+import qualified Test.Hspec.Core.QuickCheck as H++import qualified Test.QuickCheck as QC+import qualified Test.Hspec.Core.Hooks as H++main :: IO ()+main = hspec spec++quickCheckOptions :: [([Char], Args -> Int)]+quickCheckOptions = [("--qc-max-success", QC.maxSuccess), ("--qc-max-size", QC.maxSize), ("--qc-max-discard", QC.maxDiscardRatio)]++runPropFoo :: [String] -> IO String+runPropFoo args = unlines . normalizeSummary . lines <$> do+ capture_ . ignoreExitCode . withArgs args . H.hspec . H.modifyMaxSuccess (const 1000000) $ do+ H.it "foo" $ do+ property (/= (23 :: Int))++spec :: Spec+spec = do+ describe "hspec" $ do+ it "runs a spec" $ do+ silence . H.hspec $ do+ H.it "foobar" True+ `shouldReturn` ()++ it "exits with exitFailure if not all examples pass" $ do+ silence . H.hspec $ do+ H.it "foobar" False+ `shouldThrow` (== ExitFailure 1)++ it "allows output to stdout" $ do+ r <- captureLines . H.hspec $ do+ H.it "foobar" $ do+ putStrLn "baz"+ r `shouldSatisfy` elem "baz"++ it "prints an error message on unrecognized command-line options" $ do+ withProgName "myspec" . withArgs ["--foo"] $ do+ hSilence [stderr] (H.hspec $ pure ()) `shouldThrow` (== ExitFailure 1)+ fst `fmap` hCapture [stderr] (ignoreExitCode (H.hspec $ pure ())) `shouldReturn` unlines [+ "myspec: unrecognized option `--foo'"+ , "Try `myspec --help' for more information."+ ]++ it "stores a failure report in the environment" $ do+ silence . ignoreExitCode . withArgs ["--seed", "23"] . H.hspec $ do+ H.describe "foo" $ do+ H.describe "bar" $ do+ H.it "example 1" True+ H.it "example 2" False+ H.describe "baz" $ do+ H.it "example 3" False+ lookupEnv "HSPEC_FAILURES" `shouldReturn` (Just . show) FailureReport {+ failureReportSeed = 23+ , failureReportMaxSuccess = 100+ , failureReportMaxSize = 100+ , failureReportMaxDiscardRatio = 10+ , failureReportPaths = [+ (["foo", "bar"], "example 2")+ , (["baz"], "example 3")+ ]+ }++ describe "with --rerun" $ do+ let runSpec = (captureLines . ignoreExitCode . H.hspec) $ do+ H.it "example 1" True+ H.it "example 2" False+ H.it "example 3" False+ H.it "example 4" True+ H.it "example 5" False++ it "reruns examples that previously failed" $ do+ r0 <- runSpec+ r0 `shouldSatisfy` elem "5 examples, 3 failures"++ r1 <- withArgs ["--rerun"] runSpec+ r1 `shouldSatisfy` elem "3 examples, 3 failures"++ it "reuses the same seed" $ do+ r <- runPropFoo ["--seed", "42"]+ runPropFoo ["-r"] `shouldReturn` r++ forM_ quickCheckOptions $ \(flag, accessor) -> do+ it ("reuses same " ++ flag) $ do+ [flag, "23"] `shouldUseArgs` ((== 23) . accessor)+ ["--rerun"] `shouldUseArgs` ((== 23) . accessor)++ context "when there is no failure report in the environment" $ do+ it "runs everything" $ do+ unsetEnv "HSPEC_FAILURES"+ r <- hSilence [stderr] $ withArgs ["-r"] runSpec+ r `shouldSatisfy` elem "5 examples, 3 failures"++ it "prints a warning to stderr" $ do+ unsetEnv "HSPEC_FAILURES"+ r <- hCapture_ [stderr] $ withArgs ["-r"] runSpec+ r `shouldBe` "WARNING: Could not read environment variable HSPEC_FAILURES; `--rerun' is ignored!\n"++ context "when parsing of failure report fails" $ do+ it "runs everything" $ do+ setEnv "HSPEC_FAILURES" "some invalid report"+ r <- hSilence [stderr] $ withArgs ["-r"] runSpec+ r `shouldSatisfy` elem "5 examples, 3 failures"++ it "prints a warning to stderr" $ do+ setEnv "HSPEC_FAILURES" "some invalid report"+ r <- hCapture_ [stderr] $ withArgs ["-r"] runSpec+ r `shouldBe` "WARNING: Could not read environment variable HSPEC_FAILURES; `--rerun' is ignored!\n"+++ it "does not leak command-line flags to examples" $ do+ silence . withArgs ["--verbose"] $ do+ H.hspec $ do+ H.it "foobar" $ do+ getArgs `shouldReturn` []+ `shouldReturn` ()++ context "when interrupted with ctrl-c" $ do+ it "prints summary immediately" $ do+ mvar <- newEmptyMVar+ sync <- newEmptyMVar+ threadId <- forkIO $ do+ r <- captureLines . ignoreUserInterrupt . withArgs ["--seed", "23"] . H.hspec $ do+ H.it "foo" False+ H.it "bar" $ do+ putMVar sync ()+ threadDelay 1000000+ H.it "baz" True+ putMVar mvar r+ takeMVar sync+ throwTo threadId E.UserInterrupt+ r <- takeMVar mvar+ normalizeSummary r `shouldBe` [+ ""+ , "foo FAILED [1]"+ , ""+ , "1) foo"+ , ""+ , "Randomized with seed 23"+ , ""+ ]++ it "throws UserInterrupt" $ do+ mvar <- newEmptyMVar+ sync <- newEmptyMVar+ threadId <- forkIO $ do+ silence . H.hspec $ do+ H.it "foo" $ do+ putMVar sync ()+ threadDelay 1000000+ `E.catch` putMVar mvar+ takeMVar sync+ throwTo threadId E.UserInterrupt+ takeMVar mvar `shouldReturn` E.UserInterrupt++ context "with --help" $ do+ let printHelp = withProgName "spec" . withArgs ["--help"] . H.hspec $ pure ()+ it "prints help" $ do+ r <- (captureLines . ignoreExitCode) printHelp+ r `shouldStartWith` ["Usage: spec [OPTION]..."]+ silence printHelp `shouldThrow` (== ExitSuccess)++ it "constrains lines to 80 characters" $ do+ r <- (captureLines . ignoreExitCode) printHelp+ r `shouldSatisfy` all ((<= 80) . length)+ r `shouldSatisfy` any ((78 <=) . length)++ context "with --dry-run" $ do+ let withDryRun = captureLines . withArgs ["--dry-run"] . H.hspec+ it "produces a report" $ do+ r <- withDryRun $ do+ H.it "foo" True+ H.it "bar" True+ normalizeSummary r `shouldBe` [+ ""+ , "foo"+ , "bar"+ , ""+ , "Finished in 0.0000 seconds"+ , "2 examples, 0 failures"+ ]++ it "does not verify anything" $ do+ e <- newMock+ _ <- withDryRun $ do+ H.it "foo" (mockAction e)+ H.it "bar" False+ mockCounter e `shouldReturn` 0++ it "ignores afterAll-hooks" $ do+ ref <- newIORef False+ _ <- withDryRun $ do+ H.afterAll_ (writeIORef ref True) $ do+ H.it "bar" True+ readIORef ref `shouldReturn` False++ context "with --fail-fast" $ do+ it "stops after first failure" $ do+ r <- captureLines . ignoreExitCode . withArgs ["--fail-fast", "--seed", "23"] . H.hspec $ do+ H.it "foo" True+ H.it "bar" False+ H.it "baz" False+ normalizeSummary r `shouldBe` [+ ""+ , "foo"+ , "bar FAILED [1]"+ , ""+ , "1) bar"+ , ""+ , "Randomized with seed 23"+ , ""+ , "Finished in 0.0000 seconds"+ , "2 examples, 1 failure"+ ]++ it "works for nested specs" $ do+ r <- captureLines . ignoreExitCode . withArgs ["--fail-fast", "--seed", "23"] . H.hspec $ do+ H.describe "foo" $ do+ H.it "bar" False+ H.it "baz" True+ normalizeSummary r `shouldBe` [+ ""+ , "foo"+ , " bar FAILED [1]"+ , ""+ , "1) foo bar"+ , ""+ , "Randomized with seed 23"+ , ""+ , "Finished in 0.0000 seconds"+ , "1 example, 1 failure"+ ]++ context "with --match" $ do+ it "only runs examples that match a given pattern" $ do+ e1 <- newMock+ e2 <- newMock+ e3 <- newMock+ silence . withArgs ["-m", "/bar/example"] . H.hspec $ do+ H.describe "foo" $ do+ H.describe "bar" $ do+ H.it "example 1" $ mockAction e1+ H.it "example 2" $ mockAction e2+ H.describe "baz" $ do+ H.it "example 3" $ mockAction e3+ (,,) <$> mockCounter e1 <*> mockCounter e2 <*> mockCounter e3 `shouldReturn` (1, 1, 0)++ it "can be given multiple times" $ do+ e1 <- newMock+ e2 <- newMock+ e3 <- newMock+ silence . withArgs ["-m", "foo", "-m", "baz"] . H.hspec $ do+ H.describe "foo" $ do+ H.it "example 1" $ mockAction e1+ H.describe "bar" $ do+ H.it "example 2" $ mockAction e2+ H.describe "baz" $ do+ H.it "example 3" $ mockAction e3+ (,,) <$> mockCounter e1 <*> mockCounter e2 <*> mockCounter e3 `shouldReturn` (1, 0, 1)++ context "with --format" $ do+ it "uses specified formatter" $ do+ r <- capture_ . ignoreExitCode . withArgs ["--format", "progress"] . H.hspec $ do+ H.it "foo" True+ H.it "bar" True+ H.it "baz" False+ H.it "qux" True+ r `shouldContain` "..F."++ context "when given an invalid argument" $ do+ it "prints an error message to stderr" $ do+ r <- hCapture_ [stderr] . ignoreExitCode . withArgs ["--format", "foo"] . H.hspec $ do+ H.it "foo" True+ r `shouldContain` "invalid argument `foo' for `--format'"++ context "with --qc-max-success" $ do+ it "tries QuickCheck properties specified number of times" $ do+ m <- newMock+ silence . withArgs ["--qc-max-success", "23"] . H.hspec $ do+ H.it "foo" $ property $ do+ mockAction m+ mockCounter m `shouldReturn` 23++ context "when run with --rerun" $ do+ it "takes precedence" $ do+ ["--qc-max-success", "23"] `shouldUseArgs` ((== 23) . QC.maxSuccess)+ ["--rerun", "--qc-max-success", "42"] `shouldUseArgs` ((== 42) . QC.maxSuccess)++ context "with --qc-max-size" $ do+ it "passes specified size to QuickCheck properties" $ do+ ["--qc-max-size", "23"] `shouldUseArgs` ((== 23) . QC.maxSize)++ context "with --qc-max-discard" $ do+ it "uses specified discard ratio to QuickCheck properties" $ do+ ["--qc-max-discard", "23"] `shouldUseArgs` ((== 23) . QC.maxDiscardRatio)++ context "with --seed" $ do+ it "uses specified seed" $ do+ r <- runPropFoo ["--seed", "42"]+ runPropFoo ["--seed", "42"] `shouldReturn` r++ context "when run with --rerun" $ do+ it "takes precedence" $ do+ r <- runPropFoo ["--seed", "23"]+ _ <- runPropFoo ["--seed", "42"]+ runPropFoo ["--rerun", "--seed", "23"] `shouldReturn` r++ context "when given an invalid argument" $ do+ let run = withArgs ["--seed", "foo"] . H.hspec $ do+ H.it "foo" True+ it "prints an error message to stderr" $ do+ r <- hCapture_ [stderr] (ignoreExitCode run)+ r `shouldContain` "invalid argument `foo' for `--seed'"++ it "exits with exitFailure" $ do+ hSilence [stderr] run `shouldThrow` (== ExitFailure 1)++ context "with --print-cpu-time" $ do+ it "includes used CPU time in summary" $ do+ r <- capture_ $ withArgs ["--print-cpu-time"] (H.hspec $ pure ())+ (normalizeSummary . lines) r `shouldContain` ["Finished in 0.0000 seconds, used 0.0000 seconds of CPU time"]++ context "with --html" $ do+ it "produces HTML output" $ do+ r <- capture_ . withArgs ["--html"] . H.hspec $ do+ H.it "foo" True+ r `shouldContain` "</span>"++ it "marks successful examples with CSS class hspec-success" $ do+ r <- capture_ . withArgs ["--html"] . H.hspec $ do+ H.it "foo" True+ r `shouldContain` "<span class=\"hspec-success\">foo\n</span>"++ it "marks pending examples with CSS class hspec-pending" $ do+ r <- capture_ . withArgs ["--html"] . H.hspec $ do+ H.it "foo" H.pending+ r `shouldContain` "<span class=\"hspec-pending\">foo"++ it "marks failed examples with CSS class hspec-failure" $ do+ r <- capture_ . ignoreExitCode . withArgs ["--html"] . H.hspec $ do+ H.it "foo" False+ r `shouldContain` "<span class=\"hspec-failure\">foo"++ describe "hspecResult" $ do+ it "returns a summary of the test run" $ do+ silence . H.hspecResult $ do+ H.it "foo" True+ H.it "foo" False+ H.it "foo" False+ H.it "foo" True+ H.it "foo" True+ `shouldReturn` H.Summary 5 2++ it "treats uncaught exceptions as failure" $ do+ silence . H.hspecResult $ do+ H.it "foobar" throwException+ `shouldReturn` H.Summary 1 1++ it "uses the specdoc formatter by default" $ do+ _:r:_ <- captureLines . H.hspecResult $ do+ H.describe "Foo.Bar" $ do+ H.it "some example" True+ r `shouldBe` "Foo.Bar"++ it "can use a custom formatter" $ do+ r <- capture_ . H.hspecWithResult H.defaultConfig {H.configFormatter = Just H.silent} $ do+ H.describe "Foo.Bar" $ do+ H.it "some example" True+ r `shouldBe` ""++ it "does not let escape error thunks from failure messages" $ do+ r <- silence . H.hspecResult $ do+ H.it "some example" (H.Fail $ "foobar" ++ undefined)+ r `shouldBe` H.Summary 1 1++ it "runs specs in parallel" $ do+ let n = 10+ t = 0.01+ dt = t * (fromIntegral n / 2)+ r <- timeout dt . silence . H.hspecResult . H.parallel $ do+ replicateM_ n (H.it "foo" $ sleep t)+ r `shouldBe` Just (H.Summary n 0)
+ test/Test/Hspec/Core/SpecSpec.hs view
@@ -0,0 +1,69 @@+module Test.Hspec.Core.SpecSpec (main, spec) where++import Helper++import Test.Hspec.Core.Spec (Item(..), Result(..))+import qualified Test.Hspec.Core.Runner as H+import Test.Hspec.Core.Spec (Tree(..), runSpecM)++import qualified Test.Hspec.Core.Spec as H++main :: IO ()+main = hspec spec++runSpec :: H.Spec -> IO [String]+runSpec = captureLines . H.hspecResult++spec :: Spec+spec = do+ describe "describe" $ do+ it "can be nested" $ do+ [Node foo [Node bar [Leaf _]]] <- runSpecM $ do+ H.describe "foo" $ do+ H.describe "bar" $ do+ H.it "baz" True+ (foo, bar) `shouldBe` ("foo", "bar")++ context "when no description is given" $ do+ it "uses a default description" $ do+ [Node d _] <- runSpecM (H.describe "" (pure ()))+ d `shouldBe` "(no description given)"++ describe "it" $ do+ it "takes a description of a desired behavior" $ do+ [Leaf item] <- runSpecM (H.it "whatever" True)+ itemRequirement item `shouldBe` "whatever"++ it "takes an example of that behavior" $ do+ [Leaf item] <- runSpecM (H.it "whatever" True)+ itemExample item defaultParams ($ ()) noOpProgressCallback `shouldReturn` Success++ context "when no description is given" $ do+ it "uses a default description" $ do+ [Leaf item] <- runSpecM (H.it "" True)+ itemRequirement item `shouldBe` "(unspecified behavior)"++ describe "pending" $ do+ it "specifies a pending example" $ do+ r <- runSpec $ do+ H.it "foo" H.pending+ r `shouldSatisfy` any (== " # PENDING: No reason given")++ describe "pendingWith" $ do+ it "specifies a pending example with a reason for why it's pending" $ do+ r <- runSpec $ do+ H.it "foo" $ do+ H.pendingWith "for some reason"+ r `shouldSatisfy` any (== " # PENDING: for some reason")++ describe "parallel" $ do+ it "marks examples for parallel execution" $ do+ [Leaf item] <- runSpecM . H.parallel $ H.it "whatever" True+ itemIsParallelizable item `shouldBe` True++ it "is applied recursively" $ do+ [Node _ [Node _ [Leaf item]]] <- runSpecM . H.parallel $ do+ H.describe "foo" $ do+ H.describe "bar" $ do+ H.it "baz" True+ itemIsParallelizable item `shouldBe` True
+ test/Test/Hspec/Core/UtilSpec.hs view
@@ -0,0 +1,103 @@+module Test.Hspec.Core.UtilSpec (main, spec) where++import Helper+import Control.Concurrent+import qualified Control.Exception as E++import Test.Hspec.Core.Util++main :: IO ()+main = hspec spec++spec :: Spec+spec = do+ describe "pluralize" $ do+ it "returns singular when used with 1" $ do+ pluralize 1 "thing" `shouldBe` "1 thing"++ it "returns plural when used with number greater 1" $ do+ pluralize 2 "thing" `shouldBe` "2 things"++ it "returns plural when used with 0" $ do+ pluralize 0 "thing" `shouldBe` "0 things"++ describe "formatException" $ do+ it "converts exception to string" $ do+ formatException (E.toException E.DivideByZero) `shouldBe` "ArithException (divide by zero)"++ context "when used with an IOException" $ do+ it "includes the IOErrorType" $ do+ Left e <- E.try (readFile "foo")+ formatException e `shouldBe` "IOException of type NoSuchThing (foo: openFile: does not exist (No such file or directory))"++ describe "lineBreaksAt" $ do+ it "inserts line breaks at word boundaries" $ do+ lineBreaksAt 20 "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod"+ `shouldBe` [+ "Lorem ipsum dolor"+ , "sit amet,"+ , "consectetur"+ , "adipisicing elit,"+ , "sed do eiusmod"+ ]++ describe "safeTry" $ do+ it "returns Right on success" $ do+ Right e <- safeTry (return 23 :: IO Int)+ e `shouldBe` 23++ it "returns Left on exception" $ do+ Left e <- safeTry throwException+ show e `shouldBe` "foobar"++ it "evaluates result to weak head normal form" $ do+ Left e <- safeTry (return undefined)+ show e `shouldBe` "Prelude.undefined"++ it "does not catch asynchronous exceptions" $ do+ mvar <- newEmptyMVar+ sync <- newEmptyMVar+ threadId <- forkIO $ do+ safeTry (putMVar sync () >> threadDelay 1000000) >> return ()+ `E.catch` putMVar mvar+ takeMVar sync+ throwTo threadId E.UserInterrupt+ readMVar mvar `shouldReturn` E.UserInterrupt++ describe "filterPredicate" $ do+ it "tries to match a pattern against a path" $ do+ let p = filterPredicate "foo/bar/example 1"+ p (["foo", "bar"], "example 1") `shouldBe` True+ p (["foo", "bar"], "example 2") `shouldBe` False++ it "is ambiguous" $ do+ let p = filterPredicate "foo/bar/baz"+ p (["foo", "bar"], "baz") `shouldBe` True+ p (["foo"], "bar/baz") `shouldBe` True++ it "succeeds on a partial match" $ do+ let p = filterPredicate "bar/baz"+ p (["foo", "bar", "baz"], "example 1") `shouldBe` True++ it "succeeds with a pattern that matches the message give in the failure list" $ do+ let p = filterPredicate "ModuleA.ModuleB.foo does something"+ p (["ModuleA", "ModuleB", "foo"], "does something") `shouldBe` True++ describe "formatRequirement" $ do+ it "creates a sentence from a subject and a requirement" $ do+ formatRequirement (["reverse"], "reverses a list") `shouldBe` "reverse reverses a list"++ it "creates a sentence from a subject and a requirement when the subject consits of multiple words" $ do+ formatRequirement (["The reverse function"], "reverses a list") `shouldBe` "The reverse function reverses a list"++ it "returns the requirement if no subject is given" $ do+ formatRequirement ([], "reverses a list") `shouldBe` "reverses a list"++ it "inserts context separated by commas" $ do+ formatRequirement (["reverse", "when applied twice"], "reverses a list") `shouldBe` "reverse, when applied twice, reverses a list"++ it "joins components of a subject with a dot" $ do+ formatRequirement (["Data", "List", "reverse"], "reverses a list") `shouldBe` "Data.List.reverse reverses a list"++ it "properly handles context after a subject that consists of several components" $ do+ formatRequirement (["Data", "List", "reverse", "when applied twice"], "reverses a list") `shouldBe` "Data.List.reverse, when applied twice, reverses a list"
+ test/Test/Hspec/FailureReportSpec.hs view
@@ -0,0 +1,39 @@+module Test.Hspec.FailureReportSpec (main, spec) where++import Helper++import System.IO+import Test.Hspec.FailureReport+import GHC.Paths (ghc)+import System.Process+import System.Exit++main :: IO ()+main = hspec spec++spec :: Spec+spec = do+ describe "writeFailureReport" $ do+ it "prints a warning on unexpected exceptions" $ do+ r <- hCapture_ [stderr] $ writeFailureReport (error "some error")+ r `shouldBe` "WARNING: Could not write environment variable HSPEC_FAILURES (some error)\n"++ -- GHCi needs to keep the environment on :reload, so that we can store+ -- failures there. Otherwise --rerun would not be very useful. So we add a+ -- test for that.+ describe "GHCi" $ do+ it "keeps environment variables on :reload" $ do+ let flags = ["-v0", "--interactive", "-ignore-dot-ghci"]+ (Just hIn, Just hOut, Nothing, processHandle) <- createProcess $ (proc ghc flags) {+ std_in = CreatePipe+ , std_out = CreatePipe+ }+ hPutStrLn hIn "import System.SetEnv"+ hPutStrLn hIn "setEnv \"FOO\" \"bar\""+ hPutStrLn hIn ":reload"+ hPutStrLn hIn "import System.Environment"+ hPutStrLn hIn "getEnv \"FOO\""+ hClose hIn+ r <- hGetContents hOut+ length r `seq` r `shouldBe` "\"bar\"\n"+ waitForProcess processHandle `shouldReturn` ExitSuccess
+ test/Test/Hspec/OptionsSpec.hs view
@@ -0,0 +1,44 @@+module Test.Hspec.OptionsSpec (main, spec) where++import Helper+import System.Exit++import Test.Hspec.Options hiding (parseOptions)+import qualified Test.Hspec.Options as Options++main :: IO ()+main = hspec spec++fromLeft :: Either a b -> a+fromLeft (Left a) = a+fromLeft _ = error "fromLeft: No left value!"++spec :: Spec+spec = do+ describe "parseOptions" $ do++ let parseOptions = Options.parseOptions defaultConfig "my-spec"++ it "sets configColorMode to ColorAuto" $ do+ configColorMode <$> parseOptions [] `shouldBe` Right ColorAuto++ context "with --no-color" $ do+ it "sets configColorMode to ColorNever" $ do+ configColorMode <$> parseOptions ["--no-color"] `shouldBe` Right ColorNever++ context "with --color" $ do+ it "sets configColorMode to ColorAlways" $ do+ configColorMode <$> parseOptions ["--color"] `shouldBe` Right ColorAlways++ context "with --out" $ do+ it "sets configOutputFile" $ do+ either (const Nothing) Just . configOutputFile <$> parseOptions ["--out", "foo"] `shouldBe` Right (Just "foo")++ context "with --qc-max-success" $ do+ context "when given an invalid argument" $ do+ it "returns an error message" $ do+ fromLeft (parseOptions ["--qc-max-success", "foo"]) `shouldBe` (ExitFailure 1, "my-spec: invalid argument `foo' for `--qc-max-success'\nTry `my-spec --help' for more information.\n")++ context "with --depth" $ do+ it "sets depth parameter for SmallCheck" $ do+ configSmallCheckDepth <$> parseOptions ["--depth", "23"] `shouldBe` Right 23
+ test/Test/Hspec/TimerSpec.hs view
@@ -0,0 +1,29 @@+module Test.Hspec.TimerSpec (main, spec) where++import Helper++import Test.Hspec.Timer++main :: IO ()+main = hspec spec++spec :: Spec+spec = do+ describe "timer action returned by newTimer" $ do++ let dt = 0.01++ it "returns False" $ do+ timer <- newTimer dt+ timer `shouldReturn` False++ context "after specified time" $ do+ it "returns True" $ do+ timer <- newTimer dt+ sleep dt+ timer `shouldReturn` True+ timer `shouldReturn` False+ sleep dt+ sleep dt+ timer `shouldReturn` True+ timer `shouldReturn` False