Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add --fail-fast #455

Merged
merged 37 commits into from
Oct 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
da89e81
implement stop on fail mode
tgdwyer Sep 30, 2024
c91656f
Merge remote-tracking branch 'upstream/main' into stopOnFail
tgdwyer Oct 2, 2024
fae77e6
cleanup for fail-fast
tgdwyer Oct 2, 2024
5c4c557
minor
tgdwyer Oct 2, 2024
922a81e
grammar
tgdwyer Oct 2, 2024
b636d70
rename
tgdwyer Oct 2, 2024
3ea017c
help message for fail-fast
tgdwyer Oct 2, 2024
c57791a
grammar
tgdwyer Oct 2, 2024
5e02a21
minor
tgdwyer Oct 2, 2024
6163d14
fix tests
tgdwyer Oct 2, 2024
1d30f53
add fail-fast test to cabal file
tgdwyer Oct 3, 2024
9c18474
fail-fast across modules
tgdwyer Oct 3, 2024
4869344
Merge branch 'stopOnFail' of https:/tgdwyer/doctest into …
tgdwyer Oct 3, 2024
43ff72e
separate multimodules test
tgdwyer Oct 4, 2024
a660e95
in fail-fast test, force module processing order with an import, fix…
tgdwyer Oct 4, 2024
1cb85da
fix hpack order
tgdwyer Oct 4, 2024
bbe00c6
Use `MaybeT`
sol Oct 15, 2024
b4a777c
Implement fail-fast behavior
sol Oct 15, 2024
7ce7caf
MaybeT for fast-fail
tgdwyer Oct 18, 2024
5b03c92
fail-fast after preserveIt
tgdwyer Oct 18, 2024
97efed1
fail-fast after preserve-it
tgdwyer Oct 18, 2024
d1abb1f
typo
tgdwyer Oct 18, 2024
3e90a6f
clean up tests
tgdwyer Oct 18, 2024
cf4a4c7
Update MainSpec.hs
sol Oct 19, 2024
0eb1be7
Update MainSpec.hs
sol Oct 19, 2024
364d238
Update MainSpec.hs
sol Oct 19, 2024
286bdac
Update MainSpec.hs
sol Oct 19, 2024
e1f6a41
Update doctest.cabal
sol Oct 19, 2024
7c100f6
Update Foo.hs
sol Oct 19, 2024
3023bb5
Update Bar.hs
sol Oct 19, 2024
29ecd57
Update Foo.hs
sol Oct 19, 2024
c7c23ec
Update Bar.hs
sol Oct 19, 2024
cf9b0b0
Update Foo.hs
sol Oct 19, 2024
4ecd2aa
Update Foo.hs
sol Oct 19, 2024
15deacc
Update CHANGES.markdown
sol Oct 19, 2024
3e33d0b
Update package.yaml
sol Oct 19, 2024
f03a676
Update doctest.cabal
sol Oct 19, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGES.markdown
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
Changes in 0.23.0
- Add `--fail-fast`

Changes in 0.22.10
- Make progress reporting more robust

