packages feed

atelier-core-0.7.3.0: test/Unit/Atelier/Effects/ChanSpec.hs

module Unit.Atelier.Effects.ChanSpec (test_Chan) where

import Effectful (IOE, runEff)
import Effectful.Timeout (Timeout, runTimeout)
import Hedgehog (forAll, property, (===))
import Test.Tasty (TestTree, testGroup)
import Test.Tasty.HUnit (testCase, (@?=))
import Test.Tasty.Hedgehog (testProperty)

import Hedgehog.Gen qualified as Gen
import Hedgehog.Range qualified as Range

import Atelier.Effects.Chan (Chan, dupChan, newChan, readChan, readChanBatched, runChan, writeChan)
import Atelier.Time (Millisecond, Second)


test_Chan :: TestTree
test_Chan =
    testGroup
        "Chan"
        [ testGroup
            "Basic Operations"
            [ testCase "writeChan then readChan roundtrips a value" do
                result <- runChanTest $ do
                    (inChan, outChan) <- newChan
                    writeChan inChan (42 :: Int)
                    readChan outChan
                result @?= 42
            , testProperty "preserves FIFO order" $ property do
                xs <- forAll $ Gen.list (Range.linear 0 50) (Gen.int Range.linearBounded)
                result <- liftIO $ runChanTest $ do
                    (inChan, outChan) <- newChan
                    traverse_ (writeChan inChan) xs
                    replicateM (length xs) (readChan outChan)
                result === xs
            , testCase "dupChan creates an independent reader that receives the same messages" do
                result <- runChanTest $ do
                    (inChan, outChan1) <- newChan
                    outChan2 <- dupChan inChan
                    writeChan inChan (42 :: Int)
                    v1 <- readChan outChan1
                    v2 <- readChan outChan2
                    pure (v1, v2)
                result @?= (42, 42)
            ]
        , testGroup
            "readChanBatched"
            [ testGroup
                "when items fill the batch before timeout"
                [ testCase "returns a full batch" do
                    result <- runChanTest $ do
                        (inChan, outChan) <- newChan
                        traverse_ (writeChan inChan) [1, 2, 3 :: Int]
                        readChanBatched (1 :: Second) 3 outChan
                    result @?= (1 :| [2, 3])
                , testProperty "caps at batchSize even when more items are available" $ property do
                    batchSize <- forAll $ Gen.int (Range.linear 1 20)
                    extra <- forAll $ Gen.int (Range.linear 1 10)
                    let n = batchSize + extra
                    result <- liftIO $ runChanTest $ do
                        (inChan, outChan) <- newChan
                        traverse_ (writeChan inChan) [1 .. n]
                        readChanBatched (1 :: Second) batchSize outChan
                    length result === batchSize
                ]
            , testGroup
                "when timeout fires before batch is full"
                [ testCase "returns a singleton when only one item is in the channel" do
                    result <- runChanTest $ do
                        (inChan, outChan) <- newChan
                        writeChan inChan (1 :: Int)
                        readChanBatched (1 :: Millisecond) 5 outChan
                    result @?= (1 :| [])
                , testCase "returns a partial batch" do
                    result <- runChanTest $ do
                        (inChan, outChan) <- newChan
                        writeChan inChan (1 :: Int)
                        writeChan inChan 2
                        readChanBatched (1 :: Millisecond) 5 outChan
                    result @?= (1 :| [2])
                ]
            ]
        ]


--------------------------------------------------------------------------------
-- Test Helpers
--------------------------------------------------------------------------------

runChanTest :: Eff '[Chan, Timeout, IOE] a -> IO a
runChanTest = runEff . runTimeout . runChan