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
@@ -60,6 +64,8 @@ tests =
6064 , testProperty " timeout" prop_timeout
6165 , testProperty " timeouts" prop_timeouts
6266 , testProperty " stacked timeouts" prop_stacked_timeouts
67+ , testProperty " timeout: discardAfter" unit_discardAfter
68+ , testProperty " timeout: within" unit_within
6369 , testProperty " async exceptions 1" unit_timeouts_and_async_exceptions_1
6470 , testProperty " async exceptions 2" unit_timeouts_and_async_exceptions_2
6571 , testProperty " async exceptions 3" unit_timeouts_and_async_exceptions_3
@@ -1073,6 +1079,42 @@ prop_stacked_timeouts timeout0 timeout1 actionDuration =
10731079 = Just Nothing
10741080
10751081
1082+ -- | Check that `discardAfter` works as expected.
1083+ --
1084+ -- NOTE: using `discardAfter` with `IOSim` is more tricky than for `IO`
1085+ -- properties, since `IOSim` is a pure computation. One need to wrap the
1086+ -- simulation in a lambda and use `-fno-full-laziness` to avoid GHC from
1087+ -- moving the thunk outside of the lambda, and evaluating it just once.
1088+ --
1089+ unit_discardAfter :: Property
1090+ unit_discardAfter = mapTotalResult f $ discardAfter 10 $ \ () -> runSimOrThrow $ True <$ (forever (threadDelay 10 ))
1091+ where
1092+ -- if `discard` kills the computation with the `Timeout` exception,
1093+ -- `theException` is `Nothing`, but if `traceResult` wraps it, then it is
1094+ -- a `Just`. We mark each test a success if `theException` is `Nothing`,
1095+ -- otherwise the test would fail with too many discarded cases, but if we re
1096+ -- introduce the bug in `traceResult` then it fails, since then
1097+ -- `theException` is a `Just`.
1098+ f :: QC. Result -> QC. Result
1099+ f r@ MkResult { QC. theException = Nothing }
1100+ = r { ok = Just True }
1101+ f r = r
1102+
1103+
1104+ -- | Check that `within` works as expected.
1105+ --
1106+ unit_within :: Property
1107+ unit_within = mapTotalResult f $ within 1 $ runSimOrThrow $ True <$ (forever (threadDelay 10 ))
1108+ where
1109+ -- if `within` kills the computation with the `Timeout` exception,
1110+ -- `theException` is `Nothing`, but if `traceResult` wraps it, then it is
1111+ -- a `Just`.
1112+ f :: QC. Result -> QC. Result
1113+ f r@ MkResult { QC. theException = Nothing }
1114+ = r { expect = False }
1115+ f r = r
1116+
1117+
10761118unit_timeouts_and_async_exceptions_1 :: Property
10771119unit_timeouts_and_async_exceptions_1 =
10781120 let trace = runSimTrace experiment in
0 commit comments