Skip to content

Commit eb1507e

Browse files
committed
Add a 10 min timeout to it
Instead of causing the CI to timeout, we should now get the test causing the timeout to fail.
1 parent 4862a19 commit eb1507e

File tree

2 files changed

+63
-22
lines changed

2 files changed

+63
-22
lines changed

lib/test-utils/src/Test/Hspec/Extra.hs

Lines changed: 43 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
{-# LANGUAGE LambdaCase #-}
33
{-# LANGUAGE ScopedTypeVariables #-}
44
{-# LANGUAGE TupleSections #-}
5+
{-# LANGUAGE TypeApplications #-}
56

67
-- |
78
-- Copyright: © 2018-2020 IOHK
@@ -13,16 +14,23 @@
1314
module Test.Hspec.Extra
1415
( aroundAll
1516
, it
17+
, itWithCustomTimeout
1618
) where
1719

1820
import Prelude
1921

22+
import Control.Concurrent
23+
( threadDelay )
2024
import Control.Concurrent.Async
21-
( async, race, wait )
25+
( AsyncCancelled, async, race, race_, wait )
2226
import Control.Concurrent.MVar
2327
( MVar, newEmptyMVar, putMVar, takeMVar )
28+
import Control.Exception
29+
( fromException )
2430
import Control.Exception
2531
( SomeException, throwIO, try )
32+
import Data.Maybe
33+
( isJust )
2634
import Test.Hspec
2735
( ActionWith
2836
, HasCallStack
@@ -31,6 +39,7 @@ import Test.Hspec
3139
, afterAll
3240
, beforeAll
3341
, beforeWith
42+
, expectationFailure
3443
, specify
3544
)
3645

@@ -104,17 +113,39 @@ aroundAll acquire =
104113
-- | A drop-in replacement for 'it' that'll automatically retry a scenario once
105114
-- if it fails, to cope with potentially flaky tests.
106115
it :: HasCallStack => String -> ActionWith ctx -> SpecWith ctx
107-
it title action = specify title $ \ctx -> do
108-
res1 <- try $ action ctx
109-
case res1 of
110-
Right r1 -> return r1
111-
Left (e1 :: SomeException) -> do
112-
res2 <- try $ action ctx
113-
case res2 of
114-
-- If the second try fails, return the first error. The
115-
-- second error might not be helpful.
116-
Left (_e2 :: SomeException) -> throwIO e1
117-
Right r2 -> return r2
116+
it = itWithCustomTimeout (10*minute)
117+
where
118+
minute = 60*1000*1000
119+
120+
-- | Like @it@ but with a custom timeout, which makes it realistic to test.
121+
itWithCustomTimeout :: HasCallStack => Int -> String -> ActionWith ctx -> SpecWith ctx
122+
itWithCustomTimeout micro title action = specify title $ \ctx -> do
123+
race_ timeout $ do
124+
res1 <- try $ action ctx
125+
case res1 of
126+
Right r1 -> return r1
127+
Left (e1 :: SomeException)
128+
-- If we time out during the first attempt, then we /should/ time
129+
-- out.
130+
| isAsyncCancelled e1 -> throwIO e1
131+
| otherwise -> do
132+
res2 <- try $ action ctx
133+
case res2 of
134+
-- If the second try fails, return the first error. The
135+
-- second error might not be helpful.
136+
Left (e2 :: SomeException)
137+
| isAsyncCancelled e2 -> throwIO e1
138+
| otherwise -> throwIO e1
139+
Right r2 -> return r2
140+
where
141+
isAsyncCancelled = isJust . fromException @AsyncCancelled
142+
timeout = do
143+
threadDelay micro
144+
putStrLn "-------- timing out "
145+
expectationFailure
146+
$ "timed out in "
147+
<> show (micro `div` 1000000)
148+
<> " seconds"
118149

119150
-- | Some helper to help readability on the thread synchronization above.
120151
await :: MVar () -> IO ()

lib/test-utils/test/Test/Hspec/ExtraSpec.hs

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ module Test.Hspec.ExtraSpec where
22

33
import Prelude
44

5+
import Control.Concurrent
6+
( threadDelay )
57
import Data.IORef
68
( IORef, newIORef, readIORef, writeIORef )
79
import Data.List
@@ -18,6 +20,7 @@ import Test.Hspec
1820
, expectationFailure
1921
, it
2022
, shouldBe
23+
, shouldContain
2124
)
2225
import Test.Hspec.Core.Runner
2326
( defaultConfig, runSpec )
@@ -52,6 +55,14 @@ spec = do
5255
let noRetry = expectationFailure "test can't be retried"
5356
outcomes <- newIORef [failure, noRetry]
5457
(dynamically outcomes) `shouldMatchHSpecIt` failure
58+
it "can time out" $ do
59+
let s = 1000*1000
60+
let timeout = do
61+
threadDelay (10 * s)
62+
expectationFailure "should have timed out"
63+
res <- run (Extra.itWithCustomTimeout $ 2 * s) timeout
64+
res `shouldContain` "timed out in 2 seconds"
65+
5566
where
5667
-- | lhs `shouldMatchHSpecIt` rhs asserts that the output of running
5768
-- (Extra.it "" lhs) and (Hspec.it "" rhs) are equal. Modulo random seed-
@@ -62,17 +73,16 @@ spec = do
6273
hspecRes <- run it hspecTest
6374
extraRes `shouldBe` hspecRes
6475

76+
run
77+
:: (String -> ActionWith () -> SpecWith ())
78+
-> IO ()
79+
-> IO String
80+
run anyIt prop = fmap stripTime
81+
$ capture_
82+
$ flip runSpec defaultConfig
83+
$ beforeAll (return ())
84+
$ anyIt "<test spec>" (const prop)
6585
where
66-
run
67-
:: (String -> ActionWith () -> SpecWith ())
68-
-> IO ()
69-
-> IO String
70-
run anyIt prop = fmap stripTime
71-
$ capture_
72-
$ flip runSpec defaultConfig
73-
$ beforeAll (return ())
74-
$ anyIt "<test spec>" (const prop)
75-
7686
stripTime :: String -> String
7787
stripTime = unlines
7888
. filter (not . ("Finished in" `isPrefixOf`))

0 commit comments

Comments
 (0)