module Test.Hspec.RunnerSpec (main, spec) where import Test.Hspec.Meta import System.IO.Silently import System.IO (stderr) import Control.Applicative import Control.Monad (unless) import System.Environment (withArgs, withProgName, getArgs) import System.Exit import qualified Control.Exception as E import SpecHelper import Mock import System.SetEnv import Test.Hspec.Util (getEnv) import Test.QuickCheck import qualified Test.Hspec.Runner as H import qualified Test.Hspec.Core as H (Result(..)) import qualified Test.Hspec as H (describe, it, pending) import qualified Test.Hspec.Formatters as H (silent) ignoreExitCode :: IO () -> IO () ignoreExitCode action = action `E.catch` \e -> let _ = e :: ExitCode in return () ignoreUserInterrupt :: IO () -> IO () ignoreUserInterrupt action = action `E.catch` \e -> unless (e == E.UserInterrupt) (E.throwIO e) main :: IO () main = hspec spec 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 "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 r <- captureLines . ignoreUserInterrupt . withArgs ["--seed", "23"] . H.hspec $ do H.it "foo" False H.it "bar" $ do E.throwIO E.UserInterrupt :: IO () H.it "baz" True normalizeSummary r `shouldBe` [ "" , "- foo FAILED [1]" , "" , "1) foo FAILED" , "" , "Randomized with seed 23" , "" ] it "throws UserInterrupt" $ do silence . H.hspec $ do H.it "foo" $ do E.throwIO E.UserInterrupt :: IO () `shouldThrow` (== 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 it "produces a report" $ do r <- captureLines . withArgs ["--dry-run"] . H.hspec $ 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 _ <- captureLines . withArgs ["--dry-run"] . H.hspec $ do H.it "foo" (mockAction e) H.it "bar" False mockCounter e `shouldReturn` 0 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 FAILED" , "" , "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 FAILED" , "" , "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 --maximum-generated-tests=n" $ do it "tries QuickCheck properties n times" $ do m <- newMock silence . withArgs ["--qc-max-succes", "23"] . H.hspec $ do H.it "foo" $ property $ do mockAction m mockCounter m `shouldReturn` 23 context "when given an invalid argument" $ do it "prints an error message to stderr" $ do r <- hCapture_ [stderr] . ignoreExitCode . withArgs ["--qc-max-succes", "foo"] . H.hspec $ do H.it "foo" True r `shouldContain` "invalid argument `foo' for `--qc-max-success'" context "with --seed" $ do it "uses specified seed" $ do r <- captureLines . ignoreExitCode . withArgs ["--seed", "2413421499272008081"] . H.hspec $ do H.it "foo" $ property $ \n -> ((17 + 31 * n) `mod` 50) /= (23 :: Int) r `shouldContain` [ "Falsifiable (after 55 tests): " , "1190445426" ] context "when run with --re-run" $ do it "takes precedence" $ do let runSpec args = capture_ . ignoreExitCode . withArgs args . H.hspec $ do H.it "foo" $ property $ \n -> ((17 + 31 * n) `mod` 50) /= (23 :: Int) r0 <- runSpec ["--seed", "23"] r0 `shouldContain` "(after 13 tests)" r1 <- runSpec ["--seed", "42"] r1 `shouldContain` "(after 18 tests)" r2 <- runSpec ["--re-run", "--seed", "23"] r2 `shouldContain` "(after 13 tests)" 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` "" it "marks successful examples with CSS class hspec-success" $ do r <- capture_ . withArgs ["--html"] . H.hspec $ do H.it "foo" True r `shouldContain` "- foo\n" it "marks pending examples with CSS class hspec-pending" $ do r <- capture_ . withArgs ["--html"] . H.hspec $ do H.it "foo" H.pending r `shouldContain` "- 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` "- foo" describe "hspec (experimental features)" $ do it "stores a failure report in 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 getEnv "HSPEC_FAILURES" `shouldReturn` Just "(23,[([\"foo\",\"bar\"],\"example 2\"),([\"baz\"],\"example 3\")])" describe "with --re-run" $ 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 "re-runs examples that previously failed" $ do r0 <- runSpec r0 `shouldSatisfy` elem "5 examples, 3 failures" r1 <- withArgs ["-r"] runSpec r1 `shouldSatisfy` elem "3 examples, 3 failures" it "reuses the same seed" $ do let runSpec_ = (captureLines . ignoreExitCode . H.hspec) $ do H.it "foo" $ property $ \n -> ((17 + 31 * n) `mod` 50) /= (23 :: Int) r0 <- withArgs ["--seed", "2413421499272008081"] runSpec_ r0 `shouldContain` [ "Falsifiable (after 55 tests): " , "1190445426" ] r1 <- withArgs ["-r"] runSpec_ r1 `shouldContain` [ "Falsifiable (after 55 tests): " , "1190445426" ] 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; `--re-run' 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 fst r `shouldBe` "WARNING: Could not read environment variable HSPEC_FAILURES; `--re-run' is ignored!\n" describe "hspecWith" $ do it "returns a summary of the test run" $ do silence . H.hspecWith H.defaultConfig $ 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.hspecWith H.defaultConfig $ do H.it "foobar" (E.throwIO (E.ErrorCall "foobar") >> pure ()) `shouldReturn` H.Summary 1 1 it "uses the specdoc formatter by default" $ do _:r:_ <- captureLines . H.hspecWith H.defaultConfig $ 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.hspecWith H.defaultConfig {H.configFormatter = 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.hspecWith H.defaultConfig $ do H.it "some example" (H.Fail $ "foobar" ++ undefined) r `shouldBe` H.Summary 1 1