Skip to content

Commit d974eda

Browse files
committed
Run RefCount tests in both IO and IOSim
This serves as a sanity check for the changes introduced by previous commits
1 parent 286a8e9 commit d974eda

File tree

1 file changed

+102
-45
lines changed

1 file changed

+102
-45
lines changed

test-control/Test/Control/RefCount.hs

Lines changed: 102 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -4,37 +4,81 @@
44
module Test.Control.RefCount (tests) where
55

66
import Control.Concurrent.Class.MonadMVar
7-
import Control.Exception
7+
import Control.Exception (AssertionFailed (..))
88
import Control.Monad
9+
import Control.Monad.Class.MonadThrow
10+
import Control.Monad.IOSim (IOSim, runSimOrThrow)
11+
import Control.Monad.Primitive
912
import Control.RefCount
1013
import Data.Primitive.PrimVar
1114
import Test.Tasty (TestTree, testGroup)
1215
import Test.Tasty.QuickCheck
1316

1417
#ifdef NO_IGNORE_ASSERTS
15-
import Data.IORef
18+
import Data.Primitive
1619
#endif
1720

1821
tests :: TestTree
1922
tests = testGroup "Test.Control.RefCount" [
20-
testProperty "prop_RefCounter" prop_RefCounter
23+
testProperty "prop_RefCounter @IO" $
24+
ioPropertyOnce prop_RefCounter
25+
#ifndef NO_IGNORE_ASSERTS
26+
-- prop_RefCounter throws and catches AssertionFailed exceptions, but we
27+
-- can only catch these exceptions in IO. In IOSim, these uncaught
28+
-- exceptions will lead to property failures. So, we only run the property
29+
-- in IOSim if assertions are turned off.
30+
, testProperty "prop_RefCounter @IOSim" $
31+
ioSimPropertyOnce prop_RefCounter
32+
#endif
2133
#ifdef NO_IGNORE_ASSERTS
2234
-- All of these tests below are checking that the debug implementation of
2335
-- Ref does indeed detect all the violations (double-free, use-after-free,
2436
-- never-free). But this obviously depends on the debug implementation
2537
-- being in use. Hence only tested when NO_IGNORE_ASSERTS.
26-
, testProperty "prop_ref_double_free" prop_ref_double_free
27-
, testProperty "prop_ref_use_after_free" prop_ref_use_after_free
28-
, testProperty "prop_ref_never_released0" prop_ref_never_released0
29-
, testProperty "prop_ref_never_released1" prop_ref_never_released1
30-
, testProperty "prop_ref_never_released2" prop_ref_never_released2
31-
, testProperty "prop_release_ref_exception" prop_release_ref_exception
38+
, testGroup "IO" [
39+
testProperty "prop_ref_double_free" $
40+
ioPropertyOnce prop_ref_double_free
41+
, testProperty "prop_ref_use_after_free" $
42+
ioPropertyOnce $ prop_ref_use_after_free True
43+
, testProperty "prop_ref_never_released0" $
44+
ioPropertyOnce prop_ref_never_released0
45+
, testProperty "prop_ref_never_released1" $
46+
ioPropertyOnce prop_ref_never_released1
47+
, testProperty "prop_ref_never_released2" $
48+
ioPropertyOnce prop_ref_never_released2
49+
, testProperty "prop_release_ref_exception" $
50+
ioPropertyOnce prop_release_ref_exception
51+
]
52+
, testGroup "IOSim" [
53+
testProperty "prop_ref_double_free" $
54+
ioSimPropertyOnce prop_ref_double_free
55+
, testProperty "prop_ref_use_after_free" $
56+
-- Exceptions thrown by the DeRef pattern can only be caught from
57+
-- IO, so we do not test the DeRef pattern in IOSim.
58+
ioSimPropertyOnce $ prop_ref_use_after_free False
59+
, testProperty "prop_ref_never_released0" $
60+
ioSimPropertyOnce prop_ref_never_released0
61+
, testProperty "prop_ref_never_released1" $
62+
ioSimPropertyOnce prop_ref_never_released1
63+
, testProperty "prop_ref_never_released2" $
64+
ioSimPropertyOnce prop_ref_never_released2
65+
, testProperty "prop_release_ref_exception" $
66+
ioSimPropertyOnce prop_release_ref_exception
67+
]
3268
#endif
3369
]
3470

