packages feed

hspec 0.3.0 → 0.4.3

raw patch · 10 files changed

+405/−260 lines, 10 filesdep +ansi-terminaldep +silentlydep +transformersPVP ok

version bump matches the API change (PVP)

Dependencies added: ansi-terminal, silently, transformers

API changes (from Hackage documentation)

- Test.Hspec: hspecB :: IO [Spec] -> IO Bool
- Test.Hspec: hspecX :: IO [Spec] -> IO a
- Test.Hspec: pureHspec :: [Spec] -> [String]
- Test.Hspec: pureHspecB :: [Spec] -> ([String], Bool)
- Test.Hspec.Internal: Fail :: String -> Result
- Test.Hspec.Internal: Pending :: String -> Result
- Test.Hspec.Internal: Spec :: String -> String -> Result -> Spec
- Test.Hspec.Internal: Success :: Result
- Test.Hspec.Internal: class SpecVerifier a
- Test.Hspec.Internal: data Result
- Test.Hspec.Internal: data Spec
- Test.Hspec.Internal: describe :: String -> [IO (String, Result)] -> IO [Spec]
- Test.Hspec.Internal: descriptions :: [IO [Spec]] -> IO [Spec]
- Test.Hspec.Internal: documentGroup :: [String] -> [Spec] -> ([String], [String])
- Test.Hspec.Internal: documentSpec :: [String] -> Spec -> ([String], String)
- Test.Hspec.Internal: documentSpecs :: [Spec] -> [String]
- Test.Hspec.Internal: failedCount :: [Spec] -> Int
- Test.Hspec.Internal: hHspec :: Handle -> IO [Spec] -> IO Bool
- Test.Hspec.Internal: hspec :: IO [Spec] -> IO ()
- Test.Hspec.Internal: hspecB :: IO [Spec] -> IO Bool
- Test.Hspec.Internal: hspecX :: IO [Spec] -> IO a
- Test.Hspec.Internal: instance Eq Result
- Test.Hspec.Internal: instance SpecVerifier Bool
- Test.Hspec.Internal: instance SpecVerifier Result
- Test.Hspec.Internal: it :: SpecVerifier a => String -> a -> IO (String, Result)
- Test.Hspec.Internal: name :: Spec -> String
- Test.Hspec.Internal: pending :: String -> Result
- Test.Hspec.Internal: pureHspec :: [Spec] -> [String]
- Test.Hspec.Internal: pureHspecB :: [Spec] -> ([String], Bool)
- Test.Hspec.Internal: quantify :: Num a => a -> String -> String
- Test.Hspec.Internal: requirement :: Spec -> String
- Test.Hspec.Internal: result :: Spec -> Result
- Test.Hspec.Internal: safely :: Result -> IO Result
- Test.Hspec.Internal: successSummary :: [Spec] -> String
- Test.Hspec.Internal: timingSummary :: Double -> String
- Test.Hspec.Internal: toExitCode :: Bool -> ExitCode
+ Test.Hspec: type Specs = [IO Spec]
+ Test.Hspec.Core: Fail :: String -> Result
+ Test.Hspec.Core: Formatter :: (Handle -> Spec -> IO ()) -> (Handle -> Spec -> [String] -> IO ()) -> (Handle -> Spec -> [String] -> IO ()) -> (Handle -> Spec -> [String] -> IO ()) -> (Handle -> [String] -> IO ()) -> (Handle -> [Spec] -> Double -> IO ()) -> Formatter
+ Test.Hspec.Core: Pending :: String -> Result
+ Test.Hspec.Core: Spec :: String -> String -> Result -> Spec
+ Test.Hspec.Core: Success :: Result
+ Test.Hspec.Core: class SpecVerifier a
+ Test.Hspec.Core: data Formatter
+ Test.Hspec.Core: data Result
+ Test.Hspec.Core: data Spec
+ Test.Hspec.Core: describe :: String -> [IO (String, Result)] -> IO [IO Spec]
+ Test.Hspec.Core: descriptions :: [IO [IO Spec]] -> IO [IO Spec]
+ Test.Hspec.Core: errorsFormatter :: Formatter -> Handle -> [String] -> IO ()
+ Test.Hspec.Core: exampleFailed :: Formatter -> Handle -> Spec -> [String] -> IO ()
+ Test.Hspec.Core: exampleGroupStarted :: Formatter -> Handle -> Spec -> IO ()
+ Test.Hspec.Core: examplePassed :: Formatter -> Handle -> Spec -> [String] -> IO ()
+ Test.Hspec.Core: examplePending :: Formatter -> Handle -> Spec -> [String] -> IO ()
+ Test.Hspec.Core: failedCount :: [Spec] -> Int
+ Test.Hspec.Core: footerFormatter :: Formatter -> Handle -> [Spec] -> Double -> IO ()
+ Test.Hspec.Core: instance Eq Result
+ Test.Hspec.Core: instance SpecVerifier Bool
+ Test.Hspec.Core: instance SpecVerifier Result
+ Test.Hspec.Core: isFailure :: Result -> Bool
+ Test.Hspec.Core: it :: SpecVerifier a => String -> a -> IO (String, Result)
+ Test.Hspec.Core: name :: Spec -> String
+ Test.Hspec.Core: pending :: String -> Result
+ Test.Hspec.Core: quantify :: Num a => a -> String -> String
+ Test.Hspec.Core: requirement :: Spec -> String
+ Test.Hspec.Core: result :: Spec -> Result
+ Test.Hspec.Core: safely :: Result -> IO Result
+ Test.Hspec.Formatters: specdoc :: Bool -> Formatter
+ Test.Hspec.Monadic: data Result
+ Test.Hspec.Monadic: data Spec
+ Test.Hspec.Monadic: describe :: String -> Writer [ItSpec] () -> Specs
+ Test.Hspec.Monadic: descriptions :: [IO [IO Spec]] -> IO [IO Spec]
+ Test.Hspec.Monadic: hHspec :: Handle -> Specs -> IO [Spec]
+ Test.Hspec.Monadic: hspec :: Specs -> IO [Spec]
+ Test.Hspec.Monadic: it :: SpecVerifier v => String -> v -> Writer [ItSpec] ()
+ Test.Hspec.Monadic: pending :: String -> Result
+ Test.Hspec.Monadic: type Specs = Writer [IO [IO Spec]] ()
+ Test.Hspec.Runner: describe :: String -> [IO (String, Result)] -> IO [IO Spec]
+ Test.Hspec.Runner: hHspec :: Handle -> IO Specs -> IO [Spec]
+ Test.Hspec.Runner: hHspecWithFormat :: Formatter -> Handle -> IO Specs -> IO [Spec]
+ Test.Hspec.Runner: hspec :: IO Specs -> IO [Spec]
+ Test.Hspec.Runner: it :: SpecVerifier a => String -> a -> IO (String, Result)
+ Test.Hspec.Runner: type Specs = [IO Spec]
- Test.Hspec: describe :: String -> [IO (String, Result)] -> IO [Spec]
+ Test.Hspec: describe :: String -> [IO (String, Result)] -> IO [IO Spec]
- Test.Hspec: descriptions :: [IO [Spec]] -> IO [Spec]
+ Test.Hspec: descriptions :: [IO [IO Spec]] -> IO [IO Spec]
- Test.Hspec: hHspec :: Handle -> IO [Spec] -> IO Bool
+ Test.Hspec: hHspec :: Handle -> IO Specs -> IO [Spec]
- Test.Hspec: hspec :: IO [Spec] -> IO ()
+ Test.Hspec: hspec :: IO Specs -> IO [Spec]

