hspec-discover 0.2.0 → 2.0.0
raw patch · 8 files changed
+419/−7 lines, 8 filesdep +basedep +directorydep +filepathdep −hspecnew-component:exe:hspec-discover
Dependencies added: base, directory, filepath, hspec-meta
Dependencies removed: hspec
Files
- LICENSE +3/−1
- hspec-discover.cabal +57/−6
- src/Config.hs +45/−0
- src/Main.hs +8/−0
- src/Run.hs +139/−0
- test/ConfigSpec.hs +47/−0
- test/RunSpec.hs +119/−0
- test/Spec.hs +1/−0
LICENSE view
@@ -1,4 +1,6 @@-Copyright (c) 2012 Simon Hengel <sol@typeful.net>+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
hspec-discover.cabal view
@@ -1,16 +1,67 @@ name: hspec-discover-version: 0.2.0+version: 2.0.0 license: MIT license-file: LICENSE-copyright: (c) 2012-2013 Simon Hengel+copyright: (c) 2012-2014 Simon Hengel author: Simon Hengel <sol@typeful.net> maintainer: Simon Hengel <sol@typeful.net> build-type: Simple-cabal-version: >= 1.8+cabal-version: >= 1.10 category: Testing+stability: experimental+bug-reports: https://github.com/hspec/hspec/issues+homepage: http://hspec.github.io/ synopsis: Automatically discover and run Hspec tests-description: This has been integrated into hspec. There is no need to use- this package anymore!+description: Automatically discover and run Hspec tests+ .+ <http://hspec.github.io/hspec-discover.html> +-- find test-data/ -type f+ test-data/nested-spec/FooSpec.hs+ test-data/nested-spec/Foo/Bar/BazSpec.hs+ test-data/nested-spec/Foo/BarSpec.hs+ test-data/empty-dir/Foo/Bar/Baz/.placeholder++source-repository head+ type: git+ location: https://github.com/hspec/hspec+ subdir: hspec-discover+ library- build-depends: hspec >= 1.3+ default-language: Haskell2010++executable hspec-discover+ ghc-options:+ -Wall+ hs-source-dirs:+ src+ main-is:+ Main.hs+ other-modules:+ Run+ Config+ build-depends:+ base == 4.*+ , filepath+ , directory+ default-language: Haskell2010++test-suite spec+ type:+ exitcode-stdio-1.0+ ghc-options:+ -Wall+ hs-source-dirs:+ src+ , test+ main-is:+ Spec.hs+ other-modules:+ RunSpec+ ConfigSpec+ build-depends:+ base == 4.*+ , filepath+ , directory+ , hspec-meta >= 1.12+ default-language: Haskell2010
+ src/Config.hs view
@@ -0,0 +1,45 @@+module Config (+ Config (..)+, defaultConfig+, parseConfig+, usage+) where++import Data.Maybe+import System.Console.GetOpt++data Config = Config {+ configNested :: Bool+, configFormatter :: Maybe String+, configNoMain :: Bool+, configModuleName :: Maybe String+} deriving (Eq, Show)++defaultConfig :: Config+defaultConfig = Config False Nothing False Nothing++options :: [OptDescr (Config -> Config)]+options = [+ Option [] ["nested"] (NoArg $ \c -> c {configNested = True}) ""+ , Option [] ["formatter"] (ReqArg (\s c -> c {configFormatter = Just s}) "FORMATTER") ""+ , Option [] ["module-name"] (ReqArg (\s c -> c {configModuleName = Just s}) "NAME") ""+ , Option [] ["no-main"] (NoArg $ \c -> c {configNoMain = True}) ""+ ]++usage :: String -> String+usage prog = "\nUsage: " ++ prog ++ " SRC CUR DST [--module-name=NAME]\n"++parseConfig :: String -> [String] -> Either String Config+parseConfig prog args = case getOpt Permute options args of+ (opts, [], []) -> let+ c = (foldl (flip id) defaultConfig opts)+ in+ if (configNoMain c && isJust (configFormatter c))+ then+ formatError "option `--formatter=<fmt>' does not make sense with `--no-main'\n"+ else+ Right c+ (_, _, err:_) -> formatError err+ (_, arg:_, _) -> formatError ("unexpected argument `" ++ arg ++ "'\n")+ where+ formatError err = Left (prog ++ ": " ++ err ++ usage prog)
+ src/Main.hs view
@@ -0,0 +1,8 @@+module Main (main) where++import System.Environment++import Run (run)++main :: IO ()+main = getArgs >>= run
+ src/Run.hs view
@@ -0,0 +1,139 @@+{-# LANGUAGE TypeSynonymInstances, FlexibleInstances, OverloadedStrings #-}+{-# OPTIONS_GHC -fno-warn-orphans #-}+-- | A preprocessor that finds and combines specs.+module Run (+ run++-- exported for testing+, Spec(..)+, importList+, fileToSpec+, findSpecs+, getFilesRecursive+, driverWithFormatter+, moduleNameFromId+, pathToModule+) where+import Control.Monad+import Control.Applicative+import Data.List+import Data.Char+import Data.Maybe+import Data.String+import System.Environment+import System.Exit+import System.IO+import System.Directory (doesDirectoryExist, getDirectoryContents, doesFileExist)+import System.FilePath hiding (combine)++import Config++instance IsString ShowS where+ fromString = showString++data Spec = Spec {+ specFile :: FilePath+, specModule :: String+} deriving (Eq, Show)++run :: [String] -> IO ()+run args_ = do+ name <- getProgName+ case args_ of+ src : _ : dst : args -> case parseConfig name args of+ Left err -> do+ hPutStrLn stderr err+ exitFailure+ Right conf -> do+ when (configNested conf) (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 conf specs)+ _ -> do+ hPutStrLn stderr (usage name)+ exitFailure++mkSpecModule :: FilePath -> Config -> [Spec] -> String+mkSpecModule src conf nodes =+ ( "{-# LINE 1 " . shows src . " #-}\n"+ . showString "{-# OPTIONS_GHC -fno-warn-warnings-deprecations #-}\n"+ . showString ("module " ++ moduleName src conf ++" where\n")+ . importList nodes+ . showString "import Test.Hspec.Discover\n"+ . maybe driver driverWithFormatter (configFormatter conf)+ . showString "spec :: Spec\n"+ . showString "spec = "+ . formatSpecs nodes+ ) "\n"+ where+ driver =+ case configNoMain conf of+ False ->+ showString "main :: IO ()\n"+ . showString "main = hspec spec\n"+ True -> ""++moduleName :: FilePath -> Config -> String+moduleName src conf = fromMaybe (if configNoMain conf then pathToModule src else "Main") (configModuleName conf)++-- | Derive module name from specified path.+pathToModule :: FilePath -> String+pathToModule f = toUpper m:ms+ where+ fileName = last $ splitDirectories f+ m:ms = takeWhile (/='.') fileName++driverWithFormatter :: String -> ShowS+driverWithFormatter f =+ showString "import qualified " . showString (moduleNameFromId f) . showString "\n"+ . showString "main :: IO ()\n"+ . showString "main = hspecWithFormatter " . showString f . showString " spec\n"++-- | Return module name of a fully qualified identifier.+moduleNameFromId :: String -> String+moduleNameFromId = reverse . dropWhile (== '.') . dropWhile (/= '.') . reverse++-- | Generate imports for a list of specs.+importList :: [Spec] -> ShowS+importList = foldr (.) "" . map f+ where+ f :: Spec -> ShowS+ f spec = "import qualified " . showString (specModule spec) . "Spec\n"++-- | Combine a list of strings with (>>).+sequenceS :: [ShowS] -> ShowS+sequenceS = foldr (.) "" . intersperse " >> "++-- | Convert a list of specs to code.+formatSpecs :: [Spec] -> ShowS+formatSpecs xs+ | null xs = "return ()"+ | otherwise = sequenceS (map formatSpec xs)++-- | Convert a spec to code.+formatSpec :: Spec -> ShowS+formatSpec (Spec file name) = "postProcessSpec " . shows file . " (describe " . shows name . " " . showString name . "Spec.spec)"++findSpecs :: FilePath -> IO [Spec]+findSpecs src = do+ let (dir, file) = splitFileName src+ mapMaybe (fileToSpec dir) . filter (/= file) <$> getFilesRecursive dir++fileToSpec :: FilePath -> FilePath -> Maybe Spec+fileToSpec dir file = case reverse $ splitDirectories file of+ x:xs -> case stripSuffix "Spec.hs" x <|> stripSuffix "Spec.lhs" x of+ Just name | (not . null) name -> Just . Spec (dir </> file) $ (intercalate "." . reverse) (name : xs)+ _ -> Nothing+ _ -> Nothing+ where+ stripSuffix :: Eq a => [a] -> [a] -> Maybe [a]+ stripSuffix suffix str = reverse <$> stripPrefix (reverse suffix) (reverse str)++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)
+ test/ConfigSpec.hs view
@@ -0,0 +1,47 @@+module ConfigSpec (main, spec) where++import Test.Hspec.Meta++import Config++main :: IO ()+main = hspec spec++spec :: Spec+spec = do+ describe "parseConfig" $ do+ let parse = parseConfig "hspec-discover"++ it "recognizes --nested" $ do+ parse ["--nested"] `shouldBe` Right (defaultConfig {configNested = True})++ it "recognizes --formatter" $ do+ parse ["--formatter", "someFormatter"] `shouldBe` Right (defaultConfig {configFormatter = Just "someFormatter"})++ it "recognizes --no-main" $ do+ parse ["--no-main"] `shouldBe` Right (defaultConfig {configNoMain = True})++ it "returns error message on unrecognized option" $ do+ parse ["--foo"] `shouldBe` (Left . unlines) [+ "hspec-discover: unrecognized option `--foo'"+ , ""+ , "Usage: hspec-discover SRC CUR DST [--module-name=NAME]"+ ]++ it "returns error message on unexpected argument" $ do+ parse ["foo"] `shouldBe` (Left . unlines) [+ "hspec-discover: unexpected argument `foo'"+ , ""+ , "Usage: hspec-discover SRC CUR DST [--module-name=NAME]"+ ]++ it "returns error message on --formatter=<fmt> with --no-main" $ do+ parse ["--no-main", "--formatter=foo"] `shouldBe` (Left . unlines) [+ "hspec-discover: option `--formatter=<fmt>' does not make sense with `--no-main'"+ , ""+ , "Usage: hspec-discover SRC CUR DST [--module-name=NAME]"+ ]++ context "when option is given multiple times" $ do+ it "gives the last occurrence precedence" $ do+ parse ["--formatter", "foo", "--formatter", "bar"] `shouldBe` Right (defaultConfig {configFormatter = Just "bar"})
+ test/RunSpec.hs view
@@ -0,0 +1,119 @@+module RunSpec (main, spec) where++import Test.Hspec.Meta++import Control.Applicative+import System.IO+import System.Directory+import System.FilePath+import Data.List (sort)++import Run hiding (Spec)+import qualified 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 "run" $ do+ it "generates test driver" $ withTempFile $ \f -> do+ run ["test-data/nested-spec/Spec.hs", "", f]+ readFile f `shouldReturn` unlines [+ "{-# LINE 1 \"test-data/nested-spec/Spec.hs\" #-}"+ , "{-# OPTIONS_GHC -fno-warn-warnings-deprecations #-}"+ , "module Main where"+ , "import qualified Foo.Bar.BazSpec"+ , "import qualified Foo.BarSpec"+ , "import qualified FooSpec"+ , "import Test.Hspec.Discover"+ , "main :: IO ()"+ , "main = hspec spec"+ , "spec :: Spec"+ , "spec = " ++ unwords [+ "postProcessSpec \"test-data/nested-spec/Foo/Bar/BazSpec.hs\" (describe \"Foo.Bar.Baz\" Foo.Bar.BazSpec.spec)"+ , ">> postProcessSpec \"test-data/nested-spec/Foo/BarSpec.hs\" (describe \"Foo.Bar\" Foo.BarSpec.spec)"+ , ">> postProcessSpec \"test-data/nested-spec/FooSpec.hs\" (describe \"Foo\" FooSpec.spec)"+ ]+ ]++ it "generates test driver for an empty directory" $ withTempFile $ \f -> do+ run ["test-data/empty-dir/Spec.hs", "", f]+ readFile f `shouldReturn` unlines [+ "{-# LINE 1 \"test-data/empty-dir/Spec.hs\" #-}"+ , "{-# OPTIONS_GHC -fno-warn-warnings-deprecations #-}"+ , "module Main where"+ , "import Test.Hspec.Discover"+ , "main :: IO ()"+ , "main = hspec spec"+ , "spec :: Spec"+ , "spec = return ()"+ ]++ describe "pathToModule" $ do+ it "derives module name from a given path" $ do+ pathToModule "test/Spec.hs" `shouldBe` "Spec"++ describe "getFilesRecursive" $ do+ it "recursively returns all file entries of a given directory" $ do+ getFilesRecursive "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"+ ]++ describe "fileToSpec" $ do+ it "converts path to spec name" $ do+ fileToSpec "" "FooSpec.hs" `shouldBe` Just (spec_ "FooSpec.hs" "Foo")++ it "rejects spec with empty name" $ do+ fileToSpec "" "Spec.hs" `shouldBe` Nothing++ it "works for lhs files" $ do+ fileToSpec "" "FooSpec.lhs" `shouldBe` Just (spec_ "FooSpec.lhs" "Foo")++ it "returns Nothing for invalid spec name" $ do+ fileToSpec "" "foo" `shouldBe` Nothing++ context "when path has directory component" $ do+ it "converts path to spec name" $ do+ let file = "Foo" </> "Bar" </> "BazSpec.hs"+ fileToSpec "" file `shouldBe` Just (spec_ file "Foo.Bar.Baz")++ it "rejects spec with empty name" $ do+ fileToSpec "" ("Foo" </> "Bar" </> "Spec.hs") `shouldBe` Nothing++ describe "findSpecs" $ do+ it "finds specs" $ do+ let dir = "test-data/nested-spec"+ findSpecs (dir </> "Spec.hs") `shouldReturn` [spec_ (dir </> "Foo/Bar/BazSpec.hs") "Foo.Bar.Baz", spec_ (dir </> "Foo/BarSpec.hs") "Foo.Bar", spec_ (dir </> "FooSpec.hs") "Foo"]++ describe "driverWithFormatter" $ do+ it "generates a test driver that uses a custom formatter" $ do+ driverWithFormatter "Some.Module.formatter" "" `shouldBe` unlines [+ "import qualified Some.Module"+ , "main :: IO ()"+ , "main = hspecWithFormatter Some.Module.formatter spec"+ ]++ describe "moduleNameFromId" $ do+ it "returns the module name of a fully qualified identifier" $ do+ moduleNameFromId "Some.Module.someId" `shouldBe` "Some.Module"++ describe "importList" $ do+ it "generates imports for a list of specs" $ do+ importList [spec_ "FooSpec.hs" "Foo", spec_ "BarSpec.hs" "Bar"] "" `shouldBe` unlines [+ "import qualified FooSpec"+ , "import qualified BarSpec"+ ]+ where+ spec_ = Run.Spec
+ test/Spec.hs view
@@ -0,0 +1,1 @@+{-# OPTIONS_GHC -F -pgmF hspec-meta-discover #-}