44module Test.Control.RefCount (tests ) where
55
66import Control.Concurrent.Class.MonadMVar
7- import Control.Exception
7+ import Control.Exception ( AssertionFailed ( .. ))
88import Control.Monad
9+ import Control.Monad.Class.MonadThrow
10+ import Control.Monad.IOSim (IOSim , runSimOrThrow )
11+ import Control.Monad.Primitive
912import Control.RefCount
1013import Data.Primitive.PrimVar
1114import Test.Tasty (TestTree , testGroup )
1215import Test.Tasty.QuickCheck
1316
1417#ifdef NO_IGNORE_ASSERTS
15- import Data.IORef
18+ import Data.Primitive
1619#endif
1720
1821tests :: TestTree
1922tests = 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
107151readRefCount (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
155210prop_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
167223prop_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
181236expectRefNeverReleased e@ RefNeverReleased {} =
182237 -- Print the displayed exception as an example
183238 return (tabulate " displayException" [displayException e] (property True ))
184239expectRefNeverReleased 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