Expand Down
6 changes: 5 additions & 1 deletion doctest.cabal

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
name: doctest
version: 0.22.10
version: 0.23.0
synopsis: Test interactive Haskell examples
description: |
`doctest` is a tool that checks [examples](https://www.haskell.org/haddock/doc/html/ch03s08.html#idm140354810775744)
Expand Down
10 changes: 9 additions & 1 deletion src/Options.hs
Original file line number Diff line number Diff line change
Expand Up @@ -28,14 +28,16 @@ import Info
usage :: String
usage = unlines [
"Usage:"
, " doctest [ --fast | --preserve-it | --no-magic | --verbose | GHC OPTION | MODULE ]..."
, " doctest [ --fast | --preserve-it | --fail-fast | --no-magic | --verbose | GHC OPTION | MODULE ]..."
, " doctest --help"
, " doctest --version"
, " doctest --info"
, ""
, "Options:"
, " --fast disable :reload between example groups"
, " --preserve-it preserve the `it` variable between examples"
, " --fail-fast abort on first failure"
, " --no-magic disable magic mode"
sol marked this conversation as resolved.
Show resolved Hide resolved
sol marked this conversation as resolved.
Show resolved Hide resolved
, " --verbose print each test as it is run"
, " --help display this help and exit"
, " --version output version information and exit"
Expand All @@ -57,6 +59,7 @@ data Config = Config {
ghcOptions :: [String]
, fastMode :: Bool
, preserveIt :: Bool
, failFast :: Bool
sol marked this conversation as resolved.
Show resolved Hide resolved
, verbose :: Bool
, repl :: (String, [String])
} deriving (Eq, Show)
Expand All @@ -66,6 +69,7 @@ defaultConfig = Config {
ghcOptions = []
, fastMode = False
, preserveIt = False
, failFast = False
sol marked this conversation as resolved.
Show resolved Hide resolved
, verbose = False
, repl = (ghc, ["--interactive"])
}
Expand Down Expand Up @@ -105,6 +109,9 @@ setFastMode fastMode run@Run{..} = run { runConfig = runConfig { fastMode } }
setPreserveIt :: Bool -> Run -> Run
setPreserveIt preserveIt run@Run{..} = run { runConfig = runConfig { preserveIt } }

setFailFastMode :: Bool -> Run -> Run
setFailFastMode failFast run@Run{..} = run { runConfig = runConfig { failFast } }

setVerbose :: Bool -> Run -> Run
setVerbose verbose run@Run{..} = run { runConfig = runConfig { verbose } }

Expand Down Expand Up @@ -134,6 +141,7 @@ commonRunOptions :: RunOptionsParser
commonRunOptions = do
parseFlag "--fast" (setFastMode True)
parseFlag "--preserve-it" (setPreserveIt True)
parseFlag "--fail-fast" (setFailFastMode True)
parseFlag "--verbose" (setVerbose True)

parseFlag :: String -> (Run -> Run) -> RunOptionsParser
Expand Down
4 changes: 1 addition & 3 deletions src/Run.hs
Original file line number Diff line number Diff line change
Expand Up @@ -136,9 +136,6 @@ doctestWith = doctestWithResult >=> evaluateResult

type Result = Summary

isSuccess :: Result -> Bool
isSuccess s = sErrors s == 0 && sFailures s == 0

evaluateResult :: Result -> IO ()
evaluateResult r = unless (isSuccess r) exitFailure

Expand All @@ -158,5 +155,6 @@ runDocTests Config{..} modules = do
runModules
(if fastMode then FastMode else NoFastMode)
(if preserveIt then PreserveIt else NoPreserveIt)
(if failFast then FailFast else NoFailFast)
sol marked this conversation as resolved.
Show resolved Hide resolved
(if verbose then Verbose else NonVerbose)
interpreter modules
37 changes: 30 additions & 7 deletions src/Runner.hs
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,16 @@ module Runner (
runModules
, FastMode(..)
, PreserveIt(..)
, FailFast(..)
, Verbose(..)
, Summary(..)
, isSuccess
, formatSummary

#ifdef TEST
, Report
, ReportState(..)
, runReport
, Interactive(..)
, report
, reportTransient
Expand All @@ -23,7 +26,10 @@ import Imports hiding (putStr, putStrLn, error)
import Text.Printf (printf)
import System.IO hiding (putStr, putStrLn)

import Control.Monad.Trans.State
import Control.Monad.Trans.Class
import Control.Monad.Trans.Maybe
import Control.Monad.Trans.State (StateT, evalStateT)
import qualified Control.Monad.Trans.State as State
import Control.Monad.IO.Class
import Data.IORef

Expand All @@ -45,6 +51,9 @@ data Summary = Summary {
instance Show Summary where
show = formatSummary

isSuccess :: Summary -> Bool
isSuccess s = sErrors s == 0 && sFailures s == 0

formatSummary :: Summary -> String
formatSummary (Summary examples tried errors failures) =
printf "Examples: %d Tried: %d Errors: %d Failures: %d" examples tried errors failures
Expand All @@ -66,8 +75,8 @@ withLineBuffering h action = bracket (hGetBuffering h) (hSetBuffering h) $ \ _ -
action

-- | Run all examples from a list of modules.
runModules :: FastMode -> PreserveIt -> Verbose -> Interpreter -> [Module [Located DocTest]] -> IO Summary
runModules fastMode preserveIt verbose repl modules = withLineBuffering stderr $ do
runModules :: FastMode -> PreserveIt -> FailFast -> Verbose -> Interpreter -> [Module [Located DocTest]] -> IO Summary
runModules fastMode preserveIt failFast verbose repl modules = withLineBuffering stderr $ do

interactive <- hIsTerminalDevice stderr <&> \ case
False -> NonInteractive
Expand All @@ -82,7 +91,7 @@ runModules fastMode preserveIt verbose repl modules = withLineBuffering stderr $
hPutStrLn stderr (formatSummary final)

run :: IO ()
run = flip evalStateT (ReportState interactive verbose summary) $ do
run = runReport (ReportState interactive failFast verbose summary) $ do
reportProgress
forM_ modules $ runModule fastMode preserveIt repl
verboseReport "# Final summary:"
Expand All @@ -97,23 +106,32 @@ runModules fastMode preserveIt verbose repl modules = withLineBuffering stderr $
countExpressions :: Module [Located DocTest] -> Int
countExpressions (Module _ setup tests) = sum (map length tests) + maybe 0 length setup

type Report = StateT ReportState IO
type Report = MaybeT (StateT ReportState IO)

data Interactive = NonInteractive | Interactive

data FastMode = NoFastMode | FastMode

data FailFast = NoFailFast | FailFast

data Verbose = NonVerbose | Verbose

data ReportState = ReportState {
reportStateInteractive :: Interactive
, reportStateFailFast :: FailFast
, reportStateVerbose :: Verbose
, reportStateSummary :: IORef Summary
}

runReport :: ReportState -> Report () -> IO ()
runReport st = void . flip evalStateT st . runMaybeT

getSummary :: Report Summary
getSummary = gets reportStateSummary >>= liftIO . readIORef

gets :: (ReportState -> a) -> Report a
gets = lift . State.gets

-- | Add output to the report.
report :: String -> Report ()
report = liftIO . hPutStrLn stderr
Expand Down Expand Up @@ -143,8 +161,7 @@ runModule fastMode preserveIt repl (Module module_ setup examples) = do

-- only run tests, if setup does not produce any errors/failures
when (e0 == e1 && f0 == f1) $
forM_ examples $
runTestGroup preserveIt repl setup_
forM_ examples $ runTestGroup preserveIt repl setup_
where
reload :: IO ()
reload = do
Expand Down Expand Up @@ -202,6 +219,12 @@ updateSummary summary = do
ref <- gets reportStateSummary
liftIO $ modifyIORef' ref $ mappend summary
reportProgress
gets reportStateFailFast >>= \ case
NoFailFast -> pass
FailFast -> unless (isSuccess summary) abort

abort :: Report ()
abort = MaybeT $ return Nothing

reportProgress :: Report ()
reportProgress = gets reportStateVerbose >>= \ case
Expand Down
37 changes: 28 additions & 9 deletions test/MainSpec.hs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import Test.HUnit (assertEqual, Assertion)

import System.Directory (getCurrentDirectory, setCurrentDirectory)
import System.FilePath
import Run hiding (doctest)
import Run hiding (doctest, doctestWith)
import System.IO.Silently
import System.IO

Expand All @@ -19,13 +19,18 @@ withCurrentDirectory workingDir action = do
setCurrentDirectory workingDir
action

-- | Construct a doctest specific 'Assertion'.
doctest :: HasCallStack => FilePath -> [String] -> Summary -> Assertion
doctest = doctestWithPreserveIt False
doctest = doctestWith False False

doctestWithPreserveIt :: HasCallStack => Bool -> FilePath -> [String] -> Summary -> Assertion
doctestWithPreserveIt preserveIt workingDir ghcOptions expected = do
actual <- withCurrentDirectory ("test/integration" </> workingDir) (hSilence [stderr] $ doctestWithResult defaultConfig {ghcOptions, preserveIt})
doctestWithPreserveIt :: HasCallStack => FilePath -> [String] -> Summary -> Assertion
doctestWithPreserveIt = doctestWith True False

doctestWithFailFast :: HasCallStack => FilePath -> [String] -> Summary -> Assertion
doctestWithFailFast = doctestWith False True

doctestWith :: HasCallStack => Bool -> Bool -> FilePath -> [String] -> Summary -> Assertion
doctestWith preserveIt failFast workingDir ghcOptions expected = do
actual <- withCurrentDirectory ("test/integration" </> workingDir) (hSilence [stderr] $ doctestWithResult defaultConfig {ghcOptions, preserveIt, failFast})
assertEqual label (formatSummary expected) (formatSummary actual)
where
label = workingDir ++ " " ++ show ghcOptions
Expand All @@ -44,21 +49,35 @@ spec = do
(cases 1)

it "it-variable" $ do
doctestWithPreserveIt True "." ["it/Foo.hs"]
doctestWithPreserveIt "." ["it/Foo.hs"]
(cases 5)

it "it-variable in $setup" $ do
doctestWithPreserveIt True "." ["it/Setup.hs"]
doctestWithPreserveIt "." ["it/Setup.hs"]
(cases 5)

it "failing" $ do
doctest "." ["failing/Foo.hs"]
(cases 1) {sFailures = 1}

it "skips subsequent examples from the same group if an example fails" $
it "skips subsequent examples from the same group if an example fails" $ do
doctest "." ["failing-multiple/Foo.hs"]
(cases 4) {sTried = 2, sFailures = 1}

context "without --fail-fast" $ do
it "continuous even if some tests fail" $ do
doctest "fail-fast" ["Foo.hs"]
(cases 4) {sTried = 4, sFailures = 1}

context "with --fail-fast" $ do
it "stops after the first failure" $ do
doctestWithFailFast "fail-fast" ["Foo.hs"]
(cases 4) {sTried = 2, sFailures = 1}

it "stops after failures in $setup" $ do
doctestWithFailFast "fail-fast" ["SetupFoo.hs"]
(cases 6) {sTried = 1, sFailures = 1}

it "testImport" $ do
doctest "testImport" ["ModuleA.hs"]
(cases 3)
Expand Down
11 changes: 10 additions & 1 deletion test/OptionsSpec.hs
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ spec = do
fastMode . runConfig <$> parseOptions [] `shouldBe` Result False

context "with --fast" $ do
it "enabled fast mode" $ do
it "enables fast mode" $ do
fastMode . runConfig <$> parseOptions ["--fast"] `shouldBe` Result True

describe "--preserve-it" $ do
Expand All @@ -77,6 +77,15 @@ spec = do
it "preserves the `it` variable" $ do
preserveIt . runConfig <$> parseOptions ["--preserve-it"] `shouldBe` Result True

describe "--fail-fast" $ do
context "without --fail-fast" $ do
it "disables fail-fast mode" $ do
failFast . runConfig <$> parseOptions [] `shouldBe` Result False

context "with --fail-fast" $ do
it "enables fail-fast mode" $ do
failFast . runConfig <$> parseOptions ["--fail-fast"] `shouldBe` Result True

context "with --help" $ do
it "outputs usage information" $ do
parseOptions ["--help"] `shouldBe` Output usage
Expand Down
5 changes: 2 additions & 3 deletions test/RunnerSpec.hs
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,12 @@ import Test.Hspec
import Data.IORef
import System.IO
import System.IO.Silently (hCapture_)
import Control.Monad.Trans.State
import Runner

capture :: Interactive -> Report a -> IO String
capture :: Interactive -> Report () -> IO String
capture interactive action = do
ref <- newIORef mempty
hCapture_ [stderr] (evalStateT action (ReportState interactive NonVerbose ref))
hCapture_ [stderr] (runReport (ReportState interactive NoFailFast NonVerbose ref) action)

spec :: Spec
spec = do
Expand Down
8 changes: 8 additions & 0 deletions test/integration/fail-fast/Bar.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
module Bar where

-- | bar
-- a passing test
-- >>> bar
-- 42
bar :: Int
bar = 42
24 changes: 24 additions & 0 deletions test/integration/fail-fast/Foo.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
module Foo where

import Bar

-- | A passing example
--
-- >>> 23
-- 23
test1 :: a
test1 = undefined

-- | A failing example
--
-- >>> 23
-- 42
test2 :: a
test2 = undefined

-- | Another passing example
--
-- >>> 23
-- 23
test3 :: a
test3 = undefined
12 changes: 12 additions & 0 deletions test/integration/fail-fast/SetupBar.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
module SetupBar where

-- $setup
-- >>> 23
-- 23

-- | bar
-- a passing test
-- >>> bar
-- 42
bar :: Int
bar = 42
Loading
Loading