71+
ioPropertyOnce :: Testable prop => IO prop -> Property
72+
ioPropertyOnce p = once $ ioProperty p
73+
74+
ioSimPropertyOnce :: Testable prop => (forall s. IOSim s prop) -> Property
75+
ioSimPropertyOnce p = once $ property $ runSimOrThrow p
76+
3577
-- | Test for the low level RefCounter API
36-
prop_RefCounter :: Property
37-
prop_RefCounter = once $ ioProperty $ do
78+
prop_RefCounter ::
79+
(MonadMVar m, PrimMonad m, MonadMask m)
80+
=> m Property
81+
prop_RefCounter = do
3882
obj <- newMVar False
3983
ref <- newRefCounter (void $ modifyMVar_ obj (\x -> pure (not x)) )
4084

@@ -103,72 +147,83 @@ prop_RefCounter = once $ ioProperty $ do
103147
-- is no way to reliably act on the information. It is only useful for tests or
104148
-- debugging.
105149
--
106-
readRefCount :: RefCounter IO -> IO Int
150+
readRefCount :: PrimMonad m => RefCounter m -> m Int
107151
readRefCount (RefCounter countVar _) = readPrimVar countVar
108152

109153
#ifdef NO_IGNORE_ASSERTS
110-
data TestObject = TestObject !(RefCounter IO)
154+
data TestObject m = TestObject !(RefCounter m)
111155

112-
instance RefCounted IO TestObject where
156+
instance RefCounted m (TestObject m) where
113157
getRefCounter (TestObject rc) = rc
114158

115-
data TestObject2 = TestObject2 (Ref TestObject)
159+
data TestObject2 m = TestObject2 (Ref (TestObject m))
116160

117-
instance RefCounted IO TestObject2 where
161+
instance RefCounted m (TestObject2 m) where
118162
getRefCounter (TestObject2 (DeRef to1)) = getRefCounter to1
119163

120-
prop_ref_double_free :: Property
121-
prop_ref_double_free = once $ ioProperty $ do
122-
finalised <- newIORef False
123-
ref <- newRef (writeIORef finalised True) TestObject
164+
prop_ref_double_free ::
165+
(PrimMonad m, MonadMask m, MonadFail m)
166+
=> m Property
167+
prop_ref_double_free = do
168+
finalised <- newMutVar False
169+
ref <- newRef (writeMutVar finalised True) TestObject
124170
releaseRef ref
125-
True <- readIORef finalised
171+
True <- readMutVar finalised
126172
Left e@RefDoubleRelease{} <- try $ releaseRef ref
127173
checkForgottenRefs
128174
-- Print the displayed exception as an example
129175
pure $ tabulate "displayException" [displayException e] ()
130176

131-
prop_ref_use_after_free :: Property
132-
prop_ref_use_after_free = once $ ioProperty $ do
133-
finalised <- newIORef False
134-
ref <- newRef (writeIORef finalised True) TestObject
177+
prop_ref_use_after_free ::
178+
(PrimMonad m, MonadMask m, MonadFail m)
179+
=> Bool -- ^ Test the DeRef pattern
180+
-> m Property
181+
prop_ref_use_after_free testDeRef = do
182+
finalised <- newMutVar False
183+
ref <- newRef (writeMutVar finalised True) TestObject
135184
releaseRef ref
136-
True <- readIORef finalised
185+
True <- readMutVar finalised
137186
Left e@RefUseAfterRelease{} <- try $ withRef ref return
138-
Left RefUseAfterRelease{} <- try $ case ref of DeRef _ -> return ()
187+
when testDeRef $ do
188+
Left RefUseAfterRelease{} <- try $ case ref of DeRef _ -> return ()
189+
pure ()
139190
Left RefUseAfterRelease{} <- try $ dupRef ref
140191
checkForgottenRefs
141192
-- Print the displayed exception as an example
142193
pure $ tabulate "displayException" [displayException e] ()
143194

