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

added test run time limits #890

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions hspec-core/help.txt
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ RUNNER OPTIONS
pending: fail on pending spec items
empty-description: fail on empty descriptions
--[no-]strict same as --fail-on=focused,pending
--[no-]timeout=N each test will run at most N seconds
--[no-]fail-fast abort on first failure
--[no-]randomize randomize execution order
-r --rerun rerun all examples that failed in the previous
Expand Down
8 changes: 6 additions & 2 deletions hspec-core/src/Test/Hspec/Core/Clock.hs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ module Test.Hspec.Core.Clock (
, toMicroseconds
, getMonotonicTime
, measure
, measureWithTimeout
, sleep
, timeout
) where
Expand Down Expand Up @@ -42,9 +43,12 @@ getMonotonicTime = do
#endif

measure :: IO a -> IO (Seconds, a)
measure action = do
measure = fmap (second fromJust) . measureWithTimeout Nothing

measureWithTimeout :: Maybe Seconds -> IO a -> IO (Seconds, Maybe a)
measureWithTimeout maxTime action = do
t0 <- getMonotonicTime
a <- action
a <- maybe (fmap Just) timeout maxTime action
t1 <- getMonotonicTime
return (t1 - t0, a)
BebeSparkelSparkel marked this conversation as resolved.
Show resolved Hide resolved

Expand Down
10 changes: 9 additions & 1 deletion hspec-core/src/Test/Hspec/Core/Config/Definition.hs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ import System.Directory (getTemporaryDirectory, removeFile)
import System.IO (openTempFile, hClose)
import System.Process (system)

