reactive-banana-1.3.2.0: test/Reactive/Banana/Test/Low/GraphGC.hs
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE RecordWildCards #-}
{-----------------------------------------------------------------------------
reactive-banana
------------------------------------------------------------------------------}
-- | Property tests for 'GraphGC'.
module Reactive.Banana.Test.Low.GraphGC
( tests
) where
import Control.Monad
( when )
import Control.Monad.IO.Class
( liftIO )
import Data.Map.Strict
( Map )
import Data.Unique.Really
( Unique )
import Reactive.Banana.Prim.Low.Graph
( Graph )
import Reactive.Banana.Prim.Low.GraphGC
( GraphGC )
import Reactive.Banana.Test.Low.Gen
( DeltaGraph (..), TestGraph (..), Vertex )
import Test.QuickCheck
( Gen, Property, (===), (=/=) )
import Test.Tasty
( testGroup, TestTree )
import Test.Tasty.QuickCheck
( testProperty )
import qualified Data.List as List
import qualified Data.Map as Map
import qualified Data.Set as Set
import qualified Control.DeepSeq as Memory
import qualified Control.Exception as Memory
import qualified System.Mem as System
import qualified Control.Concurrent as System
import qualified Test.QuickCheck as Q
import qualified Test.QuickCheck.Monadic as Q
import qualified Reactive.Banana.Test.Low.Graph as Q
import qualified Reactive.Banana.Test.Low.Gen as Q
import qualified Reactive.Banana.Prim.Low.Graph as Graph
import qualified Reactive.Banana.Prim.Low.GraphGC as GraphGC
import qualified Reactive.Banana.Prim.Low.Ref as Ref
tests :: TestTree
tests = testGroup "GraphGC"
[ testGroup "Garbage collection (GC)"
[ testProperty "retains the reachable vertices" prop_performGC
, testProperty "not doing GC retains all vertices" prop_notPerformGC
]
]
{-----------------------------------------------------------------------------
Properties
------------------------------------------------------------------------------}
prop_performGC :: Property
prop_performGC =
Q.forAll Q.genTestGraph
$ \g0 -> Q.forAll (genGarbageCollectionRoots g0)
$ \roots ->
let g = Q.mkGraph g0
expected = Graph.collectGarbage roots g
in Q.cover 10 (Graph.size g == Graph.size expected)
"no vertices unreachable"
$ Q.cover 75 (Graph.size g > Graph.size expected)
"some vertices unreachable"
$ Q.cover 15 (Graph.size g > 2*Graph.size expected)
"many vertices unreachable"
$ Q.monadicIO $ liftIO $ do
(actual, vertices) <- mkGraphGC g0
let rootRefs = map (vertices Map.!) roots
Memory.evaluate $ Memory.rnf rootRefs
System.performMajorGC
GraphGC.removeGarbage actual
reachables <- traverse Ref.read =<<
GraphGC.listReachableVertices actual
-- keep rootsRef reachable until this point
rootsFromRef <- traverse Ref.read rootRefs
pure $
( roots === rootsFromRef )
Q..&&.
( Set.fromList (Graph.listConnectedVertices expected)
=== Set.fromList reachables
)
prop_notPerformGC :: Property
prop_notPerformGC =
Q.forAll Q.genSquareSide
$ \n -> Q.monadicIO $ liftIO $ do
-- Trigger a garbage collection now so that it is
-- highly unlikely to happen in the subsequent lines
System.performMinorGC
let g = Q.mkLinearChain n
(actual, _) <- mkGraphGC g
GraphGC.removeGarbage actual
reachables <- traverse Ref.read =<<
GraphGC.listReachableVertices actual
pure $
Set.fromList reachables === Set.fromList [1..n]
{-----------------------------------------------------------------------------
Test graphs
------------------------------------------------------------------------------}
-- | Generate a 'GraphGC' from a 'TestGraph'.
mkGraphGC :: TestGraph -> IO (GraphGC Vertex, Map Vertex (Ref.Ref Vertex))
mkGraphGC TestGraph{vertices,edges} = do
g <- GraphGC.new
refMap <- Map.fromList . zip vertices <$> traverse Ref.new vertices
let insertEdge (InsertEdge x y) = do
GraphGC.insertEdge (refMap Map.! x, refMap Map.! y) g
traverse insertEdge edges
pure (g, refMap)
-- | Randomly generate a set of garbage collection roots.
genGarbageCollectionRoots :: TestGraph -> Gen [Vertex]
genGarbageCollectionRoots TestGraph{vertices} = Q.sized $ \n ->
sequence . replicate (n `mod` 10) $ Q.elements vertices