33{-# LANGUAGE CPP #-}
44{-# LANGUAGE DeriveLift #-}
55{-# LANGUAGE DoAndIfThenElse #-}
6+ {-# LANGUAGE FlexibleContexts #-}
67{-# LANGUAGE GeneralizedNewtypeDeriving #-}
78{-# LANGUAGE LambdaCase #-}
89{-# LANGUAGE NoImplicitPrelude #-}
@@ -31,7 +32,10 @@ import Control.Concurrent.STM (TVar, atomically)
3132import qualified Control.Concurrent.STM.TVar as TVar
3233import Control.Exception.Safe (MonadCatch , catchAny )
3334import Control.Monad.IO.Class (MonadIO (.. ))
35+ import Control.Monad.Trans.Control (MonadBaseControl (.. ))
36+ import Data.IORef (newIORef , readIORef , writeIORef )
3437import Data.Maybe (isJust )
38+ import qualified System.Timeout as T
3539
3640import Hedgehog.Internal.Config
3741import Hedgehog.Internal.Gen (evalGenT )
@@ -42,6 +46,7 @@ import Hedgehog.Internal.Property (Journal(..), Coverage(..), CoverCou
4246import Hedgehog.Internal.Property (Property (.. ), PropertyConfig (.. ), PropertyName (.. ))
4347import Hedgehog.Internal.Property (PropertyT (.. ), Failure (.. ), runTestT )
4448import Hedgehog.Internal.Property (ShrinkLimit , ShrinkRetries , withTests , withSkip )
49+ import Hedgehog.Internal.Property (ShrinkTimeoutMicros (.. ))
4550import Hedgehog.Internal.Property (TerminationCriteria (.. ))
4651import Hedgehog.Internal.Property (TestCount (.. ), PropertyCount (.. ))
4752import Hedgehog.Internal.Property (confidenceSuccess , confidenceFailure )
@@ -118,17 +123,27 @@ runTreeN n m = do
118123 pure o
119124
120125takeSmallest ::
121- MonadIO m
126+ forall m .
127+ ( MonadBaseControl IO m
128+ , MonadIO m
129+ )
122130 => ShrinkCount
123131 -> ShrinkPath
124132 -> ShrinkLimit
133+ -> Maybe ShrinkTimeoutMicros
125134 -> ShrinkRetries
126135 -> (Progress -> m () )
127136 -> NodeT m (Maybe (Either Failure () , Journal ))
128137 -> m Result
129- takeSmallest shrinks0 (ShrinkPath shrinkPath0) slimit retries updateUI =
138+ takeSmallest shrinks0 (ShrinkPath shrinkPath0) slimit mstimeLimit retries updateUI =
130139 let
131- loop shrinks revShrinkPath = \ case
140+ loop ::
141+ ShrinkCount
142+ -> [Int ]
143+ -> (Result -> m () )
144+ -> NodeT m (Maybe (Either Failure () , Journal ))
145+ -> m Result
146+ loop shrinks revShrinkPath updateResultSoFar = \ case
132147 NodeT Nothing _ ->
133148 pure GaveUp
134149
@@ -141,6 +156,7 @@ takeSmallest shrinks0 (ShrinkPath shrinkPath0) slimit retries updateUI =
141156 failure =
142157 mkFailure shrinks shrinkPath Nothing loc err mdiff (reverse logs)
143158
159+ updateResultSoFar (Failed failure)
144160 updateUI $ Shrinking failure
145161
146162 if shrinks >= fromIntegral slimit then
@@ -150,14 +166,27 @@ takeSmallest shrinks0 (ShrinkPath shrinkPath0) slimit retries updateUI =
150166 findM (zip [0 .. ] xs) (Failed failure) $ \ (n, m) -> do
151167 o <- runTreeN retries m
152168 if isFailure o then
153- Just <$> loop (shrinks + 1 ) (n : revShrinkPath) o
169+ Just <$> loop (shrinks + 1 ) (n : revShrinkPath) updateResultSoFar o
154170 else
155171 return Nothing
156172
157173 Right () ->
158174 return OK
159- in
160- loop shrinks0 (reverse shrinkPath0)
175+ runLoop = loop shrinks0 (reverse shrinkPath0)
176+ in case mstimeLimit of
177+ -- no time limit, shrink normally
178+ Nothing -> runLoop (const (pure () ))
179+ -- run the loop in the timeout
180+ Just (ShrinkTimeoutMicros timeLimit) -> \ nodeT -> do
181+ resultSoFar <- liftIO $ newIORef Nothing
182+ let updateResultSoFar = liftIO . writeIORef resultSoFar . Just
183+ timeout timeLimit (runLoop updateResultSoFar nodeT) >>= \ case
184+ -- timed out, return preliminary result if it exists
185+ Nothing -> liftIO (readIORef resultSoFar) <&> \ case
186+ Nothing -> GaveUp
187+ Just r -> r
188+ -- did not time out, return result
189+ Just r -> pure r
161190
162191-- | Follow a given shrink path, instead of searching exhaustively. Assume that
163192-- the end of the path is minimal, and don't try to shrink any further than
@@ -204,7 +233,9 @@ skipToShrink (ShrinkPath shrinkPath) updateUI =
204233
205234checkReport ::
206235 forall m .
207- MonadIO m
236+ ( MonadBaseControl IO m
237+ , MonadIO m
238+ )
208239 => MonadCatch m
209240 => PropertyConfig
210241 -> Size
@@ -361,6 +392,7 @@ checkReport cfg size0 seed0 test0 updateUI = do
361392 0
362393 (ShrinkPath [] )
363394 (propertyShrinkLimit cfg)
395+ (propertyShrinkTimeoutMicros cfg)
364396 (propertyShrinkRetries cfg)
365397 (updateUI . mkReport)
366398 node
@@ -594,3 +626,16 @@ checkParallel =
594626 , runnerVerbosity =
595627 Nothing
596628 }
629+
630+ -- vendored from lifted-base
631+ timeout :: MonadBaseControl IO m => Int -> m a -> m (Maybe a )
632+ timeout t m =
633+ liftBaseWith (\ runInIO -> T. timeout t (runInIO m)) >>=
634+ maybe (pure Nothing ) (fmap Just . restoreM)
635+
636+ -- vendored from base's Data.Functor until base < 4.11.0.0 is dropped
637+ -- (ghc 8.4.1)
638+ (<&>) :: Functor f => f a -> (a -> b ) -> f b
639+ as <&> f = f <$> as
640+
641+ infixl 1 <&>
0 commit comments