module Main (main) where import Control.Concurrent (forkIO, threadDelay) import Control.Concurrent.CachedIO (Cached (..), cachedIO) import Data.Functor (void) import Data.IORef (IORef, atomicModifyIORef', newIORef, readIORef) import Test.Tasty (TestTree, defaultMain, testGroup) import Test.Tasty.HUnit (testCase, (@?=)) tests :: TestTree tests = testGroup "Control.Concurrent.CachedIO" [ testCase "Action is not called until first use" $ do ref <- newIORef 0 Cached {} <- cachedIO (5 * 60) $ increment ref count <- readIORef ref count @?= 0, testCase "Action is not called if value is fresh" $ do ref <- newIORef 0 Cached action <- cachedIO (5 * 60) $ increment ref void action void action count <- readIORef ref count @?= 1, testCase "Action is not called if cache is initializing" $ do ref <- newIORef 0 Cached action <- cachedIO (5 * 60) $ incrementSlow ref void . forkIO $ void action void action count <- readIORef ref count @?= 1 ] increment :: IORef Int -> IO Int increment ref = atomicModifyIORef' ref (\i -> (succ i, i)) incrementSlow :: IORef Int -> IO Int incrementSlow ref = do res <- atomicModifyIORef' ref (\i -> (succ i, i)) -- waiting AFTER the increase will show that Initialized is necessary -- because forking a thread takes enough time that the "action >> readIORef" -- are done before the forked thread has the chance to increment the counter. threadDelay $ 100 * 1000 -- 100 ms pure res main :: IO () main = defaultMain tests