packages feed

heftia-effects-0.6.0.0: test/Test/Concurrent.hs

{-# LANGUAGE ApplicativeDo #-}
{-# OPTIONS_GHC -fconstraint-solver-iterations=16 #-}

-- SPDX-License-Identifier: MPL-2.0

module Test.Concurrent where

import Control.Applicative ((<|>))
import Control.Monad.Hefty (Eff, Emb, liftIO, onlyFOEs, runEff, (:>))
import Control.Monad.Hefty.Concurrent.Parallel (
    Concurrently (Concurrently, runConcurrently),
    Parallel,
    cancels,
    input,
    polling,
    runConcurrentIO,
    runParallelAsSequential,
 )
import Control.Monad.Hefty.Concurrent.Timer (runTimerIO, sleep)
import Control.Monad.Hefty.State (get, modify, runStateIORef)
import Control.Monad.Hefty.Unlift (runUnliftIO)
import Data.Functor ((<&>))
import Test.Hspec (Spec, it, shouldBe)

spec_Concurrent :: Spec
spec_Concurrent = do
    let
        prog :: (Parallel :> es, Emb IO :> es) => Eff es (String, String)
        prog = runTimerIO . runStateIORef "" . runConcurrently $ do
            r <- Concurrently do
                sleep 0.001
                modify (<> "B")
                get @String <&> (<> "C")
            Concurrently do
                modify (<> "A")
            pure r

    it "Parallel" do
        (s, a) <- runUnliftIO . runConcurrentIO $ prog
        s `shouldBe` "AB"
        a `shouldBe` "ABC"

    it "Sequential" do
        (s, a) <- runEff . runParallelAsSequential $ prog
        s `shouldBe` "BA"
        a `shouldBe` "BC"

    it "Race" do
        (s, a) <-
            runUnliftIO . runTimerIO . runStateIORef "" . runConcurrentIO . runConcurrently $
                let a = Concurrently do
                        modify (<> "A")
                        sleep 0.002
                        pure '1'

                    b = Concurrently do
                        sleep 0.001
                        modify (<> "B")
                        sleep 0.002
                        modify (<> "C")
                        pure '2'
                 in a <|> b

        s `shouldBe` "AB"
        a `shouldBe` '1'

    it "Cancel" do
        (s, ((), b)) <-
            runUnliftIO . runTimerIO . runStateIORef "" . runConcurrentIO $
                sleep 0.001 `cancels` onlyFOEs do
                    modify (<> "A")
                    sleep 0.002
                    modify (<> "B")

        s `shouldBe` "A"
        b `shouldBe` Nothing

    it "Poll" do
        (s, ()) <- runUnliftIO . runTimerIO . runStateIORef "" . runConcurrentIO $ do
            polling
                do
                    sleep 0.001
                    modify (<> "B")
                    get @String <&> (<> "C")
                do
                    modify (<> "A")

                    x <- input
                    liftIO $ x `shouldBe` Nothing @String

                    sleep 0.002

                    x' <- input
                    liftIO $ x' `shouldBe` Just "ABC"

                    pure ()

        s `shouldBe` "AB"