packages feed

strict-checked-vars-0.2.1.0: test/Test/Control/Concurrent/Class/MonadSTM/Strict/TVar/Checked/WHNF.hs

{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE TupleSections #-}

module Test.Control.Concurrent.Class.MonadSTM.Strict.TVar.Checked.WHNF where

import Control.Concurrent.Class.MonadSTM (MonadSTM, STM, atomically)
import Control.Concurrent.Class.MonadSTM.Strict.TVar.Checked hiding
  ( newTVar
  , newTVarIO
  , newTVarWithInvariant
  , newTVarWithInvariantIO
  )
import Control.Concurrent.Class.MonadSTM.Strict.TVar.Checked qualified as Checked
import Control.Monad (void)
import Control.Monad.IOSim (runSimOrThrow)
import Data.Typeable (Typeable)
import NoThunks.Class (OnlyCheckWhnf (..), unsafeNoThunks)
import Test.Tasty (TestTree, testGroup)
import Test.Tasty.QuickCheck
  ( Fun
  , Property
  , applyFun
  , counterexample
  , ioProperty
  , property
  , testProperty
  )
import Test.Utils (Invariant (..), (..:))

{-------------------------------------------------------------------------------
  Main test tree
-------------------------------------------------------------------------------}

tests :: TestTree
tests =
  testGroup
    "Test.Control.Concurrent.Class.MonadSTM.Strict.TVar.Checked.WHNF"
    [ testGroup "IO" testIO
    , testGroup "IOSim" testIOSim
    ]
 where
  testIO =
    [ testProperty
        "prop_newTVarWithInvariant_IO"
        prop_newTVarWithInvariant_IO
    , testProperty
        "prop_newTVarWithInvariantIO_IO"
        prop_newTVarWithInvariantIO_IO
    , testProperty
        "prop_writeTVar_IO"
        prop_writeTVar_IO
    , testProperty
        "prop_modifyTVar_IO"
        prop_modifyTVar_IO
    , testProperty
        "prop_stateTVar_IO"
        prop_stateTVar_IO
    , testProperty
        "prop_swapTVar_IO"
        prop_swapTVar_IO
    ]

  testIOSim =
    [ testProperty
        "prop_newTVarWithInvariant_IOSim"
        prop_newTVarWithInvariant_IOSim
    , testProperty
        "prop_newTVarWithInvariantIO_IOSim"
        prop_newTVarWithInvariantIO_IOSim
    , testProperty
        "prop_writeTVar_IOSim"
        prop_writeTVar_IOSim
    , testProperty
        "prop_modifyTVar_IOSim"
        prop_modifyTVar_IOSim
    , testProperty
        "prop_stateTVar"
        prop_stateTVar_IOSim
    , testProperty
        "prop_swapTVar"
        prop_swapTVar_IOSim
    ]

{-------------------------------------------------------------------------------
  Utilities
-------------------------------------------------------------------------------}

isInWHNF :: (MonadSTM m, Typeable a) => StrictTVar m a -> m Property
isInWHNF v = do
  x <- readTVarIO v
  pure $ case unsafeNoThunks (OnlyCheckWhnf x) of
    Nothing -> property True
    Just tinfo ->
      counterexample ("Not in WHNF: " ++ show tinfo) $
        property False

-- | Wrapper around 'Checked.newTVar' and 'Checked.newTVarWithInvariant'.
newTVarWithInvariant :: MonadSTM m => Invariant a -> a -> STM m (StrictTVar m a)
newTVarWithInvariant = \case
  NoInvariant -> Checked.newTVar
  Invariant _ inv -> Checked.newTVarWithInvariant inv

-- | Wrapper around 'Checked.newTVarIO' and 'Checked.newTVarWithInvariantIO'.
newTVarWithInvariantIO :: MonadSTM m => Invariant a -> a -> m (StrictTVar m a)
newTVarWithInvariantIO = \case
  NoInvariant -> Checked.newTVarIO
  Invariant _ inv -> Checked.newTVarWithInvariantIO inv

-- | The 'isInWHNF' check fails when running tests in 'IOSim', since 'IOSim'
-- runs in the lazy 'ST' monad. 'withSanityCheckWhnf' can be used to perform the
-- test conditionally.
withSanityCheckWhnf ::
  (MonadSTM m, Typeable a) =>
  Bool ->
  StrictTVar m a ->
  m Property
withSanityCheckWhnf check v =
  if check
    then
      isInWHNF v
    else
      pure $ property True

{-------------------------------------------------------------------------------
  Properties
-------------------------------------------------------------------------------}

--
-- newTVarWithInvariant
--

-- | Test 'newTVarWithInvariant', not to be confused with
-- 'Checked.newTVarWithInvariant'.
prop_newTVarWithInvariant_M ::
  MonadSTM m =>
  Bool ->
  Invariant Int ->
  Int ->
  Fun Int Int ->
  m Property
prop_newTVarWithInvariant_M check inv x f = do
  v <- atomically $ newTVarWithInvariant inv (applyFun f x)
  withSanityCheckWhnf check v

prop_newTVarWithInvariant_IO ::
  Invariant Int ->
  Int ->
  Fun Int Int ->
  Property
prop_newTVarWithInvariant_IO =
  ioProperty
    ..: prop_newTVarWithInvariant_M True

prop_newTVarWithInvariant_IOSim ::
  Invariant Int ->
  Int ->
  Fun Int Int ->
  Property
prop_newTVarWithInvariant_IOSim inv x f =
  runSimOrThrow $
    prop_newTVarWithInvariant_M False inv x f

--
-- newTVarWithInvariantIO
--

-- | Test 'newTVarWithInvariantIO', not to be confused with
-- 'Checked.newTVarWithInvariantIO'.
prop_newTVarWithInvariantIO_M ::
  MonadSTM m =>
  Bool ->
  Invariant Int ->
  Int ->
  Fun Int Int ->
  m Property
prop_newTVarWithInvariantIO_M check inv x f = do
  v <- newTVarWithInvariantIO inv (applyFun f x)
  withSanityCheckWhnf check v

prop_newTVarWithInvariantIO_IO ::
  Invariant Int ->
  Int ->
  Fun Int Int ->
  Property
prop_newTVarWithInvariantIO_IO =
  ioProperty
    ..: prop_newTVarWithInvariantIO_M True

prop_newTVarWithInvariantIO_IOSim ::
  Invariant Int ->
  Int ->
  Fun Int Int ->
  Property
prop_newTVarWithInvariantIO_IOSim inv x f =
  runSimOrThrow $
    prop_newTVarWithInvariantIO_M False inv x f

--
-- writeTVar
--

prop_writeTVar_M ::
  MonadSTM m =>
  Bool ->
  Invariant Int ->
  Int ->
  Fun Int Int ->
  m Property
prop_writeTVar_M check inv x f = do
  v <- newTVarWithInvariantIO inv x
  atomically $ writeTVar v (applyFun f x)
  withSanityCheckWhnf check v

prop_writeTVar_IO ::
  Invariant Int ->
  Int ->
  Fun Int Int ->
  Property
prop_writeTVar_IO =
  ioProperty
    ..: prop_writeTVar_M True

prop_writeTVar_IOSim ::
  Invariant Int ->
  Int ->
  Fun Int Int ->
  Property
prop_writeTVar_IOSim inv x f =
  runSimOrThrow $
    prop_writeTVar_M False inv x f

--
-- modifyTVar
--

prop_modifyTVar_M ::
  MonadSTM m =>
  Bool ->
  Invariant Int ->
  Int ->
  Fun Int Int ->
  m Property
prop_modifyTVar_M check inv x f = do
  v <- newTVarWithInvariantIO inv x
  atomically $ modifyTVar v (applyFun f)
  withSanityCheckWhnf check v

prop_modifyTVar_IO ::
  Invariant Int ->
  Int ->
  Fun Int Int ->
  Property
prop_modifyTVar_IO =
  ioProperty
    ..: prop_modifyTVar_M True

prop_modifyTVar_IOSim ::
  Invariant Int ->
  Int ->
  Fun Int Int ->
  Property
prop_modifyTVar_IOSim inv x f =
  runSimOrThrow $
    prop_modifyTVar_M False inv x f

--
-- stateTVar
--

prop_stateTVar_M ::
  MonadSTM m =>
  Bool ->
  Invariant Int ->
  Int ->
  Fun Int Int ->
  m Property
prop_stateTVar_M check inv x f = do
  v <- newTVarWithInvariantIO inv x
  atomically $ stateTVar v (((),) . applyFun f)
  withSanityCheckWhnf check v

prop_stateTVar_IO ::
  Invariant Int ->
  Int ->
  Fun Int Int ->
  Property
prop_stateTVar_IO =
  ioProperty
    ..: prop_stateTVar_M True

prop_stateTVar_IOSim ::
  Invariant Int ->
  Int ->
  Fun Int Int ->
  Property
prop_stateTVar_IOSim inv x f =
  runSimOrThrow $
    prop_stateTVar_M False inv x f

--
-- swapTVar
--

prop_swapTVar_M ::
  MonadSTM m =>
  Bool ->
  Invariant Int ->
  Int ->
  Fun Int Int ->
  m Property
prop_swapTVar_M check inv x f = do
  v <- newTVarWithInvariantIO inv x
  void $ atomically $ swapTVar v (applyFun f x)
  withSanityCheckWhnf check v

prop_swapTVar_IO ::
  Invariant Int ->
  Int ->
  Fun Int Int ->
  Property
prop_swapTVar_IO =
  ioProperty
    ..: prop_swapTVar_M True

prop_swapTVar_IOSim ::
  Invariant Int ->
  Int ->
  Fun Int Int ->
  Property
prop_swapTVar_IOSim inv x f =
  runSimOrThrow $
    prop_swapTVar_M False inv x f