import Test.Hspec.Core.Format (Format, FormatConfig)
import Test.Hspec.Core.Format (Format, FormatConfig, Seconds(Seconds))
import Test.Hspec.Core.Formatters.Pretty (pretty2)
import qualified Test.Hspec.Core.Formatters.V1.Monad as V1
import Test.Hspec.Core.Util
Expand All @@ -48,6 +48,7 @@ data Config = Config {
, configFailOnFocused :: Bool
, configFailOnPending :: Bool
, configFailOnEmptyDescription :: Bool
, configTimeout :: Maybe Seconds
, configPrintSlowItems :: Maybe Int
, configPrintCpuTime :: Bool
, configFailFast :: Bool
Expand Down Expand Up @@ -107,6 +108,7 @@ mkDefaultConfig formatters = Config {
, configFailOnFocused = False
, configFailOnPending = False
, configFailOnEmptyDescription = False
, configTimeout = Nothing
, configPrintSlowItems = Nothing
, configPrintCpuTime = False
, configFailFast = False
Expand Down Expand Up @@ -304,6 +306,9 @@ setMaxShrinks n c = c {configQuickCheckMaxShrinks = Just n}
setSeed :: Integer -> Config -> Config
setSeed n c = c {configSeed = Just n}

setTimeout :: Maybe Seconds -> Config -> Config
setTimeout n c = c {configTimeout = n}

data FailOn =
FailOnEmpty
| FailOnFocused
Expand Down Expand Up @@ -346,6 +351,9 @@ runnerOptions = [
, mkOption "no-fail-on" Nothing (argument "ITEMS" readFailOnItems (setFailOnItems False)) helpForFailOn
, flag "strict" setStrict $ "same as --fail-on=" <> showFailOnItems strict

, option "timeout" (argument "N" (fmap Seconds . readMaybe) (setTimeout . Just)) "each test will run at most N seconds"
, mkOptionNoArg "no-timeout" Nothing (setTimeout Nothing) "remove test time limits"

, flag "fail-fast" setFailFast "abort on first failure"
, flag "randomize" setRandomize "randomize execution order"
, mkOptionNoArg "rerun" (Just 'r') setRerun "rerun all examples that failed in the previous test run (only works in combination with --failure-report or in GHCi)"
Expand Down
17 changes: 13 additions & 4 deletions hspec-core/src/Test/Hspec/Core/Runner.hs
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ import qualified Test.QuickCheck as QC
import Test.Hspec.Core.Util (Path)
import Test.Hspec.Core.Clock
import Test.Hspec.Core.Spec hiding (pruneTree, pruneForest)
import Test.Hspec.Core.Tree (formatDefaultDescription)
import Test.Hspec.Core.Tree (formatDefaultDescription, setItemTimeout)
import Test.Hspec.Core.Config
import Test.Hspec.Core.Format (Format, FormatConfig(..))
import qualified Test.Hspec.Core.Formatters.V1 as V1
Expand Down Expand Up @@ -233,11 +233,14 @@ hspecWithResult config = fmap toSummary . hspecWithSpecResult config

hspecWithSpecResult :: Config -> Spec -> IO SpecResult
hspecWithSpecResult defaults spec = do
(c, forest) <- evalSpec defaults spec
(c, f) <- evalSpec defaults spec
config <- getArgs >>= readConfig c
oldFailureReport <- readFailureReportOnRerun config

let
forest :: [SpecTree ()]
forest = maybe id (fmap . fmap . setItemTimeout . Just) (configTimeout config) f

normalMode :: IO SpecResult
normalMode = doNotLeakCommandLineArgumentsToExamples $ runSpecForest_ oldFailureReport forest config

Expand Down Expand Up @@ -450,16 +453,22 @@ toEvalItemForest :: Params -> [SpecTree ()] -> [EvalItemTree]
toEvalItemForest params = bimapForest id toEvalItem . filterForest itemIsFocused
where
toEvalItem :: Item () -> EvalItem
toEvalItem (Item requirement loc isParallelizable _isFocused e) = EvalItem {
toEvalItem (Item requirement loc isParallelizable _isFocused e maxTime) = EvalItem {
evalItemDescription = requirement
, evalItemLocation = loc
, evalItemConcurrency = if isParallelizable == Just True then Concurrent else Sequential
, evalItemAction = \ progress -> measure $ e params withUnit progress
, evalItemAction = \ progress -> let
failResultTimeout = Result requirement $ Failure loc $ Reason "exceeded timeout"
resolveResult = fmap $ fmap $ fromMaybe failResultTimeout
measure' = measureWithTimeout maxTime
in resolveResult $ measure' $ e params withUnit progress
}

withUnit :: ActionWith () -> IO ()
withUnit action = action ()



dumpFailureReport :: Config -> Integer -> QC.Args -> [Path] -> IO ()
dumpFailureReport config seed qcArgs xs = do
writeFailureReport config FailureReport {
Expand Down
9 changes: 9 additions & 0 deletions hspec-core/src/Test/Hspec/Core/Tree.hs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ module Test.Hspec.Core.Tree (
SpecTree
, Tree (..)
, Item (..)
, setItemTimeout
, specGroup
, specItem
, bimapTree
Expand All @@ -34,6 +35,7 @@ import System.FilePath
import qualified Data.CallStack as CallStack

import Test.Hspec.Core.Example
import Test.Hspec.Core.Clock (Seconds)

-- | Internal tree data structure
data Tree c a =
Expand Down Expand Up @@ -116,8 +118,14 @@ data Item a = Item {

-- | Example for behavior
, itemExample :: Params -> (ActionWith a -> IO ()) -> ProgressCallback -> IO Result

-- | Maximum number of seconds the test can run for.
, itemTimeout :: Maybe Seconds
}

setItemTimeout :: Maybe Seconds -> Item a -> Item a
setItemTimeout x i = i {itemTimeout = x}

-- | The @specGroup@ function combines a list of specs into a larger spec.
specGroup :: HasCallStack => String -> [SpecTree a] -> SpecTree a
specGroup s = Node msg
Expand All @@ -135,6 +143,7 @@ specItem s e = Leaf Item {
, itemIsParallelizable = Nothing
, itemIsFocused = False
, itemExample = safeEvaluateExample e
, itemTimeout = Nothing
}

location :: HasCallStack => Maybe Location
Expand Down
1 change: 1 addition & 0 deletions hspec-core/test/Helper.hs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ module Helper (
, withTempDirectory
, inTempDirectory

, silentConfig
, hspecSilent
, hspecResultSilent
, hspecCapture
Expand Down
13 changes: 13 additions & 0 deletions hspec-core/test/Test/Hspec/Core/RunnerSpec.hs
Original file line number Diff line number Diff line change
Expand Up @@ -387,6 +387,18 @@ spec = do
, "2 examples, 1 failure"
]

describe "timeout" $ do
let success = Test.Hspec.Core.Runner.Result.isSuccess
it "exceeds timeout" $ do
s <- H.hspecWithResult (silentConfig {H.configTimeout = Just 0}) $
H.it "should fail from timeout" $ threadDelay 10000000
success s `shouldBe` False

it "within timeout" $ do
s <- H.hspecWithResult (silentConfig {H.configTimeout = Just 100}) $
H.it "should succeed" $ True `shouldBe` True
success s `shouldBe` True

context "with --fail-fast" $ do
it "stops after first failure" $ do
hspecCapture ["--fail-fast", "--seed", "23"] $ do
Expand Down Expand Up @@ -1017,3 +1029,4 @@ spec = do
context "on failure" $ do
it "returns False" $ do
H.rerunAll config (Just report) result { specResultSuccess = False } `shouldBe` False

Loading