hspec 1.7.2.1 → 1.8.0
raw patch · 33 files changed
+290/−363 lines, 33 filesdep ~QuickCheckdep ~hspec-expectationsdep ~hspec-meta
Dependency ranges changed: QuickCheck, hspec-expectations, hspec-meta
Files
- hspec-discover/src/Config.hs +1/−1
- hspec-discover/src/Run.hs +45/−114
- hspec-discover/test-data/lhs-spec/FooSpec.lhs +0/−0
- hspec-discover/test-data/lhs-spec/Spec.hs +0/−0
- hspec-discover/test-data/no-intermediate-specs/Foo/Bar/BazSpec.hs +0/−0
- hspec-discover/test-data/prefix-name/Foo/BazSpec.hs +0/−0
- hspec-discover/test-data/prefix-name/FooBar/BazSpec.hs +0/−0
- hspec-discover/test-data/prefix-name/FooBarSpec.hs +0/−0
- hspec-discover/test-data/prefix-name/FooSpec.hs +0/−0
- hspec-discover/test-data/several-specs/BarSpec.hs +0/−0
- hspec-discover/test-data/several-specs/BazSpec.hs +0/−0
- hspec-discover/test-data/several-specs/FooSpec.hs +0/−0
- hspec-discover/test-data/single-spec-nested/Foo/BarSpec.hs +0/−0
- hspec-discover/test-data/single-spec/FooSpec.hs +0/−0
- hspec-discover/test-data/single-spec/Spec.hs +0/−0
- hspec-discover/test/ConfigSpec.hs +3/−3
- hspec-discover/test/RunSpec.hs +64/−72
- hspec.cabal +4/−16
- src/Test/Hspec.hs +9/−97
- src/Test/Hspec/Compat.hs +0/−11
- src/Test/Hspec/Core.hs +15/−5
- src/Test/Hspec/Core/QuickCheckUtil.hs +28/−0
- src/Test/Hspec/Core/Type.hs +17/−11
- src/Test/Hspec/HUnit.hs +1/−1
- src/Test/Hspec/QuickCheck.hs +33/−1
- src/Test/Hspec/Runner.hs +1/−1
- src/Test/Hspec/Runner/Eval.hs +2/−2
- src/Test/Hspec/Util.hs +1/−1
- test/Helper.hs +5/−2
- test/Test/Hspec/Core/TypeSpec.hs +29/−3
- test/Test/Hspec/FormattersSpec.hs +0/−3
- test/Test/Hspec/HUnitSpec.hs +2/−2
- test/Test/HspecSpec.hs +30/−17
hspec-discover/src/Config.hs view
@@ -22,7 +22,7 @@ ] usage :: String -> String-usage prog = "\nUsage: " ++ prog ++ " SRC CUR DST [--nested] [--formatter=FORMATTER]\n"+usage prog = "\nUsage: " ++ prog ++ " SRC CUR DST [--formatter=FORMATTER]\n" parseConfig :: String -> [String] -> Either String Config parseConfig prog args = case getOpt Permute options args of
hspec-discover/src/Run.hs view
@@ -1,17 +1,26 @@ {-# LANGUAGE TypeSynonymInstances, FlexibleInstances, OverloadedStrings #-} {-# OPTIONS_GHC -fno-warn-orphans #-} -- | A preprocessor that finds and combines specs.-module Run where+module Run (+ run++-- exported for testing+, importList+, fileToSpec+, findSpecs+, getFilesRecursive+, driverWithFormatter+, moduleName+) where import Control.Monad import Control.Applicative import Data.List import Data.Maybe import Data.String-import Data.Function import System.Environment import System.Exit import System.IO-import System.Directory+import System.Directory (doesDirectoryExist, getDirectoryContents, doesFileExist) import System.FilePath hiding (combine) import Config@@ -19,6 +28,8 @@ instance IsString ShowS where fromString = showString +type Spec = String+ run :: [String] -> IO () run args_ = do name <- getProgName@@ -28,25 +39,22 @@ hPutStrLn stderr err exitFailure Right c -> do+ when (configNested c) (hPutStrLn stderr "hspec-discover: WARNING - The `--nested' flag is deprecated and will be removed in a future release!") specs <- findSpecs src writeFile dst (mkSpecModule src c specs) _ -> do hPutStrLn stderr (usage name) exitFailure -mkSpecModule :: FilePath -> Config -> [SpecNode] -> String+mkSpecModule :: FilePath -> Config -> [Spec] -> String mkSpecModule src c nodes = ( "{-# LINE 1 " . shows src . " #-}" . showString "module Main where\n" . importList nodes . maybe driver (driverWithFormatter (null nodes)) (configFormatter c)- . format nodes+ . formatSpecs nodes ) "\n" where- format- | configNested c = formatSpecsNested- | otherwise = formatSpecs- driver = showString "import Test.Hspec\n" . showString "main :: IO ()\n"@@ -64,125 +72,48 @@ moduleName = reverse . dropWhile (== '.') . dropWhile (/= '.') . reverse -- | Generate imports for a list of specs.-importList :: [SpecNode] -> ShowS-importList = go ""+importList :: [Spec] -> ShowS+importList = foldr (.) "" . map f where- go :: ShowS -> [SpecNode] -> ShowS- go current = foldr (.) "" . map (f current)- f current (SpecNode name inhabited children) = this . go (current . showString name . ".") children- where- this- | inhabited = "import qualified " . current . showString name . "Spec\n"- | otherwise = id+ f :: Spec -> ShowS+ f name = "import qualified " . showString name . "Spec\n" -- | Combine a list of strings with (>>). sequenceS :: [ShowS] -> ShowS sequenceS = foldr (.) "" . intersperse " >> " -- | Convert a list of specs to code.-formatSpecs :: [SpecNode] -> ShowS+formatSpecs :: [Spec] -> ShowS formatSpecs xs | null xs = "return ()" | otherwise = sequenceS (map formatSpec xs) -- | Convert a spec to code.-formatSpec :: SpecNode -> ShowS-formatSpec = sequenceS . go ""- where- go :: String -> SpecNode -> [ShowS]- go current (SpecNode name inhabited children) = addThis $ concatMap (go (current ++ name ++ ".")) children- where- addThis :: [ShowS] -> [ShowS]- addThis- | inhabited = ("describe " . shows (current ++ name) . " " . showString (current ++ name ++ "Spec.spec") :)- | otherwise = id---- | Convert a list of specs to code.------ Hierarchical modules are mapped to nested specs.-formatSpecsNested :: [SpecNode] -> ShowS-formatSpecsNested xs- | null xs = "return ()"- | otherwise = sequenceS (map formatSpecNested xs)---- | Convert a spec to code.------ Hierarchical modules are mapped to nested specs.-formatSpecNested :: SpecNode -> ShowS-formatSpecNested = go ""- where- go current (SpecNode name inhabited children) = "describe " . shows name . " (" . specs . ")"- where- specs :: ShowS- specs = (sequenceS . addThis . map (go (current . showString name . "."))) children- addThis- | inhabited = ((current . showString name . "Spec.spec") :)- | otherwise = id--data SpecNode = SpecNode String Bool [SpecNode]- deriving (Eq, Show)--specNodeName :: SpecNode -> String-specNodeName (SpecNode name _ _) = name--specNodeInhabited :: SpecNode -> Bool-specNodeInhabited (SpecNode _ inhabited _) = inhabited--specNodeChildren :: SpecNode -> [SpecNode]-specNodeChildren (SpecNode _ _ children) = children--getFilesAndDirectories :: FilePath -> IO ([FilePath], [FilePath])-getFilesAndDirectories dir = do- c <- filter (`notElem` ["..", "."]) <$> getDirectoryContents dir- dirs <- filterM (doesDirectoryExist . (dir </>)) c- files <- filterM (doesFileExist . (dir </>)) c- return (dirs, files)+formatSpec :: Spec -> ShowS+formatSpec name = "describe " . shows name . " " . showString name . "Spec.spec" --- | Find specs relative to given source file.------ The source file itself is not considered.-findSpecs :: FilePath -> IO [SpecNode]+findSpecs :: FilePath -> IO [Spec] findSpecs src = do let (dir, file) = splitFileName src- (dirs, files) <- getFilesAndDirectories dir- go dir (dirs, filter (/= file) files)- where- go :: FilePath -> ([FilePath], [FilePath]) -> IO [SpecNode]- go base (dirs, files) = do- nestedSpecs <- forM dirs $ \d -> do- let dir = base </> d- SpecNode d False <$> (getFilesAndDirectories dir >>= go dir)- (return . filterSpecs . combineSpecs) (specsFromFiles files ++ nestedSpecs)- where- specsFromFiles = catMaybes . map specFromFile- where- suffixes :: [String]- suffixes = ["Spec.hs","Spec.lhs"]-- specFromFile :: FilePath -> Maybe SpecNode- specFromFile file = msum $ map (specFromFile_ file) suffixes-- specFromFile_ :: FilePath -> String -> Maybe SpecNode- specFromFile_ file suffix- | suffix `isSuffixOf` file = Just $ SpecNode (dropEnd (length suffix) file) True []- | otherwise = Nothing-- dropEnd :: Int -> [a] -> [a]- dropEnd n = reverse . drop n . reverse-- -- remove empty leafs- filterSpecs :: [SpecNode] -> [SpecNode]- filterSpecs = filter (\x -> specNodeInhabited x || (not . null . specNodeChildren) x)+ mapMaybe fileToSpec . filter (/= file) <$> getFilesRecursive dir - -- sort specs, and merge nodes with the same name- combineSpecs :: [SpecNode] -> [SpecNode]- combineSpecs = foldr f [] . sortBy (compare `on` specNodeName)- where- f x@(SpecNode n1 _ _) (y@(SpecNode n2 _ _):acc) | n1 == n2 = x `combine` y : acc- f x acc = x : acc+fileToSpec :: FilePath -> Maybe String+fileToSpec f = intercalate "." . reverse <$> case reverse $ splitDirectories f of+ x:xs -> case stripSuffix "Spec.hs" x <|> stripSuffix "Spec.lhs" x of+ Nothing -> Nothing+ Just "" -> Nothing+ Just ys -> Just (ys : xs)+ _ -> Nothing+ where+ stripSuffix :: Eq a => [a] -> [a] -> Maybe [a]+ stripSuffix suffix str = reverse <$> stripPrefix (reverse suffix) (reverse str) - x `combine` y = SpecNode name inhabited children- where- name = specNodeName x- inhabited = specNodeInhabited x || specNodeInhabited y- children = specNodeChildren x ++ specNodeChildren y+getFilesRecursive :: FilePath -> IO [FilePath]+getFilesRecursive baseDir = sort <$> go []+ where+ go :: FilePath -> IO [FilePath]+ go dir = do+ c <- map (dir </>) . filter (`notElem` [".", ".."]) <$> getDirectoryContents (baseDir </> dir)+ dirs <- filterM (doesDirectoryExist . (baseDir </>)) c >>= mapM go+ files <- filterM (doesFileExist . (baseDir </>)) c+ return (files ++ concat dirs)
− hspec-discover/test-data/lhs-spec/FooSpec.lhs
− hspec-discover/test-data/lhs-spec/Spec.hs
− hspec-discover/test-data/no-intermediate-specs/Foo/Bar/BazSpec.hs
− hspec-discover/test-data/prefix-name/Foo/BazSpec.hs
− hspec-discover/test-data/prefix-name/FooBar/BazSpec.hs
− hspec-discover/test-data/prefix-name/FooBarSpec.hs
− hspec-discover/test-data/prefix-name/FooSpec.hs
− hspec-discover/test-data/several-specs/BarSpec.hs
− hspec-discover/test-data/several-specs/BazSpec.hs
− hspec-discover/test-data/several-specs/FooSpec.hs
− hspec-discover/test-data/single-spec-nested/Foo/BarSpec.hs
− hspec-discover/test-data/single-spec/FooSpec.hs
− hspec-discover/test-data/single-spec/Spec.hs
hspec-discover/test/ConfigSpec.hs view
@@ -22,16 +22,16 @@ parse ["--foo"] `shouldBe` (Left . unlines) [ "hspec-discover: unrecognized option `--foo'" , ""- , "Usage: hspec-discover SRC CUR DST [--nested] [--formatter=FORMATTER]"+ , "Usage: hspec-discover SRC CUR DST [--formatter=FORMATTER]" ] it "returns error message on unexpected argument" $ do parse ["foo"] `shouldBe` (Left . unlines) [ "hspec-discover: unexpected argument `foo'" , ""- , "Usage: hspec-discover SRC CUR DST [--nested] [--formatter=FORMATTER]"+ , "Usage: hspec-discover SRC CUR DST [--formatter=FORMATTER]" ] context "when option is given multiple times" $ do- it "the last occurrence takes precedence" $ do+ it "gives the last occurrence precedence" $ do parse ["--formatter", "foo", "--formatter", "bar"] `shouldBe` Right (defaultConfig {configFormatter = Just "bar"})
hspec-discover/test/RunSpec.hs view
@@ -2,47 +2,82 @@ import Test.Hspec.Meta -import Data.List (intercalate)+import Control.Applicative+import System.IO+import System.Directory+import System.FilePath+import Data.List (intercalate, sort) import Run main :: IO () main = hspec spec +withTempFile :: (FilePath -> IO a) -> IO a+withTempFile action = do+ dir <- getTemporaryDirectory+ (file, h) <- openTempFile dir ""+ hClose h+ action file <* removeFile file++ spec :: Spec spec = do- describe "findSpecs" $ do- context "when specs are not nested" $ do- it "finds a single spec" $ do- findSpecs "hspec-discover/test-data/single-spec/Spec.hs" `shouldReturn` [SpecNode "Foo" True []]+ describe "run" $ do+ it "generates test driver" $ withTempFile $ \f -> do+ run ["hspec-discover/test-data/nested-spec/Spec.hs", "", f]+ readFile f `shouldReturn` unlines [+ "{-# LINE 1 \"hspec-discover/test-data/nested-spec/Spec.hs\" #-}module Main where"+ , "import qualified Foo.Bar.BazSpec"+ , "import qualified Foo.BarSpec"+ , "import qualified FooSpec"+ , "import Test.Hspec"+ , "main :: IO ()"+ , "main = hspec $ describe \"Foo.Bar.Baz\" Foo.Bar.BazSpec.spec >> describe \"Foo.Bar\" Foo.BarSpec.spec >> describe \"Foo\" FooSpec.spec"+ ] - it "finds several specs" $ do- findSpecs "hspec-discover/test-data/several-specs/Spec.hs" `shouldReturn` [SpecNode "Bar" True [], SpecNode "Baz" True [], SpecNode "Foo" True []]+ it "generates test driver for an empty directory" $ withTempFile $ \f -> do+ run ["hspec-discover/test-data/empty-dir/Spec.hs", "", f]+ readFile f `shouldReturn` unlines [+ "{-# LINE 1 \"hspec-discover/test-data/empty-dir/Spec.hs\" #-}module Main where"+ , "import Test.Hspec"+ , "main :: IO ()"+ , "main = hspec $ return ()"+ ] - it "discovers .lhs files" $ do- findSpecs "hspec-discover/test-data/lhs-spec/Spec.hs" `shouldReturn` [SpecNode "Foo" True []]+ describe "getFilesRecursive" $ do+ it "recursively returns all file entries of a given directory" $ do+ getFilesRecursive "hspec-discover/test-data" `shouldReturn` sort [+ "empty-dir/Foo/Bar/Baz/.placeholder"+ , "nested-spec/Foo/Bar/BazSpec.hs"+ , "nested-spec/Foo/BarSpec.hs"+ , "nested-spec/FooSpec.hs"+ ] - context "when specs are nested" $ do- it "finds a single spec" $ do- findSpecs "hspec-discover/test-data/single-spec-nested/Spec.hs" `shouldReturn` [SpecNode "Foo" False [SpecNode "Bar" True []]]+ describe "fileToSpec" $ do+ it "converts path to spec name" $ do+ fileToSpec "FooSpec.hs" `shouldBe` Just "Foo" - it "properly groups nested specs" $ do- findSpecs "hspec-discover/test-data/nested-spec/Spec.hs" `shouldReturn` [SpecNode "Foo" True [SpecNode "Bar" True [SpecNode "Baz" True []]]]+ it "rejects spec with empty name" $ do+ fileToSpec "Spec.hs" `shouldBe` Nothing - context "given a nested spec, without specs at the intermediate nodes" $ do- it "finds a single spec" $ do- findSpecs "hspec-discover/test-data/no-intermediate-specs/Spec.hs" `shouldReturn` [SpecNode "Foo" False [SpecNode "Bar" False [SpecNode "Baz" True []]]]+ it "works for lhs files" $ do+ fileToSpec "FooSpec.lhs" `shouldBe` Just "Foo" - context "given a nested specs, with specs at the intermediate nodes" $ do- context "with two top-level specs, where one spec name is a prefix of the other" $ do- it "properly sorts specs" $ do- findSpecs "hspec-discover/test-data/prefix-name/Spec.hs" `shouldReturn`- [SpecNode "Foo" True [SpecNode "Baz" True []], SpecNode "FooBar" True [SpecNode "Baz" True []]]+ it "returns Nothing for invalid spec name" $ do+ fileToSpec "foo" `shouldBe` Nothing - context "when there are no specs" $ do- it "returns an empty list" $ do- findSpecs "hspec-discover/test-data/empty-dir/Spec.hs" `shouldReturn` []+ context "when path has directory component" $ do+ it "converts path to spec name" $ do+ fileToSpec ("Foo" </> "Bar" </> "BazSpec.hs") `shouldBe` Just "Foo.Bar.Baz" + it "rejects spec with empty name" $ do+ fileToSpec ("Foo" </> "Bar" </> "Spec.hs") `shouldBe` Nothing++ describe "findSpecs" $ do+ it "finds specs" $ do+ findSpecs "hspec-discover/test-data/nested-spec/Spec.hs" `shouldReturn` ["Foo.Bar.Baz","Foo.Bar","Foo"]+ describe "driverWithFormatter" $ do it "generates a test driver that uses a custom formatter" $ do driverWithFormatter False "Some.Module.formatter" "" `shouldBe` intercalate "\n" [@@ -57,52 +92,9 @@ it "returns the module name of an fully qualified identifier" $ do moduleName "Some.Module.someId" `shouldBe` "Some.Module" - describe "formatSpec" $ do- it "generates code for a spec" $- formatSpec (SpecNode "Foo" True []) "" `shouldBe` "describe \"Foo\" FooSpec.spec"-- it "generates code for a nested spec" $- formatSpec (SpecNode "Foo" True [SpecNode "Bar" True [SpecNode "Baz" True []]]) "" `shouldBe`- "describe \"Foo\" FooSpec.spec >> describe \"Foo.Bar\" Foo.BarSpec.spec >> describe \"Foo.Bar.Baz\" Foo.Bar.BazSpec.spec"-- describe "formatSpecs" $ do- it "generates code for a list of specs" $ do- formatSpecs [SpecNode "Bar" True [], SpecNode "Baz" True [], SpecNode "Foo" True []] "" `shouldBe`- "describe \"Bar\" BarSpec.spec >> describe \"Baz\" BazSpec.spec >> describe \"Foo\" FooSpec.spec"-- it "generates code for an empty list" $ do- formatSpecs [] "" `shouldBe`- "return ()"-- describe "formatSpecNested" $ do- it "generates code for a spec" $- formatSpecNested (SpecNode "Foo" True []) "" `shouldBe` "describe \"Foo\" (FooSpec.spec)"-- it "generates code for a nested spec" $- formatSpecNested (SpecNode "Foo" True [SpecNode "Bar" True [SpecNode "Baz" True []]]) "" `shouldBe`- "describe \"Foo\" (FooSpec.spec >> describe \"Bar\" (Foo.BarSpec.spec >> describe \"Baz\" (Foo.Bar.BazSpec.spec)))"-- describe "formatSpecsNested" $ do- it "generates code for a list of specs" $ do- formatSpecsNested [SpecNode "Bar" True [], SpecNode "Baz" True [], SpecNode "Foo" True []] "" `shouldBe`- "describe \"Bar\" (BarSpec.spec) >> describe \"Baz\" (BazSpec.spec) >> describe \"Foo\" (FooSpec.spec)"-- it "generates code for an empty list" $ do- formatSpecsNested [] "" `shouldBe`- "return ()"- describe "importList" $ do- it "generates imports for a spec" $ do- importList [SpecNode "Foo" True []] "" `shouldBe` "import qualified FooSpec\n"-- it "generates imports for a nested spec" $- importList [SpecNode "Foo" True [SpecNode "Bar" True [SpecNode "Baz" True []]]] "" `shouldBe`- "import qualified FooSpec\nimport qualified Foo.BarSpec\nimport qualified Foo.Bar.BazSpec\n"- it "generates imports for a list of specs" $ do- importList [SpecNode "Bar" True [], SpecNode "Baz" True [], SpecNode "Foo" True []] "" `shouldBe`- "import qualified BarSpec\nimport qualified BazSpec\nimport qualified FooSpec\n"-- it "generates imports for a nested spec that has no specs at the intermediate nodes" $ do- importList [SpecNode "Foo" False [SpecNode "Bar" False [SpecNode "Baz" True []]]] "" `shouldBe`- "import qualified Foo.Bar.BazSpec\n"+ importList ["Foo", "Bar"] "" `shouldBe` unlines [+ "import qualified FooSpec"+ , "import qualified BarSpec"+ ]
hspec.cabal view
@@ -1,5 +1,5 @@ name: hspec-version: 1.7.2.1+version: 1.8.0 license: MIT license-file: LICENSE copyright: (c) 2011-2013 Simon Hengel,@@ -24,23 +24,10 @@ -- find hspec-discover/test-data/ -type f extra-source-files:- hspec-discover/test-data/single-spec/FooSpec.hs- hspec-discover/test-data/single-spec/Spec.hs- hspec-discover/test-data/single-spec-nested/Foo/BarSpec.hs hspec-discover/test-data/nested-spec/FooSpec.hs hspec-discover/test-data/nested-spec/Foo/Bar/BazSpec.hs hspec-discover/test-data/nested-spec/Foo/BarSpec.hs- hspec-discover/test-data/lhs-spec/FooSpec.lhs- hspec-discover/test-data/lhs-spec/Spec.hs- hspec-discover/test-data/several-specs/BarSpec.hs- hspec-discover/test-data/several-specs/FooSpec.hs- hspec-discover/test-data/several-specs/BazSpec.hs hspec-discover/test-data/empty-dir/Foo/Bar/Baz/.placeholder- hspec-discover/test-data/prefix-name/FooSpec.hs- hspec-discover/test-data/prefix-name/Foo/BazSpec.hs- hspec-discover/test-data/prefix-name/FooBarSpec.hs- hspec-discover/test-data/prefix-name/FooBar/BazSpec.hs- hspec-discover/test-data/no-intermediate-specs/Foo/Bar/BazSpec.hs source-repository head type: git@@ -62,7 +49,7 @@ , HUnit >= 1.2.5 , QuickCheck >= 2.5.1 , quickcheck-io- , hspec-expectations == 0.3.3.*+ , hspec-expectations == 0.4.0.* exposed-modules: Test.Hspec Test.Hspec.Core@@ -75,6 +62,7 @@ Test.Hspec.Util Test.Hspec.Compat Test.Hspec.Core.Type+ Test.Hspec.Core.QuickCheckUtil Test.Hspec.Config Test.Hspec.Options Test.Hspec.FailureReport@@ -119,7 +107,7 @@ , quickcheck-io , hspec-expectations - , hspec-meta >= 1.7.1+ , hspec-meta >= 1.8.0 , process , ghc-paths
src/Test/Hspec.hs view
@@ -1,23 +1,12 @@ -- | -- Stability: stable ----- Hspec is a framework for /Behavior-Driven Development (BDD)/ in Haskell. BDD--- is an approach to software development that combines Test-Driven--- Development, Domain-Driven Design, and Acceptance Test-Driven Planning.--- Hspec helps you do the TDD part of that equation, focusing on the--- documentation and design aspects of TDD.+-- Hspec is a testing library for Haskell. ----- Hspec (and the preceding intro) are based on the Ruby library RSpec. Much of--- what applies to RSpec also applies to Hspec. Hspec ties together--- /textual descriptions/ of behavior and /examples/ for that behavior. The--- examples serve as test cases for the specified behavior. Hspec's mechanism--- for examples is extensible. Support for QuickCheck properties and HUnit--- tests is included in the core package.+-- This is the library reference for Hspec.+-- The <http://hspec.github.io/ User's Manual> contains more in-depth+-- documentation. module Test.Hspec (---- * Introduction--- $intro- -- * Types Spec , Example@@ -45,73 +34,9 @@ import Test.Hspec.Runner import Test.Hspec.HUnit () import Test.Hspec.Expectations+import Test.Hspec.Core (mapSpecItem) import qualified Test.Hspec.Core as Core -import Data.IORef-import Control.Applicative---- $intro------ 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.QuickCheck--- > import Test.HUnit--- >--- > main :: IO ()--- > main = hspec spec------ 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 = undefined--- >--- > formatPhoneNumber :: String -> String--- > formatPhoneNumber = undefined------ The 'describe' function takes a list of behaviors and examples bound--- together with the 'it' function------ > spec :: Spec--- > spec = do--- > describe "unformatPhoneNumber" $ do------ A `Bool` can be used as an 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.HUnit.Lang.Assertion' can be used as an example.------ > it "converts letters to numbers" $ do--- > let expected = "6862377"--- > actual = unformatPhoneNumber "NUMBERS"--- > actual @?= expected--------- A QuickCheck 'Test.QuickCheck.Property' can be used as an example.------ > it "can add and remove formatting without changing the number" $ property $--- > forAll phoneNumber $ \n -> unformatPhoneNumber (formatPhoneNumber n) == n--- >--- > phoneNumber :: Gen String--- > phoneNumber = do--- > n <- elements [7,10,11,12,13,14,15]--- > vectorOf n (elements "0123456789")-- -- | Combine a list of specs into a larger spec. describe :: String -> Spec -> Spec describe label action = fromSpecList [Core.describe label (runSpecM action)]@@ -151,29 +76,16 @@ -- | Run examples of given spec in parallel. parallel :: Spec -> Spec-parallel = mapSpecItem $ \_ r e -> SpecItem True r e+parallel = mapSpecItem $ \item -> item {itemIsParallelizable = True} -- | Run a custom action before every spec item. before :: IO () -> Spec -> Spec-before action = mapSpecItem $ \b r e -> SpecItem b r (\params -> action *> e params)+before action = around (action >>) -- | Run a custom action after every spec item. after :: IO () -> Spec -> Spec-after action = mapSpecItem $ \b r e -> SpecItem b r (\params -> e params <* action)+after action = around (>> action) -- | Run a custom action before and/or after every spec item. around :: (IO () -> IO ()) -> Spec -> Spec-around action = mapSpecItem $ \b r e -> SpecItem b r (\params -> wrap (e params))- where- wrap e = do- ref <- newIORef (Fail "")- action (e >>= writeIORef ref)- readIORef ref--mapSpecItem :: (Bool -> String -> (Params -> IO Result) -> SpecTree) -> Spec -> Spec-mapSpecItem f = fromSpecList . map go . runSpecM- where- go :: SpecTree -> SpecTree- go spec = case spec of- SpecItem b r e -> f b r e- SpecGroup d es -> SpecGroup d (map go es)+around a2 = mapSpecItem $ \item -> item {itemExample = \params a1 -> itemExample item params (a1 . a2)}
src/Test/Hspec/Compat.hs view
@@ -2,7 +2,6 @@ module Test.Hspec.Compat ( showType , showFullType-, isUserInterrupt , readMaybe , module Data.IORef #if !MIN_VERSION_base(4,6,0)@@ -11,7 +10,6 @@ ) where import Data.Typeable (Typeable, typeOf, typeRepTyCon)-import qualified Test.QuickCheck as QC import Text.Read import Data.IORef @@ -71,12 +69,3 @@ #else show t #endif--isUserInterrupt :: QC.Result -> Bool-isUserInterrupt r = case r of-#if MIN_VERSION_QuickCheck(2,6,0)- QC.Failure {QC.interrupted = x} -> x-#else- QC.Failure {QC.reason = "Exception: 'user interrupt'"} -> True-#endif- _ -> False
src/Test/Hspec/Core.hs view
@@ -18,11 +18,13 @@ -- * Internal representation of a spec tree , SpecTree (..)+, Item (..)+, mapSpecItem+, modifyParams , describe , it -- * Deprecated types and functions-, Spec , Specs , hspecB , hspecX@@ -33,13 +35,24 @@ import Control.Applicative import System.IO (Handle) -import Test.Hspec.Core.Type hiding (Spec)+import Test.Hspec.Core.Type import qualified Test.Hspec.Runner as Runner import Test.Hspec.Runner (Summary(..), Config(..), defaultConfig) hspecWith :: Config -> [SpecTree] -> IO Summary hspecWith c = Runner.hspecWith c . fromSpecList +mapSpecItem :: (Item -> Item) -> Spec -> Spec+mapSpecItem f = fromSpecList . map go . runSpecM+ where+ go :: SpecTree -> SpecTree+ go spec = case spec of+ SpecItem item -> SpecItem (f item)+ SpecGroup d es -> SpecGroup d (map go es)++modifyParams :: (Params -> Params) -> Spec -> Spec+modifyParams f = mapSpecItem $ \item -> item {itemExample = \p -> (itemExample item) (f p)}+ {-# DEPRECATED hspecX "use `Test.Hspec.Runner.hspec` instead" #-} -- since 1.2.0 hspecX :: [SpecTree] -> IO () hspecX = hspec@@ -55,9 +68,6 @@ {-# DEPRECATED hHspec "use `Test.Hspec.Runner.hspecWith` instead" #-} -- since 1.4.0 hHspec :: Handle -> [SpecTree] -> IO Summary hHspec h = hspecWith defaultConfig {configHandle = Left h}--{-# DEPRECATED Spec "use `SpecTree` instead" #-} -- since 1.4.0-type Spec = SpecTree {-# DEPRECATED Specs "use `[SpecTree]` instead" #-} -- since 1.4.0 type Specs = [SpecTree]
+ src/Test/Hspec/Core/QuickCheckUtil.hs view
@@ -0,0 +1,28 @@+{-# LANGUAGE CPP #-}+module Test.Hspec.Core.QuickCheckUtil where++import Data.IORef+import Test.QuickCheck hiding (Result(..))+import Test.QuickCheck as QC+import Test.QuickCheck.Property hiding (Result(..))+import qualified Test.QuickCheck.Property as QCP+import Test.QuickCheck.IO ()+import Control.Applicative++aroundProperty :: (IO () -> IO ()) -> Property -> Property+aroundProperty action p = MkProp . aroundRose action . unProp <$> p++aroundRose :: (IO () -> IO ()) -> Rose QCP.Result -> Rose QCP.Result+aroundRose action r = ioRose $ do+ ref <- newIORef (return QCP.succeeded)+ action (reduceRose r >>= writeIORef ref)+ readIORef ref++isUserInterrupt :: QC.Result -> Bool+isUserInterrupt r = case r of+#if MIN_VERSION_QuickCheck(2,6,0)+ QC.Failure {QC.interrupted = x} -> x+#else+ QC.Failure {QC.reason = "Exception: 'user interrupt'"} -> True+#endif+ _ -> False
src/Test/Hspec/Core/Type.hs view
@@ -5,6 +5,7 @@ , runSpecM , fromSpecList , SpecTree (..)+, Item (..) , Example (..) , Result (..) , Params (..)@@ -34,10 +35,9 @@ import qualified Test.QuickCheck.Property as QCP import qualified Test.QuickCheck.IO () -import Test.Hspec.Compat (isUserInterrupt)+import Test.Hspec.Core.QuickCheckUtil import Control.DeepSeq (deepseq) - type Spec = SpecM () -- | A writer monad for `SpecTree` forests.@@ -75,8 +75,14 @@ -- | Internal representation of a spec. data SpecTree = SpecGroup String [SpecTree]- | SpecItem Bool String (Params -> IO Result)+ | SpecItem Item +data Item = Item {+ itemIsParallelizable :: Bool+, itemRequirement :: String+, itemExample :: Params -> (IO () -> IO ()) -> IO Result+}+ -- | The @describe@ function combines a list of specs into a larger spec. describe :: String -> [SpecTree] -> SpecTree describe s = SpecGroup msg@@ -87,7 +93,7 @@ -- | Create a spec item. it :: Example a => String -> a -> SpecTree-it s e = SpecItem False msg (`evaluateExample` e)+it s e = SpecItem $ Item False msg (evaluateExample e) where msg | null s = "(unspecified behavior)"@@ -95,23 +101,23 @@ -- | A type class for examples. class Example a where- evaluateExample :: Params -> a -> IO Result+ evaluateExample :: a -> Params -> (IO () -> IO ()) -> IO Result instance Example Bool where- evaluateExample _ b = if b then return Success else return (Fail "")+ evaluateExample b _ _ = if b then return Success else return (Fail "") instance Example Expectation where- evaluateExample _ action = (action >> return Success) `E.catches` [- E.Handler (\(HUnitFailure err) -> (return . Fail) err)+ 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- evaluateExample _ = return+ evaluateExample r _ _ = return r instance Example QC.Property where- evaluateExample c p = do- r <- QC.quickCheckWithResult (paramsQuickCheckArgs c) {QC.chatty = False} (QCP.callback progressCallback p)+ evaluateExample p c action = do+ r <- QC.quickCheckWithResult (paramsQuickCheckArgs c) {QC.chatty = False} (QCP.callback progressCallback $ aroundProperty action p) when (isUserInterrupt r) $ do E.throwIO E.UserInterrupt
src/Test/Hspec/HUnit.hs view
@@ -12,7 +12,7 @@ -- | This instance is deprecated, use `Test.Hspec.HUnit.fromHUnitTest` instead! instance Example Test where- evaluateExample _ test = do+ evaluateExample test _ _ = do (counts, fails) <- HU.runTestText HU.putTextToShowS test let r = if HU.errors counts + HU.failures counts == 0 then Success
src/Test/Hspec/QuickCheck.hs view
@@ -1,19 +1,24 @@ -- | -- Stability: provisional module Test.Hspec.QuickCheck (+-- * Params+ modifyMaxSuccess+, modifyMaxDiscardRatio+, modifyMaxSize -- * Re-exports from QuickCheck -- | -- Previous versions of Hspec provided a distinct `property` combinator, but -- it's now possible to use QuickCheck's `property` instead. For backward -- compatibility we now re-export QuickCheck's `property`, but it is advisable -- to import it from "Test.QuickCheck" instead.- property+, property -- * Shortcuts , prop ) where import Test.QuickCheck import Test.Hspec+import Test.Hspec.Core (Params(..), modifyParams) -- | -- > prop ".." $@@ -25,3 +30,30 @@ -- > .. prop :: Testable prop => String -> prop -> Spec prop s = it s . property++-- | Use a modified `maxSuccess` for given spec.+modifyMaxSuccess :: (Int -> Int) -> Spec -> Spec+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) -> Spec -> Spec+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) -> Spec -> Spec+modifyMaxSize = modifyArgs . modify+ where+ modify :: (Int -> Int) -> Args -> Args+ modify f args = args {maxSize = f (maxSize args)}++modifyArgs :: (Args -> Args) -> Spec -> Spec+modifyArgs = modifyParams . modify+ where+ modify :: (Args -> Args) -> Params -> Params+ modify f p = p {paramsQuickCheckArgs = f (paramsQuickCheckArgs p)}
src/Test/Hspec/Runner.hs view
@@ -53,7 +53,7 @@ goSpec :: [String] -> SpecTree -> Maybe SpecTree goSpec groups spec = case spec of- SpecItem _ requirement _ -> guard (p (groups, requirement)) >> return spec+ SpecItem item -> guard (p (groups, itemRequirement item)) >> return spec SpecGroup group specs -> case goSpecs (groups ++ [group]) specs of [] -> Nothing xs -> Just (SpecGroup group xs)
src/Test/Hspec/Runner/Eval.hs view
@@ -40,8 +40,8 @@ defer (exampleGroupStarted formatter n (reverse rGroups) group) forM_ (zip [0..] xs) (queueSpec (group : rGroups)) defer (exampleGroupDone formatter)- queueSpec rGroups (_, SpecItem isParallelizable requirement e) =- queueExample isParallelizable (reverse rGroups, requirement) e+ queueSpec rGroups (_, SpecItem (Item isParallelizable requirement e)) =+ queueExample isParallelizable (reverse rGroups, requirement) (`e` id) queueExample :: Bool -> Path -> (Params -> IO Result) -> IO () queueExample isParallelizable path e
src/Test/Hspec/Util.hs view
@@ -85,7 +85,7 @@ getEnv :: String -> IO (Maybe String) getEnv key = either (const Nothing) Just <$> safeTry (Environment.getEnv key) --- ensure that lines are not longer then given `n`, insert line beraks at word+-- 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
test/Helper.hs view
@@ -34,7 +34,8 @@ import Test.Hspec.Meta import Test.QuickCheck hiding (Result(..)) -import qualified Test.Hspec.Core as H (Result(..), Params(..), fromSpecList, SpecTree(..))+import qualified Test.Hspec as H+import qualified Test.Hspec.Core as H (Params(..), Item(..), mapSpecItem) import qualified Test.Hspec.Runner as H ignoreExitCode :: IO () -> IO ()@@ -73,6 +74,8 @@ shouldUseArgs :: [String] -> (Args -> Bool) -> Expectation shouldUseArgs args p = do spy <- newIORef (H.paramsQuickCheckArgs defaultParams)- let spec = H.fromSpecList [H.SpecItem False "foo" $ \params -> writeIORef spy (H.paramsQuickCheckArgs params) >> return (H.Fail "example failed")]+ let interceptArgs item = item {H.itemExample = \params action -> writeIORef spy (H.paramsQuickCheckArgs params) >> H.itemExample item params action}+ spec = H.mapSpecItem interceptArgs $+ H.it "foo" False (silence . ignoreExitCode . withArgs args . H.hspec) spec readIORef spy >>= (`shouldSatisfy` p)
test/Test/Hspec/Core/TypeSpec.hs view
@@ -3,6 +3,7 @@ import Helper import Mock import Data.List+import Data.IORef import Control.Exception (AsyncException(..), throwIO) import qualified Test.Hspec.Core.Type as H hiding (describe, it)@@ -13,8 +14,11 @@ main = hspec spec evaluateExample :: H.Example e => e -> IO H.Result-evaluateExample = H.evaluateExample (defaultParams {H.paramsQuickCheckArgs = (H.paramsQuickCheckArgs defaultParams) {replay = Just (read "", 0)}})+evaluateExample e = H.evaluateExample e (defaultParams {H.paramsQuickCheckArgs = (H.paramsQuickCheckArgs defaultParams) {replay = Just (read "", 0)}}) id +evaluateExampleWith :: H.Example e => (IO () -> IO ()) -> e -> IO H.Result+evaluateExampleWith action e = H.evaluateExample e (defaultParams {H.paramsQuickCheckArgs = (H.paramsQuickCheckArgs defaultParams) {replay = Just (read "", 0)}}) action+ spec :: Spec spec = do describe "evaluateExample" $ do@@ -38,6 +42,17 @@ 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@@ -62,6 +77,17 @@ , "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` 200+ context "when used with shouldBe" $ do it "shows what falsified it" $ do H.Fail r <- evaluateExample $ property $ \x y -> x + y `shouldBe` (x * y :: Int)@@ -78,7 +104,7 @@ evaluateExample p `shouldThrow` (== UserInterrupt) it "propagates exceptions" $ do- pendingWith "this probaly needs a patch to QuickCheck"+ pendingWith "this probably needs a patch to QuickCheck" -- evaluateExample (property $ (error "foobar" :: Int -> Bool)) `shouldThrow` errorCall "foobar" context "when used with `pending`" $ do@@ -99,7 +125,7 @@ (reverse . reverse) xs `shouldBe` (xs :: [Int]) mockCounter e `shouldReturn` 100 - it "can be used with expecatations/HUnit assertions" $ do+ 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
test/Test/Hspec/FormattersSpec.hs view
@@ -154,9 +154,6 @@ r <- runSpec testSpec r `shouldSatisfy` any (== " # PENDING: pending message") - it "outputs failed examples in red, pending in yellow, and successful in green" $ do- pending- context "same as failed_examples" $ do failed_examplesSpec H.progress
test/Test/Hspec/HUnitSpec.hs view
@@ -4,7 +4,7 @@ import qualified Test.Hspec as H import qualified Test.Hspec.Runner as H-import Test.Hspec.Core.Type (SpecTree(..), runSpecM)+import Test.Hspec.Core.Type (SpecTree(..), Item(..), runSpecM) import Test.Hspec.HUnit import Test.HUnit @@ -24,7 +24,7 @@ go :: SpecTree -> Tree go x = case x of SpecGroup s xs -> Group s (map go xs)- SpecItem _ s _ -> Example s+ SpecItem item -> Example (itemRequirement item) spec :: Spec spec = do
test/Test/HspecSpec.hs view
@@ -2,10 +2,10 @@ import Helper import Mock+import Data.IORef import Data.List (isPrefixOf) -import Data.IORef-import Test.Hspec.Core (SpecTree(..), Result(..), runSpecM)+import Test.Hspec.Core (SpecTree(..), Item(..), Result(..), runSpecM) import qualified Test.Hspec as H import qualified Test.Hspec.Runner as H (hspecResult) @@ -42,7 +42,7 @@ length r `shouldBe` 3 it "can be nested" $ do- let [SpecGroup foo [SpecGroup bar [SpecItem _ baz _]]] = runSpecM $ do+ let [SpecGroup foo [SpecGroup bar [SpecItem Item {itemRequirement = baz}]]] = runSpecM $ do H.describe "foo" $ do H.describe "bar" $ do H.it "baz" True@@ -55,17 +55,17 @@ describe "it" $ do it "takes a description of a desired behavior" $ do- let [SpecItem _ d _] = runSpecM (H.it "whatever" True)- d `shouldBe` "whatever"+ let [SpecItem item] = runSpecM (H.it "whatever" True)+ itemRequirement item `shouldBe` "whatever" it "takes an example of that behavior" $ do- let [SpecItem _ _ e] = runSpecM (H.it "whatever" True)- e defaultParams `shouldReturn` Success+ let [SpecItem item] = runSpecM (H.it "whatever" True)+ itemExample item defaultParams id `shouldReturn` Success context "when no description is given" $ do it "uses a default description" $ do- let [SpecItem _ d _] = runSpecM (H.it "" True)- d `shouldBe` "(unspecified behavior)"+ let [SpecItem item] = runSpecM (H.it "" True)+ itemRequirement item `shouldBe` "(unspecified behavior)" describe "example" $ do it "fixes the type of an expectation" $ do@@ -76,15 +76,15 @@ describe "parallel" $ do it "marks examples for parallel execution" $ do- let [SpecItem isParallelizable _ _] = runSpecM . H.parallel $ H.it "whatever" True- isParallelizable `shouldBe` True+ let [SpecItem item] = runSpecM . H.parallel $ H.it "whatever" True+ itemIsParallelizable item `shouldBe` True it "is applied recursively" $ do- let [SpecGroup _ [SpecGroup _ [SpecItem isParallelizable _ _]]] = runSpecM . H.parallel $ do+ let [SpecGroup _ [SpecGroup _ [SpecItem item]]] = runSpecM . H.parallel $ do H.describe "foo" $ do H.describe "bar" $ do H.it "baz" True- isParallelizable `shouldBe` True+ itemIsParallelizable item `shouldBe` True describe "before" $ do it "runs an action before each spec item" $ do@@ -96,6 +96,19 @@ mockCounter mock `shouldReturn` 2 mockCounter mock `shouldReturn` 2 + context "when used multiple times" $ do+ it "is evaluated outside in" $ do+ ref <- newIORef (0 :: Int)+ let action1 = do+ readIORef ref `shouldReturn` 0+ modifyIORef ref succ+ action2 = do+ readIORef ref `shouldReturn` 1+ modifyIORef ref succ+ silence $ H.hspec $ H.before action1 $ H.before action2 $ do+ H.it "foo" $ do+ readIORef ref `shouldReturn` 2+ describe "after" $ do it "runs an action after each spec item" $ do mock <- newMock@@ -107,16 +120,16 @@ mockCounter mock `shouldReturn` 2 describe "around" $ do- it "runs an action before and/or after each spec item" $ do+ it "wraps each spec item with an action" $ do ref <- newIORef (0 :: Int)- let wrapper :: IO () -> IO ()- wrapper e = do+ let action :: IO () -> IO ()+ action e = do readIORef ref `shouldReturn` 0 writeIORef ref 1 e readIORef ref `shouldReturn` 2 writeIORef ref 3- silence $ H.hspec $ H.around wrapper $ do+ silence $ H.hspec $ H.around action $ do H.it "foo" $ do readIORef ref `shouldReturn` 1 writeIORef ref 2