{-# LANGUAGE RecursiveDo #-} {----------------------------------------------------------------------------- reactive-banana ------------------------------------------------------------------------------} -- | Exemplar tests for space usage and garbage collection. module Reactive.Banana.Test.High.Space where import Control.Monad ( forM ) import Test.Tasty ( testGroup, TestTree ) import Test.Tasty.QuickCheck ( testProperty ) import qualified Test.QuickCheck as Q import qualified Test.QuickCheck.Monadic as Q import qualified Control.Exception as Memory import qualified Control.Concurrent as System import qualified System.Mem as System import Reactive.Banana import Reactive.Banana.Frameworks tests :: TestTree tests = testGroup "Space usage, high level" [ testGroup "Network size stays bounded" [ testBoundedNetworkSize "execute" execute1 , testBoundedNetworkSize "observe accumE, issue #261" observeAccumE1 , testBoundedNetworkSize "execute accumE, issue #261" executeAccumE1 , testBoundedNetworkSize "switch accumE, issue #261" switchAccumE1 ] ] {----------------------------------------------------------------------------- Tests ------------------------------------------------------------------------------} execute1 :: Event Int -> MomentIO (Event (Event Int)) execute1 e = execute $ (\i -> liftIO $ Memory.evaluate (i <$ e)) <$> e observeAccumE1 :: Event Int -> MomentIO (Event (Event ())) observeAccumE1 e = pure $ observeE (accumE () never <$ e) executeAccumE1 :: Event Int -> MomentIO (Event (Event ())) executeAccumE1 e = execute (accumE () (id <$ e) <$ e) switchAccumE1 :: Event Int -> MomentIO (Event ()) switchAccumE1 e = do let e2 :: Event (Event ()) e2 = observeE (accumE () (id <$ e) <$ e) switchE never e2 {----------------------------------------------------------------------------- Test harness ------------------------------------------------------------------------------} -- | Execute an FRP network with a sequence of inputs -- with intermittend of garbage collection and record network sizes. runNetworkSizes :: (Event a -> MomentIO (Event ignore)) -> [a] -> IO [Int] runNetworkSizes f xs = do (network, fire) <- setup run network fire where setup = do (ah, fire) <- newAddHandler network <- compile $ do ein <- fromAddHandler ah eout <- f ein reactimate $ pure () <$ eout performSufficientGC actuate network pure (network, fire) run network fire = forM xs $ \i -> do fire i performSufficientGC System.yield Memory.evaluate =<< getSize network -- | Test whether the network size stays bounded. testBoundedNetworkSize :: String -> (Event Int -> MomentIO (Event ignore)) -> TestTree testBoundedNetworkSize name f = testProperty name $ Q.once $ Q.monadicIO $ do sizes <- liftIO $ runNetworkSizes f [1..n] Q.monitor $ Q.counterexample "network size grows" . Q.counterexample ("network sizes: " <> show sizes) Q.assert $ isBounded sizes where n = 20 :: Int isBounded sizes = sizes !! 3 >= sizes !! (n-1) performSufficientGC :: IO () performSufficientGC = System.performMinorGC