packages feed

lsm-tree-1.0.0.0: test-control/Test/Control/Concurrent/Class/MonadSTM/RWVar.hs

module Test.Control.Concurrent.Class.MonadSTM.RWVar (tests) where

import qualified Control.Concurrent.Class.MonadSTM.RWVar as RW
import           Control.Monad.Class.MonadAsync
import           Control.Monad.Class.MonadSay (MonadSay (say))
import           Control.Monad.Class.MonadTest (MonadTest (exploreRaces))
import           Control.Monad.Class.MonadThrow
import           Control.Monad.IOSim
import           Test.QuickCheck
import           Test.Tasty
import           Test.Tasty.QuickCheck

tests :: TestTree
tests = testGroup "Test.Control.Concurrent.Class.MonadSTM.RWVar" [
      testProperty "prop_noRace" prop_noRace
    ]

data Action a = Read a | Incr a
  deriving stock (Show, Eq, Read)

instance Arbitrary a => Arbitrary (Action a) where
  arbitrary = oneof [
        Read <$> arbitrary
      , Incr <$> arbitrary
      ]
  shrink (Read x) = [Read x' | x' <- shrink x]
  shrink (Incr x) = [Incr x' | x' <- shrink x]

-- | Performing reads and increments on an @'RWVar' Int@ in parallel should not
-- lead to races. We let IOSimPOR check that there are no deadlocks. We also
-- look at the trace to see if the value inside the 'RWVar' increases
-- monotonically.
prop_noRace ::
     Action ()
  -> Action ()
  -> Action ()
  -> Property
prop_noRace a1 a2 a3 = exploreSimTrace modifyOpts prop $ \_ tr ->
    case traceResult False tr of
      Left e -> counterexample (show e) (property False)
      _      -> propSayMonotonic tr
  where
    modifyOpts = id

    prop :: IOSim s ()
    prop = do
      exploreRaces
      var <- RW.new (0 :: Int)
      let g = \case
            Read () ->
              RW.withReadAccess var $ \x -> do
                say (show (Read x))
            Incr () ->
              RW.withWriteAccess_ var $ \x -> do
                let x' = x + 1
                say (show (Incr x'))
                pure x'
      t1 <- async (g a1)
      t2 <- async (g a2)
      t3 <- async (g a3)
      async (cancel t1) >>= wait
      (_ :: Either AsyncCancelled ()) <- try (wait t1)
      (_ :: Either AsyncCancelled ()) <- try (wait t2)
      (_ :: Either AsyncCancelled ()) <- try (wait t3)
      pure ()

propSayMonotonic :: SimTrace () -> Property
propSayMonotonic simTrace = propResultsMonotonic (actionResults simTrace)

propResultsMonotonic :: [Action Int] -> Property
propResultsMonotonic as =
    counterexample
      ("Action results are not monotonic: " ++ show as)
      (resultsMonotonic as)

resultsMonotonic :: [Action Int] -> Bool
resultsMonotonic = go 0
  where
    go _ []               = True
    go prev (Read x : as) = prev == x     && go x as
    go prev (Incr x : as) = prev + 1 == x && go x as

actionResults :: SimTrace () -> [Action Int]
actionResults = map read . selectTraceEventsSay