Files

Specs.hs view
@@ -1,22 +1,31 @@  module Specs where -import Test.Hspec.Internal+import Test.Hspec+import Test.Hspec.Runner (hHspecWithFormat)+import Test.Hspec.Core (Spec(..),Result(..),quantify,failedCount)+import Test.Hspec.Formatters (specdoc) import Test.Hspec.QuickCheck import Test.Hspec.HUnit () import System.IO+import System.IO.Silently import System.Environment import System.Exit+import Data.List (isPrefixOf) import qualified Test.HUnit as HUnit +toExitCode :: Bool -> ExitCode+toExitCode True  = ExitSuccess+toExitCode False = ExitFailure 1+ main :: IO () main = do   ar <- getArgs-  b <- case ar of+  ss <- case ar of     ["README"] -> withFile "README" WriteMode (\ h -> hPutStrLn h preable >> hHspec h specs)     [filename] -> withFile filename WriteMode (\ h -> hHspec h specs)-    _          -> hspecB specs-  exitWith $ toExitCode b+    _          -> hspec specs+  exitWith $ toExitCode (failedCount ss == 0)  preable :: String preable = unlines [@@ -37,12 +46,12 @@     "Step 2, write whatever you are describing",     "> myabs n = undefined",     "",-    "Step 3, watch your examples fail",+    "Step 3, watch your examples fail with red text",     "> hspec specs",     "myabs",-    " x returns the original number when given a positive input [1]",-    " x returns a positive number when given a negative input [2]",-    " x returns zero when given zero [3]",+    " x returns the original number when given a positive input FAILED [1]",+    " x returns a positive number when given a negative input FAILED [2]",+    " x returns zero when given zero FAILED [3]",     "",     "1) myabs returns the original number when given a positive input FAILED",     "Prelude.undefined",@@ -60,7 +69,7 @@     "Step 4, implement your desired behavior",     "> myabs n = if n < 0 then negate n else n",     "",-    "Step 5, watch your examples pass",+    "Step 5, watch your examples pass with green text",     "> hspec specs",     "myabs",     " - returns the original number when given a positive input",@@ -76,24 +85,21 @@     "",     "Here's the report of hspec's behavior:" ] -specs :: IO [Spec]+specs :: IO Specs specs = do-  exampleSpecs <- describe "Example" [-    it "pass" (Success),-    it "fail 1" (Fail "fail message"),-    it "pending" (Pending "pending message"),-    it "fail 2" (HUnit.assertEqual "assertEqual test" 1 (2::Int)),-    it "exceptions" (undefined :: Bool),-    it "quickcheck" (property $ \ i -> i == (i+1::Integer))]+  let testSpecs = describe "Example" [+          it "pass" (Success),+          it "fail 1" (Fail "fail message"),+          it "pending" (Pending "pending message"),+          it "fail 2" (HUnit.assertEqual "assertEqual test" 1 (2::Int)),+          it "exceptions" (undefined :: Bool),+          it "quickcheck" (property $ \ i -> i == (i+1::Integer))] -  let report = pureHspec exampleSpecs+  (reportContents, exampleSpecs) <- capture $ hHspecWithFormat (specdoc False) stdout testSpecs -  -- uncomment the next line to aid in debugging-  --mapM_ putStrLn $ ["-- START example specs --"] ++ report ++ ["-- END example specs --"]+  let report = lines reportContents -  -- the real specs   descriptions [-     describe "the \"describe\" function" [         it "takes a description of what the behavior is for"             ((=="Example") . name . head $ exampleSpecs),@@ -112,7 +118,7 @@             (True),          it "will treat exceptions as failures"-            (any (==" x exceptions [3]") report)+            (any (==" x exceptions FAILED [3]") report)     ],     describe "the \"hspec\" function" [         it "displays a header for each thing being described"@@ -120,20 +126,16 @@          it "displays one row for each behavior"             (HUnit.assertEqual "" 29 (length report)),-            -- 19 = group header + behaviors + blank lines + time + error messsages + pending message + example/failures footer          it "displays a '-' for successfull examples"             (any (==" - pass") report),          it "displays an 'x' for failed examples"-            (any (==" x fail 1 [1]") report),+            (any (==" x fail 1 FAILED [1]") report), -        it "displays a list of failed examples"+        it "displays a detailed list of failed examples"             (any (=="1) Example fail 1 FAILED") report), -        it "displays available details for failed examples"-            (any (=="fail message") report),-         it "displays a '-' for pending examples"             (any (==" - pending") report ), @@ -141,12 +143,12 @@             (any (=="     # pending message") report ),          it "summarizes the time it takes to finish"-            (any (=="Finished in 0.0000 seconds") (pureHspec exampleSpecs)),+            (any ("Finished in " `isPrefixOf`) report),          it "summarizes the number of examples and failures"-            (any (=="6 examples, 4 failures") (pureHspec exampleSpecs)),+            (any (=="6 examples, 4 failures") report), -        it "outputs to stdout"+        it "outputs failed examples in red, pending in yellow, and passing in green"             (True)     ],     describe "Bool as an example" [@@ -162,14 +164,13 @@          it "will show the failed assertion text if available (e.g. assertBool)"             (HUnit.TestCase $ do-              innerSpecs <- describe "" [ it "" (HUnit.assertBool "trivial" False)]-              let innerReport = pureHspec innerSpecs-              HUnit.assertBool "should find assertion text" $ any (=="trivial") innerReport),+              (innerReport, _) <- capture $ hspec $ describe "" [ it "" (HUnit.assertBool "trivial" False)]+              HUnit.assertBool "should find assertion text" $ any (=="trivial") (lines innerReport)),          it "will show the failed assertion expected and actual values if available (e.g. assertEqual)"             (HUnit.TestCase $ do-              innerSpecs <- describe "" [ it "" (HUnit.assertEqual "trivial" (1::Int) 2)]-              let innerReport = pureHspec innerSpecs+              (innerReportContents, _) <- capture $ hspec $ describe "" [ it "" (HUnit.assertEqual "trivial" (1::Int) 2)]+              let innerReport = lines innerReportContents               HUnit.assertBool "should find assertion text" $ any (=="trivial") innerReport               HUnit.assertBool "should find 'expected: 1'" $ any (=="expected: 1") innerReport               HUnit.assertBool "should find ' but got: 2'" $ any (==" but got: 2") innerReport)@@ -180,24 +181,13 @@          it "will show what falsified it"             (any (=="0") report)-        ],+    ],     describe "pending as an example" [         it "is specified with the \"pending\" function and an explanation"             (pending "message" == Pending "message"),          it "accepts a message to display in the report"             (any (== "     # pending message") report)-        ],-    describe "hspecB" [-        it "returns true if no examples failed"-            (HUnit.TestCase $ do-              ss <- describe "" [it "" Success]-              HUnit.assertEqual "no errors" True (snd $ pureHspecB ss)),--        it "returns false if any examples failed"-            (HUnit.TestCase $ do-              ss <- describe "" [it "" (Fail "test")]-              HUnit.assertEqual "one error" False (snd $ pureHspecB ss))     ],     describe "quantify (an internal function)" [         it "returns an amount and a word given an amount and word"@@ -212,3 +202,4 @@         it "returns a plural word given the number 0"             (quantify (0::Int) "thing" == "0 things")     ]]+
hspec.cabal view
@@ -1,5 +1,5 @@ name: hspec-version: 0.3.0+version: 0.4.3 cabal-version: -any build-type: Custom license: BSD3@@ -7,11 +7,12 @@ copyright: (c) 2011 Trystan Spangler maintainer: trystan.s@comcast.net build-depends: HUnit >=1 && <=2, QuickCheck >=2.4.0.1 && <=2.5,-               base >=4 && <=5+               base >=4 && <=5, silently == 1.1.1,+               ansi-terminal == 0.5.5, transformers >= 0.2.0 && < 0.3.0 stability: experimental homepage: https://github.com/trystan/hspec package-url: https://github.com/trystan/hspec-bug-reports: mailto:trystan.s@comcast.net+bug-reports: https://github.com/trystan/hspec/issues synopsis: Behavior Driven Development for Haskell description: Behavior Driven Development for Haskell              .@@ -22,10 +23,13 @@ data-files: data-dir: "" extra-source-files: ./Specs.hs ./src/Test/Hspec/HUnit.hs-                    ./src/Test/Hspec/Internal.hs ./src/Test/Hspec/QuickCheck.hs+                    ./src/Test/Hspec/Runner.hs ./src/Test/Hspec/Monadic.hs+                    ./src/Test/Hspec/Formatters.hs+                    ./src/Test/Hspec/Core.hs ./src/Test/Hspec/QuickCheck.hs extra-tmp-files:-exposed-modules: Test.Hspec Test.Hspec.HUnit Test.Hspec.Internal-                 Test.Hspec.QuickCheck+exposed-modules: Test.Hspec Test.Hspec.HUnit Test.Hspec.Core+                 Test.Hspec.Runner Test.Hspec.Monadic+                 Test.Hspec.Formatters Test.Hspec.QuickCheck exposed: True buildable: True build-tools:
src/Test/Hspec.hs view
@@ -73,11 +73,13 @@ -- module Test.Hspec (   -- types-  Spec(), Result(),+  Spec(), Result(),Specs,   -- the main api   describe, it, hspec, pending, descriptions,   -- alternate "runner" functions-  hHspec, hspecX, hspecB, pureHspec, pureHspecB+  hHspec ) where -import Test.Hspec.Internal+import Test.Hspec.Core+import Test.Hspec.Runner+
+ src/Test/Hspec/Core.hs view
@@ -0,0 +1,108 @@+{-# OPTIONS -XFlexibleInstances #-}++-- | This module contains the core types, constructors, classes,+-- instances, and utility functions common to hspec.+--+module Test.Hspec.Core where++import System.IO+import System.IO.Silently+import Control.Exception+import Control.Monad (liftM)++-- | The result of running an example.+data Result = Success | Fail String | Pending String+  deriving Eq++-- | Everything needed to specify and show a specific behavior.+data Spec = Spec {+                 -- | What is being tested, usually the name of a type.+                 name::String,+                 -- | The specific behavior being tested.+                 requirement::String,+                 -- | The status of this behavior.+                 result::Result }++data Formatter = Formatter { exampleGroupStarted :: Handle -> Spec -> IO (),+                             examplePassed   :: Handle -> Spec -> [String] -> IO (),+                             exampleFailed   :: Handle -> Spec -> [String] -> IO (),+                             examplePending  :: Handle -> Spec -> [String] -> IO (),+                             errorsFormatter :: Handle -> [String] -> IO (),+                             footerFormatter :: Handle -> [Spec] -> Double -> IO () }+++-- | Create a set of specifications for a specific type being described.+-- Once you know what you want specs for, use this.+--+-- > describe "abs" [+-- >   it "returns a positive number given a negative number"+-- >     (abs (-1) == 1)+-- >   ]+--+describe :: String                -- ^ The name of what is being described, usually a function or type.+         -> [IO (String, Result)] -- ^ A list of behaviors and examples, created by a list of 'it'.+         -> IO [IO Spec]+describe n = return . map (>>= \ (req, res) -> return (Spec n req res))++-- | Combine a list of descriptions.+descriptions :: [IO [IO Spec]] -> IO [IO Spec]+descriptions = liftM concat . sequence++-- | Evaluate a Result. Any exceptions (undefined, etc.) are treated as failures.+safely :: Result -> IO Result+safely f = Control.Exception.catch ok failed+  where ok = silence $ f `seq` return f+        failed e = return $ Fail (show (e :: SomeException))++-- | Anything that can be used as an example of a behavior.+class SpecVerifier a where+  -- | Create a description and example of a behavior, a list of these+  -- is used by 'describe'. Once you know what you want to specify, use this.+  --+  -- > describe "closeEnough" [+  -- >   it "is true if two numbers are almost the same"+  -- >     (1.001 `closeEnough` 1.002),+  -- >+  -- >   it "is false if two numbers are not almost the same"+  -- >     (not $ 1.001 `closeEnough` 1.003)+  -- >   ]+  --+  it :: String           -- ^ A description of this behavior.+     -> a                -- ^ An example for this behavior.+     -> IO (String, Result)++instance SpecVerifier Bool where+  it description example = do+    r <- safely (if example then Success else Fail "")+    return (description, r)++instance SpecVerifier Result where+  it description example = do+    r <- safely example+    return (description, r)++-- | Declare an example as not successful or failing but pending some other work.+-- If you want to report on a behavior but don't have an example yet, use this.+--+-- > describe "fancyFormatter" [+-- >   it "can format text in a way that everyone likes"+-- >     (pending "waiting for clarification from the designers")+-- >   ]+--+pending :: String  -- ^ An explanation for why this behavior is pending.+        -> Result+pending = Pending++++failedCount :: [Spec] -> Int+failedCount ss = length $ filter (isFailure.result) ss++isFailure :: Result -> Bool+isFailure (Fail _) = True+isFailure _        = False++-- | Create a more readable display of a quantity of something.+quantify :: Num a => a -> String -> String+quantify 1 s = "1 " ++ s+quantify n s = show n ++ " " ++ s ++ "s"
+ src/Test/Hspec/Formatters.hs view
@@ -0,0 +1,59 @@+-- | This module contains formaatters that take a set of specs and write to a given handle.+-- They follow a structure similar to RSpec formatters.+--+module Test.Hspec.Formatters (+  specdoc+) where++import Test.Hspec.Core+import System.IO+import Data.List (intersperse)+import Text.Printf+import Control.Monad (when)+import System.Console.ANSI++specdoc :: Bool -> Formatter+specdoc useColor = Formatter {+  exampleGroupStarted = \ h spec -> do+    when useColor (normalColor h)+    hPutStr h ('\n' : name spec ++ "\n"),++  examplePassed = \ h spec _ -> do+    when useColor (passColor h)+    hPutStrLn h $ " - " ++ requirement spec,++  exampleFailed = \ h spec errors -> do+    when useColor (failColor h)+    hPutStrLn h $ " x " ++ requirement spec ++ " FAILED [" ++ (show $ (length errors) + 1) ++ "]",++  examplePending = \ h spec _ -> do+    when useColor (pendingColor h)+    let (Pending s) = result spec+    hPutStrLn h $ " - " ++ requirement spec ++ "\n     # " ++ s,++  errorsFormatter = \ h errors -> do+    when useColor (failColor h)+    mapM_ (hPutStrLn h) ("" : intersperse "" errors)+    when (not $ null errors) (hPutStrLn h ""),++  footerFormatter = \ h specs time -> do+    when useColor (if failedCount specs == 0 then passColor h else failColor h)+    hPutStrLn h $ printf "Finished in %1.4f seconds" time+    hPutStrLn h ""+    hPutStr   h $ quantify (length specs) "example" ++ ", "+    hPutStrLn h $ quantify (failedCount specs) "failure"+    when useColor (normalColor h)+  }+++failColor :: Handle -> IO()+failColor h = hSetSGR h [ SetColor Foreground Dull Red ]++passColor :: Handle -> IO()+passColor h = hSetSGR h [ SetColor Foreground Dull Green ]++pendingColor :: Handle -> IO()+pendingColor h = hSetSGR h [ SetColor Foreground Dull Yellow ]++normalColor :: Handle -> IO()+normalColor h = hSetSGR h [ Reset ]
src/Test/Hspec/HUnit.hs view
@@ -16,7 +16,8 @@ module Test.Hspec.HUnit ( ) where -import Test.Hspec.Internal+import System.IO.Silently+import Test.Hspec.Core import qualified Test.HUnit as HU import Data.List (intersperse) @@ -25,10 +26,11 @@  instance SpecVerifier HU.Test where   it description example = do-    (counts, fails) <- HU.runTestText HU.putTextToShowS example-    if HU.errors counts + HU.failures counts == 0-      then return (description, Success)-      else return (description, Fail (details $ fails ""))+    (counts, fails) <- silence $ HU.runTestText HU.putTextToShowS example+    let r' = if HU.errors counts + HU.failures counts == 0+             then Success+             else Fail (details $ fails "")+    return (description, r')  details :: String -> String details = concat . intersperse "\n" . tail . init . lines
− src/Test/Hspec/Internal.hs
@@ -1,190 +0,0 @@-{-# OPTIONS -XFlexibleInstances #-}--module Test.Hspec.Internal where--import System.IO-import System.Exit-import Data.List (mapAccumL, groupBy, intersperse)-import System.CPUTime (getCPUTime)-import Text.Printf-import Control.Exception-import Control.Monad (liftM)---- | The result of running an example.-data Result = Success | Fail String | Pending String-  deriving Eq----- | Everything needed to specify and show a specific behavior.-data Spec = Spec {-                 -- | What is being tested, usually the name of a type.-                 name::String,-                 -- | The specific behavior being tested.-                 requirement::String,-                 -- | The status of this behavior.-                 result::Result }----- | Create a set of specifications for a specific type being described.--- Once you know what you want specs for, use this.------ > describe "abs" [--- >   it "returns a positive number given a negative number"--- >     (abs (-1) == 1)--- >   ]----describe :: String                -- ^ The name of what is being described, usually a function or type.-         -> [IO (String, Result)] -- ^ A list of behaviors and examples, created by a list of 'it'.-         -> IO [Spec]-describe n ss = do-  ss' <- sequence ss-  return $ map (\ (req, res) -> Spec n req res) ss'---- | Combine a list of descriptions.-descriptions :: [IO [Spec]] -> IO [Spec]-descriptions = liftM concat . sequence---- | Evaluate a Result. Any exceptions (undefined, etc.) are treated as failures.-safely :: Result -> IO Result-safely f = Control.Exception.catch ok failed-  where ok = f `seq` return f-        failed e = return $ Fail (show (e :: SomeException))----- | Anything that can be used as an example of a behavior.-class SpecVerifier a where-  -- | Create a description and example of a behavior, a list of these-  -- is used by 'describe'. Once you know what you want to specify, use this.-  ---  -- > describe "closeEnough" [-  -- >   it "is true if two numbers are almost the same"-  -- >     (1.001 `closeEnough` 1.002),-  -- >-  -- >   it "is false if two numbers are not almost the same"-  -- >     (not $ 1.001 `closeEnough` 1.003)-  -- >   ]-  ---  it :: String           -- ^ A description of this behavior.-     -> a                -- ^ An example for this behavior.-     -> IO (String, Result)---instance SpecVerifier Bool where-  it description example = do-    r <- safely (if example then Success else Fail "")-    return (description, r)--instance SpecVerifier Result where-  it description example = return (description, example)----- | Declare an example as not successful or failing but pending some other work.--- If you want to report on a behavior but don't have an example yet, use this.------ > describe "fancyFormatter" [--- >   it "can format text in a way that everyone likes"--- >     (pending "waiting for clarification from the designers")--- >   ]----pending :: String  -- ^ An explanation for why this behavior is pending.-        -> Result-pending = Pending----- | Create a document of the given specs.-documentSpecs :: [Spec] -> [String]-documentSpecs specs = lines $ unlines $ concat report ++ [""] ++ intersperse "" errors-  where organize = groupBy (\ a b -> name a == name b)-        (errors, report) = mapAccumL documentGroup [] $ organize specs---- | Create a document of the given group of specs.-documentGroup :: [String] -> [Spec] -> ([String], [String])-documentGroup errors specGroup = (errors', "" : name (head specGroup) : report)-  where (errors', report) = mapAccumL documentSpec errors specGroup---- | Create a document of the given spec.-documentSpec :: [String] -> Spec -> ([String], String)-documentSpec errors spec = case result spec of-                Success    -> (errors, " - " ++ requirement spec)-                Fail s     -> (errors ++ [errorDetails s], " x " ++ requirement spec ++ errorTag)-                Pending s  -> (errors, " - " ++ requirement spec ++ "\n     # " ++ s)-    where errorTag = " [" ++ (show $ length errors + 1) ++ "]"-          errorDetails s  = concat [ show (length errors + 1), ") ", name spec,-                                     " ",  requirement spec, " FAILED",-                                     if null s then "" else "\n" ++ s ]----- | Create a summary of how long it took to run the examples.-timingSummary :: Double -> String-timingSummary t = printf "Finished in %1.4f seconds" (t / (10.0^(12::Integer)) :: Double)--failedCount :: [Spec] -> Int-failedCount ss = length $ filter (isFailure.result) ss-  where-    isFailure (Fail _) = True-    isFailure _        = False---- | Create a summary of how many specs exist and how many examples failed.-successSummary :: [Spec] -> String-successSummary ss = quantify (length ss) "example" ++ ", " ++ quantify (failedCount ss) "failure"----- | Create a document of the given specs.--- This does not track how much time it took to check the examples. If you want--- a description of each spec and don't need to know how long it tacks to check,--- use this.-pureHspec :: [Spec]   -- ^ The specs you are interested in.-          -> [String]-pureHspec = fst . pureHspecB---pureHspecB :: [Spec]   -- ^ The specs you are interested in.-          -> ([String], Bool)-pureHspecB ss = (report, failedCount ss == 0)-  where report = documentSpecs ss ++ [ "", timingSummary 0, "", successSummary ss]----- | Create a document of the given specs and write it to stdout.--- This does track how much time it took to check the examples. Use this if--- you want a description of each spec and do need to know how long it tacks--- to check the examples or want to write to stdout.-hspec :: IO [Spec] -> IO ()-hspec ss = hspecB ss >> return ()---- | Same as 'hspec' except it returns a bool indicating if all examples ran without failures-hspecB :: IO [Spec] -> IO Bool-hspecB = hHspec stdout---- | Same as 'hspec' except the program exits successfull if all examples ran without failures or--- with an errorcode of 1 if any examples failed.-hspecX :: IO [Spec] -> IO a-hspecX ss = hspecB ss >>= exitWith . toExitCode--toExitCode :: Bool -> ExitCode-toExitCode True  = ExitSuccess-toExitCode False = ExitFailure 1----- | Create a document of the given specs and write it to the given handle.--- This does track how much time it took to check the examples. Use this if--- you want a description of each spec and do need to know how long it tacks--- to check the examples or want to write to a file or other handle.------ > writeReport filename specs = withFile filename WriteMode (\ h -> hHspec h specs)----hHspec :: Handle     -- ^ A handle for the stream you want to write to.-       -> IO [Spec]  -- ^ The specs you are interested in.-       -> IO Bool-hHspec h ss = do-  t0 <- getCPUTime-  ss' <- ss-  mapM_ (hPutStrLn h) $ documentSpecs ss'-  t1 <- getCPUTime-  mapM_ (hPutStrLn h) [ "", timingSummary (fromIntegral $ t1 - t0), "", successSummary ss']-  return $ failedCount ss' == 0----- | Create a more readable display of a quantity of something.-quantify :: Num a => a -> String -> String-quantify 1 s = "1 " ++ s-quantify n s = show n ++ " " ++ s ++ "s"
+ src/Test/Hspec/Monadic.hs view
@@ -0,0 +1,104 @@++-- | This module contains the runners that take a set of specs, specified in a monadic style, evaluate their examples, and+-- report to a given handle.+--+-- The three functions you'll use the most are 'hspec', 'describe', and 'it'. Here is an+-- example of functions that format and unformat phone numbers and the specs for them.+--+-- > import Test.Hspec+-- > import Test.Hspec.QuickCheck+-- > import Test.Hspec.HUnit+-- > import Test.QuickCheck hiding (property)+-- > import Test.HUnit+-- >+-- > main = hspec mySpecs+--+-- Since the specs are often used to tell you what to implement, it's best to start with+-- undefined functions. Once we have some specs, then you can implement each behavior+-- one at a time, ensuring that each behavior is met and there is no undocumented behavior.+--+-- > unformatPhoneNumber :: String -> String+-- > unformatPhoneNumber number = undefined+-- >+-- > formatPhoneNumber :: String -> String+-- > formatPhoneNumber number = undefined+--+-- The "describe" function takes a list of behaviors and examples bound together with the "it" function+--+-- > mySpecs = describe "unformatPhoneNumber" $ do+--+-- A boolean expression can act as a behavior's example.+--+-- >   it "removes dashes, spaces, and parenthesies"+-- >       (unformatPhoneNumber "(555) 555-1234" == "5555551234")+--+-- The "pending" function marks a behavior as pending an example. The example doesn't count as failing.+--+-- >   it "handles non-US phone numbers"+-- >       (pending "need to look up how other cultures format phone numbers")+--+-- An HUnit "Test" can act as a behavior's example. (must import @Test.Hspec.HUnit@)+--+-- >   it "removes the \"ext\" prefix of the extension"+-- >       (TestCase $ let expected = "5555551234135"+-- >                       actual   = unformatPhoneNumber "(555) 555-1234 ext 135"+-- >                   in assertEqual "remove extension" expected actual)+--+-- An @IO()@ action is treated like an HUnit "TestCase". (must import @Test.Hspec.HUnit@)+--+-- >   it "converts letters to numbers"+-- >       (do+-- >         let expected = "6862377"+-- >         let actual   = unformatPhoneNumber "NUMBERS"+-- >         assertEqual "letters to numbers" expected actual)+--+-- The "property" function allows a QuickCheck property to act as an example. (must import @Test.Hspec.HUnit@)+--+-- >   it "can add and remove formatting without changing the number"+-- >       (property $ forAll phoneNumber $+-- >         \ n -> unformatPhoneNumber (formatPhoneNumber n) == n)+-- >+-- > phoneNumber :: Gen String+-- > phoneNumber = do+-- >   nums <- elements [7,10,11,12,13,14,15]+-- >   vectorOf nums (elements "0123456789")+--+module Test.Hspec.Monadic (+  -- types+  Spec(), Result(),Specs,+  -- the main api+  describe, it, hspec, pending, descriptions,+  -- alternate "runner" functions+  hHspec+) where++import System.IO+import Test.Hspec.Core hiding (describe,it)+import qualified Test.Hspec.Core as Core+import qualified Test.Hspec.Runner as Runner++import Control.Monad.Trans.Writer (Writer, execWriter, tell)++type ItSpec = IO (String, Result)++type Specs = Writer [IO [IO Spec]] ()++-- | Create a document of the given specs and write it to stdout.+hspec :: Specs -> IO [Spec]+hspec = Runner.hspec . runSpecM++-- | Create a document of the given specs and write it to the given handle.+--+-- > writeReport filename specs = withFile filename WriteMode (\ h -> hHspec h specs)+--+hHspec :: Handle -> Specs -> IO [Spec]+hHspec h = Runner.hHspec h . runSpecM++runSpecM :: Specs -> IO [IO Spec]+runSpecM specs = descriptions $ execWriter specs++describe :: String -> Writer [ItSpec] () -> Specs+describe label action = tell [Core.describe label (execWriter action)]++it :: SpecVerifier v => String -> v -> Writer [ItSpec] ()+it label action = tell [Core.it label action]
src/Test/Hspec/QuickCheck.hs view
@@ -16,7 +16,8 @@   property ) where -import Test.Hspec.Internal+import System.IO.Silently+import Test.Hspec.Core import qualified Test.QuickCheck as QC  data QuickCheckProperty a = QuickCheckProperty a@@ -26,9 +27,10 @@  instance QC.Testable t => SpecVerifier (QuickCheckProperty t) where   it description (QuickCheckProperty prop) = do-    r <- QC.quickCheckResult prop-    case r of-      QC.Success {}           -> return (description, Success)-      f@(QC.Failure {})       -> return (description, Fail (QC.output f))-      g@(QC.GaveUp {})        -> return (description, Fail ("Gave up after " ++ quantify (QC.numTests g) "test" ))-      QC.NoExpectedFailure {} -> return (description, Fail ("No expected failure"))+    r <- silence $ QC.quickCheckResult prop+    let r' = case r of+              QC.Success {}           -> Success+              f@(QC.Failure {})       -> Fail (QC.output f)+              g@(QC.GaveUp {})        -> Fail ("Gave up after " ++ quantify (QC.numTests g) "test" )+              QC.NoExpectedFailure {} -> Fail ("No expected failure")+    return (description, r')
+ src/Test/Hspec/Runner.hs view
@@ -0,0 +1,63 @@++-- | This module contains the runners that take a set of specs, evaluate their examples, and+-- report to a given handle.+--+module Test.Hspec.Runner (+  Specs, hspec, hHspec, hHspecWithFormat, describe, it+) where++import Test.Hspec.Core+import Test.Hspec.Formatters+import System.IO+import System.CPUTime (getCPUTime)+import Control.Monad (when)++type Specs = [IO Spec]++-- | Evaluate and print the result of checking the spec examples.+runFormatter :: Formatter -> Handle -> String -> [String] -> Specs -> IO [Spec]+runFormatter formatter h _     errors []     = do+  errorsFormatter formatter h (reverse errors)+  return []+runFormatter formatter h group errors (iospec:ioss) = do+  spec <- iospec+  when (group /= name spec) (exampleGroupStarted formatter h spec)+  case result spec of+    (Success  ) -> examplePassed formatter h spec errors+    (Fail _   ) -> exampleFailed formatter h spec errors+    (Pending _) -> examplePending formatter h spec errors+  let errors' = if isFailure (result spec)+                then errorDetails spec (length errors) : errors+                else errors+  specs <- runFormatter formatter h (name spec) errors' ioss+  return $ spec : specs++errorDetails :: Spec -> Int -> String+errorDetails spec i = case result spec of+  (Fail s   ) -> concat [ show (i + 1), ") ", name spec, " ",  requirement spec, " FAILED", if null s then "" else "\n" ++ s ]+  _           -> ""+++-- | Create a document of the given specs and write it to stdout.+hspec :: IO Specs -> IO [Spec]+hspec ss = hHspec stdout ss++-- | Create a document of the given specs and write it to the given handle.+--+-- > writeReport filename specs = withFile filename WriteMode (\ h -> hHspec h specs)+--+hHspec :: Handle -> IO Specs -> IO [Spec]+hHspec h = hHspecWithFormat (specdoc $ h == stdout) h++-- | Create a document of the given specs and write it to the given handle.+-- THIS IS LIKELY TO CHANGE+hHspecWithFormat :: Formatter -> Handle -> IO Specs -> IO [Spec]+hHspecWithFormat formatter h ss = do+  t0 <- getCPUTime+  ioSpecList <- ss+  specList <- (runFormatter formatter) h "" [] ioSpecList+  t1 <- getCPUTime+  let runTime = ((fromIntegral $ t1 - t0) / (10.0^(12::Integer)) :: Double)+  (footerFormatter formatter) h specList runTime+  return specList+