context-0.2.0.1: test-suite/Test/Context/ImplicitSpec.hs
{-# OPTIONS_GHC -fno-warn-orphans #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE DerivingStrategies #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE NoImplicitPrelude #-}
{-# LANGUAGE TypeApplications #-}
module Test.Context.ImplicitSpec
( spec
) where
import Context.Implicit (Store)
import Control.Concurrent (ThreadId)
import GHC.Classes (IP(ip))
import Prelude
import System.IO.Unsafe (unsafePerformIO)
import Test.Hspec
import qualified Context.Implicit as Context
import qualified Control.Concurrent as Concurrent
import qualified Control.Concurrent.Async as Async
data Thing = Thing
{ stuff :: Int
} deriving stock (Eq, Show)
store :: Store Thing
store = unsafePerformIO $ Context.newStore Context.defaultPropagation Nothing
{-# NOINLINE store #-}
instance IP "contextStore" (Store Thing) where
ip = store
spec :: Spec
spec = do
describe "emptyStore" $ do
describe "mineMay" $ do
it "empty" $ do
Context.mineMay `shouldReturn` Nothing
it "single context" $ do
Context.use Thing { stuff = 1 } $ do
Context.mineMay `shouldReturn` Just Thing { stuff = 1 }
Context.mineMay `shouldReturn` Nothing
it "nested contexts" $ do
Context.use Thing { stuff = 1 } $ do
Context.mineMay `shouldReturn` Just Thing { stuff = 1 }
Context.use Thing { stuff = 2 } $ do
Context.mineMay `shouldReturn` Just Thing { stuff = 2 }
Context.use Thing { stuff = 3 } $ do
Context.mineMay `shouldReturn` Just Thing { stuff = 3 }
Context.mineMay `shouldReturn` Just Thing { stuff = 2 }
Context.use Thing { stuff = 4 } $ do
Context.mineMay `shouldReturn` Just Thing { stuff = 4 }
Context.mineMay `shouldReturn` Just Thing { stuff = 1 }
Context.mineMay `shouldReturn` Nothing
it "concurrent nested contexts" $ do
let mkContext i = Thing { stuff = i }
Async.forConcurrently_ [1 :: Int ..10] $ const $ do
Context.mineMay `shouldReturn` Nothing
Context.use (mkContext 1) $ do
Context.mineMay `shouldReturn` Just (mkContext 1)
Context.use (mkContext 2) $ do
Context.mineMay `shouldReturn` Just (mkContext 2)
Context.use (mkContext 3) $ do
Context.mineMay `shouldReturn` Just (mkContext 3)
Context.mineMay `shouldReturn` Just (mkContext 2)
Context.use (mkContext 4) $ do
Context.mineMay `shouldReturn` Just (mkContext 4)
Context.mineMay `shouldReturn` Just (mkContext 1)
Context.mineMay `shouldReturn` Nothing
Context.mineMay `shouldReturn` Nothing
describe "minesMay" $ do
it "empty" $ do
Context.minesMay stuff `shouldReturn` Nothing
it "single context" $ do
Context.use Thing { stuff = 1 } $ do
Context.minesMay stuff `shouldReturn` Just 1
Context.minesMay stuff `shouldReturn` Nothing
it "nested contexts" $ do
Context.use Thing { stuff = 1 } $ do
Context.minesMay stuff `shouldReturn` Just 1
Context.use Thing { stuff = 2 } $ do
Context.minesMay stuff `shouldReturn` Just 2
Context.use Thing { stuff = 3 } $ do
Context.minesMay stuff `shouldReturn` Just 3
Context.minesMay stuff `shouldReturn` Just 2
Context.use Thing { stuff = 4 } $ do
Context.minesMay stuff `shouldReturn` Just 4
Context.minesMay stuff `shouldReturn` Just 1
Context.minesMay stuff `shouldReturn` Nothing
it "concurrent nested contexts" $ do
let mkContext i = Thing { stuff = i }
Async.forConcurrently_ [1 :: Int ..10] $ const $ do
Context.minesMay stuff `shouldReturn` Nothing
Context.use (mkContext 1) $ do
Context.minesMay stuff `shouldReturn` Just 1
Context.use (mkContext 2) $ do
Context.minesMay stuff `shouldReturn` Just 2
Context.use (mkContext 3) $ do
Context.minesMay stuff `shouldReturn` Just 3
Context.minesMay stuff `shouldReturn` Just 2
Context.use (mkContext 4) $ do
Context.minesMay stuff `shouldReturn` Just 4
Context.minesMay stuff `shouldReturn` Just 1
Context.minesMay stuff `shouldReturn` Nothing
Context.minesMay stuff `shouldReturn` Nothing
describe "mine" $ do
it "empty" $ do
threadId <- Concurrent.myThreadId
Context.mine `shouldThrow` notFound threadId
it "single context" $ do
threadId <- Concurrent.myThreadId
Context.use Thing { stuff = 1 } $ do
Context.mine `shouldReturn` Thing { stuff = 1 }
Context.mine `shouldThrow` notFound threadId
it "nested contexts" $ do
threadId <- Concurrent.myThreadId
Context.use Thing { stuff = 1 } $ do
Context.mine `shouldReturn` Thing { stuff = 1 }
Context.use Thing { stuff = 2 } $ do
Context.mine `shouldReturn` Thing { stuff = 2 }
Context.use Thing { stuff = 3 } $ do
Context.mine `shouldReturn` Thing { stuff = 3 }
Context.mine `shouldReturn` Thing { stuff = 2 }
Context.use Thing { stuff = 4 } $ do
Context.mine `shouldReturn` Thing { stuff = 4 }
Context.mine `shouldReturn` Thing { stuff = 1 }
Context.mine `shouldThrow` notFound threadId
it "concurrent nested contexts" $ do
let mkContext i = Thing { stuff = i }
initThreadId <- Concurrent.myThreadId
Async.forConcurrently_ [1 :: Int ..10] $ const $ do
threadId <- Concurrent.myThreadId
Context.mine `shouldThrow` notFound threadId
Context.use (mkContext 1) $ do
Context.mine `shouldReturn` mkContext 1
Context.use (mkContext 2) $ do
Context.mine `shouldReturn` mkContext 2
Context.use (mkContext 3) $ do
Context.mine `shouldReturn` mkContext 3
Context.mine `shouldReturn` mkContext 2
Context.use (mkContext 4) $ do
Context.mine `shouldReturn` mkContext 4
Context.mine `shouldReturn` mkContext 1
Context.mine `shouldThrow` notFound threadId
Context.mine `shouldThrow` notFound initThreadId
describe "mines" $ do
it "empty" $ do
threadId <- Concurrent.myThreadId
Context.mines stuff `shouldThrow` notFound threadId
it "single context" $ do
threadId <- Concurrent.myThreadId
Context.use Thing { stuff = 1 } $ do
Context.mines stuff `shouldReturn` 1
Context.mines stuff `shouldThrow` notFound threadId
it "nested contexts" $ do
threadId <- Concurrent.myThreadId
Context.use Thing { stuff = 1 } $ do
Context.mines stuff `shouldReturn` 1
Context.use Thing { stuff = 2 } $ do
Context.mines stuff `shouldReturn` 2
Context.use Thing { stuff = 3 } $ do
Context.mines stuff `shouldReturn` 3
Context.mines stuff `shouldReturn` 2
Context.use Thing { stuff = 4 } $ do
Context.mines stuff `shouldReturn` 4
Context.mines stuff `shouldReturn` 1
Context.mines stuff `shouldThrow` notFound threadId
it "concurrent nested contexts" $ do
let mkContext i = Thing { stuff = i }
initThreadId <- Concurrent.myThreadId
Async.forConcurrently_ [1 :: Int ..10] $ const $ do
threadId <- Concurrent.myThreadId
Context.mine `shouldThrow` notFound threadId
Context.use (mkContext 1) $ do
Context.mines stuff `shouldReturn` 1
Context.use (mkContext 2) $ do
Context.mines stuff `shouldReturn` 2
Context.use (mkContext 3) $ do
Context.mines stuff `shouldReturn` 3
Context.mines stuff `shouldReturn` 2
Context.use (mkContext 4) $ do
Context.mines stuff `shouldReturn` 4
Context.mines stuff `shouldReturn` 1
Context.mines stuff `shouldThrow` notFound threadId
Context.mines stuff `shouldThrow` notFound initThreadId
describe "adjust" $ do
it "empty" $ do
threadId <- Concurrent.myThreadId
Context.adjust modifier (error "does not get here")
`shouldThrow` notFound threadId
it "single context" $ do
threadId <- Concurrent.myThreadId
Context.use Thing { stuff = 1 } $ do
Context.mine `shouldReturn` Thing { stuff = 1 }
Context.adjust modifier $ do
Context.mine `shouldReturn` Thing { stuff = 2 }
Context.mine `shouldReturn` Thing { stuff = 1 }
Context.mine `shouldThrow` notFound threadId
it "nested contexts" $ do
threadId <- Concurrent.myThreadId
Context.use Thing { stuff = 1 } $ do
Context.adjust modifier $ do
Context.mine `shouldReturn` Thing { stuff = 2 }
Context.use Thing { stuff = 3 } $ do
Context.mine `shouldReturn` Thing { stuff = 3 }
Context.use Thing { stuff = 4 } $ do
Context.mine `shouldReturn` Thing { stuff = 4 }
Context.mine `shouldReturn` Thing { stuff = 3 }
Context.use Thing { stuff = 4 } $ do
Context.mine `shouldReturn` Thing { stuff = 4 }
Context.mine `shouldReturn` Thing { stuff = 2 }
Context.mine `shouldReturn` Thing { stuff = 1 }
Context.mine `shouldThrow` notFound threadId
it "concurrent nested contexts" $ do
let mkContext i = Thing { stuff = i }
initThreadId <- Concurrent.myThreadId
Async.forConcurrently_ [1 :: Int ..10] $ const $ do
threadId <- Concurrent.myThreadId
Context.mine `shouldThrow` notFound threadId
Context.use (mkContext 1) $ do
Context.adjust modifier $ do
Context.mine `shouldReturn` mkContext 2
Context.use (mkContext 3) $ do
Context.mine `shouldReturn` mkContext 3
Context.use (mkContext 4) $ do
Context.mine `shouldReturn` mkContext 4
Context.mine `shouldReturn` mkContext 3
Context.use (mkContext 5) $ do
Context.mine `shouldReturn` mkContext 5
Context.mine `shouldReturn` mkContext 2
Context.mine `shouldReturn` mkContext 1
Context.mine `shouldThrow` notFound threadId
Context.mine `shouldThrow` notFound initThreadId
notFound :: ThreadId -> Context.NotFoundException -> Bool
notFound threadId notFoundEx =
Context.NotFoundException { Context.threadId } == notFoundEx
modifier :: Thing -> Thing
modifier thing = thing { stuff = 2 * stuff thing }