flush-queue-1.0.0: test/Control/Concurrent/STM/TBFQueueSpec.hs
module Control.Concurrent.STM.TBFQueueSpec (spec) where
import Control.Concurrent.Async
import Control.Concurrent.STM
import Control.Concurrent.STM.TBFQueue
import Data.List
import Test.Hspec
import Test.QuickCheck
import Test.QuickCheck.Monadic
prop_FillFlushNonBlocking :: [[Int]] -> Property
prop_FillFlushNonBlocking lss =
monadicIO $ do
ls <-
run $ do
q <- newTBFQueueIO (fromIntegral (sum (map length lss)))
mapConcurrently_ (foldMap (atomically . writeTBFQueue q)) lss
atomically $ flushTBFQueue q
return (sort ls === sort (concat lss))
prop_FillAndBlockFlush :: Positive Int -> [Int] -> Int -> Property
prop_FillAndBlockFlush (Positive bound) ls oneExtra =
bound < length ls ==> monadicIO $ do
let (fillWith, leftOver) = splitAt bound ls
run $ do
q <- atomically $ newTBFQueue $ fromIntegral bound
isSuccess' <- and <$> mapConcurrently (atomically . tryWriteTBFQueue q) fillWith
hasSpace <- or <$> mapConcurrently (atomically . tryWriteTBFQueue q) leftOver
len <- atomically $ lengthTBFQueue q
eLs <- atomically $ orElse (Left <$> writeTBFQueue q oneExtra) (Right <$> flushTBFQueue q)
return $
conjoin
[ counterexample "Queue wasn't fully filled up" isSuccess'
, counterexample "Left over was placed on the queue" (not hasSpace)
, fromIntegral len === length fillWith
, either
(\_ -> counterexample "Placed an element on the full queue" False)
(\ls' -> sort ls' === sort fillWith)
eLs
]
newFullQueueFromList :: Foldable t => t a -> IO (TBFQueue a)
newFullQueueFromList xs = do
q <- newTBFQueueIO $ fromIntegral $ length xs
mapM_ (atomically . writeTBFQueue q) xs
pure q
prop_FillReadTakeNonBlocking :: NonEmptyList Int -> Property
prop_FillReadTakeNonBlocking (NonEmpty xs) =
monadicIO $
run $ do
let i = fromIntegral (length xs - 1)
q <- newFullQueueFromList xs
x' <- atomically $ readTBFQueue q
xs' <- atomically $ takeTBFQueue i q
isEmpty <- atomically $ isEmptyTBFQueue q
return (head xs === x' .&&. tail xs === xs' .&&. counterexample "Queue is non-empty" isEmpty)
prop_FillReadTakeBlocking :: NonEmptyList Int -> Int -> Property
prop_FillReadTakeBlocking (NonEmpty xs) y =
monadicIO $
run $ do
let i = fromIntegral (length xs - 1)
q <- newFullQueueFromList xs
((), x') <- concurrently (atomically $ writeTBFQueue q y) (atomically $ readTBFQueue q)
xs' <- atomically $ takeTBFQueue i q
y' <- atomically $ readTBFQueue q
return (head xs === x' .&&. tail xs === xs' .&&. y === y')
prop_FillTakeNonBlocking :: [Int] -> NonNegative Int -> Property
prop_FillTakeNonBlocking xs (NonNegative i) =
monadicIO $
run $ do
let n = length xs
q <- newTBFQueueIO $ fromIntegral n
mapM_ (atomically . writeTBFQueue q) xs
xs1 <- atomically $ takeTBFQueue (fromIntegral i) q
xs2 <- atomically $ takeTBFQueue (fromIntegral (max 0 (n - i))) q
return (xs === xs1 ++ xs2)
prop_PushPopConcurrently1 :: Int -> Positive Int -> Property
prop_PushPopConcurrently1 x (Positive bound) =
monadicIO $
run $ do
q <- newTBFQueueIO $ fromIntegral bound
(x', ()) <- concurrently (atomically $ readTBFQueue q) (atomically $ writeTBFQueue q x)
return (x === x')
prop_PushPopConcurrentlyMany :: [Int] -> Positive Int -> Property
prop_PushPopConcurrentlyMany xs (Positive bound) =
monadicIO $
run $ do
q <- newTBFQueueIO $ fromIntegral bound
(xs', ()) <-
concurrently
(mapM (const (atomically (readTBFQueue q))) xs)
(mapM_ (atomically . writeTBFQueue q) xs)
return (sort xs === sort xs')
spec :: Spec
spec =
describe "Fill+Flush" $ do
it "FillFlushNonBlocking" $ property prop_FillFlushNonBlocking
it "FillAndBlockFlush" $ property prop_FillAndBlockFlush
it "FillReadTakeNonBlocking" $ property prop_FillReadTakeNonBlocking
it "FillReadTakeBlocking" $ property prop_FillReadTakeBlocking
it "FillTakeNonBlocking" $ property prop_FillTakeNonBlocking
it "PushPopConcurrently1" $ property prop_PushPopConcurrently1
it "PushPopConcurrentlyMany" $ property prop_PushPopConcurrentlyMany