144-
prop_ref_never_released0 :: Property
145-
prop_ref_never_released0 = once $ ioProperty $ do
146-
finalised <- newIORef False
147-
ref <- newRef (writeIORef finalised True) TestObject
195+
prop_ref_never_released0 ::
196+
(PrimMonad m, MonadMask m)
197+
=> m ()
198+
prop_ref_never_released0 = do
199+
finalised <- newMutVar False
200+
ref <- newRef (writeMutVar finalised True) TestObject
148201
_ <- case ref of DeRef _ -> return ()
149202
checkForgottenRefs
150203
-- ref is still being used, so check should not fail
151204
_ <- case ref of DeRef _ -> return ()
152205
releaseRef ref
153206

154-
prop_ref_never_released1 :: Property
207+
prop_ref_never_released1 ::
208+
(PrimMonad m, MonadMask m)
209+
=> m Property
155210
prop_ref_never_released1 =
156-
once $ ioProperty $
157211
handle expectRefNeverReleased $ do
158-
finalised <- newIORef False
159-
ref <- newRef (writeIORef finalised True) TestObject
212+
finalised <- newMutVar False
213+
ref <- newRef (writeMutVar finalised True) TestObject
160214
_ <- withRef ref return
161215
_ <- case ref of DeRef _ -> return ()
162216
-- ref is never released, so should fail
163217
checkForgottenRefs
164218
return (counterexample "no forgotten refs detected" $ property False)
165219

166-
prop_ref_never_released2 :: Property
220+
prop_ref_never_released2 ::
221+
(PrimMonad m, MonadMask m)
222+
=> m Property
167223
prop_ref_never_released2 =
168-
once $ ioProperty $
169224
handle expectRefNeverReleased $ do
170-
finalised <- newIORef False
171-
ref <- newRef (writeIORef finalised True) TestObject
225+
finalised <- newMutVar False
226+
ref <- newRef (writeMutVar finalised True) TestObject
172227
ref2 <- dupRef ref
173228
releaseRef ref
174229
_ <- withRef ref2 return
@@ -177,19 +232,21 @@ prop_ref_never_released2 =
177232
checkForgottenRefs
178233
return (counterexample "no forgotten refs detected" $ property False)
179234

180-
expectRefNeverReleased :: RefException -> IO Property
235+
expectRefNeverReleased :: Monad m => RefException -> m Property
181236
expectRefNeverReleased e@RefNeverReleased{} =
182237
-- Print the displayed exception as an example
183238
return (tabulate "displayException" [displayException e] (property True))
184239
expectRefNeverReleased e =
185240
return (counterexample (displayException e) $ property False)
186241

187242
-- | If a finaliser throws an exception, then the 'RefTracker' is still released
188-
prop_release_ref_exception :: Property
189-
prop_release_ref_exception = once $ ioProperty $ do
190-
finalised <- newIORef False
191-
ref <- newRef (writeIORef finalised True >> error "oops") TestObject
192-
_ <- try @SomeException (releaseRef ref)
243+
prop_release_ref_exception ::
244+
(PrimMonad m, MonadMask m)
245+
=> m ()
246+
prop_release_ref_exception = do
247+
finalised <- newMutVar False
248+
ref <- newRef (writeMutVar finalised True >> throwIO (userError "oops")) TestObject
249+
_ <- try @_ @SomeException (releaseRef ref)
193250
checkForgottenRefs
194251
#endif
195252

0 commit comments

Comments
 (0)