11{-# LANGUAGE CPP #-}
22
33{-# OPTIONS_GHC -Wno-orphans #-}
4+ -- `-fno-full-laziness` is needed for `discardAfter` to work correctly, see
5+ -- `unit_discardAfter` below.
6+ {-# OPTIONS_GHC -fno-full-laziness #-}
47
58module Test.Control.Monad.IOSim
69 ( tests
@@ -43,6 +46,7 @@ import Test.Control.Monad.STM
4346import Test.Control.Monad.Utils
4447
4548import Test.QuickCheck
49+ import Test.QuickCheck.Property as QC
4650import Test.Tasty hiding (after )
4751import Test.Tasty.QuickCheck
4852
@@ -67,6 +71,10 @@ tests =
6771 , testProperty " {register,thread}Delay" unit_registerDelay_threadDelay
6872 , testProperty " throwTo and STM" unit_throwTo_and_stm
6973 ]
74+ , testGroup " QuickCheck"
75+ [ testProperty " timeout: discardAfter" unit_discardAfter
76+ , testProperty " timeout: within" unit_within
77+ ]
7078 , testProperty " threadId order (IOSim)" (withMaxSuccess 1000 prop_threadId_order_order_Sim)
7179 , testProperty " forkIO order (IOSim)" (withMaxSuccess 1000 prop_fork_order_ST)
7280 , testProperty " order (IO)" (expectFailure prop_fork_order_IO)
@@ -1073,6 +1081,46 @@ prop_stacked_timeouts timeout0 timeout1 actionDuration =
10731081 = Just Nothing
10741082
10751083
1084+ -- | Check that `discardAfter` works as expected.
1085+ --
1086+ -- NOTE: using `discardAfter` with `IOSim` is more tricky than for `IO`
1087+ -- properties, since `IOSim` is a pure computation. One need to wrap the
1088+ -- simulation in a lambda and use `-fno-full-laziness` to avoid GHC from
1089+ -- moving the thunk outside of the lambda, and evaluating it just once.
1090+ --
1091+ unit_discardAfter :: Property
1092+ unit_discardAfter = mapTotalResult f
1093+ . discardAfter 10
1094+ $ \ () -> runSimOrThrow $ True <$ (forever (threadDelay 10 ))
1095+ where
1096+ -- if `discard` kills the computation with the `Timeout` exception,
1097+ -- `theException` is `Nothing`, but if `traceResult` wraps it, then it is
1098+ -- a `Just`. We mark each test a success if `theException` is `Nothing`,
1099+ -- otherwise the test would fail with too many discarded cases, but if we re
1100+ -- introduce the bug in `traceResult` then it fails, since then
1101+ -- `theException` is a `Just`.
1102+ f :: QC. Result -> QC. Result
1103+ f r@ MkResult { QC. theException = Nothing }
1104+ = r { ok = Just True }
1105+ f r = r
1106+
1107+
1108+ -- | Check that `within` works as expected.
1109+ --
1110+ unit_within :: Property
1111+ unit_within = mapTotalResult f
1112+ . within 10
1113+ $ runSimOrThrow $ True <$ (forever (threadDelay 10 ))
1114+ where
1115+ -- if `within` kills the computation with the `Timeout` exception,
1116+ -- `theException` is `Nothing`, but if `traceResult` wraps it, then it is
1117+ -- a `Just`.
1118+ f :: QC. Result -> QC. Result
1119+ f r@ MkResult { QC. theException = Nothing }
1120+ = r { expect = False }
1121+ f r = r
1122+
1123+
10761124unit_timeouts_and_async_exceptions_1 :: Property
10771125unit_timeouts_and_async_exceptions_1 =
10781126 let trace = runSimTrace experiment in
0 commit comments