periodic-0.1.0.0: test/Spec.hs
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE OverloadedStrings #-}
import Control.Concurrent (forkIO, killThread, threadDelay)
import Control.Concurrent.MVar (MVar, modifyMVarMasked_, newMVar,
readMVar, takeMVar)
import Control.Monad (replicateM)
import qualified Data.Text as T
import Data.Time.Calendar
import Data.Time.Clock
import qualified Database.Redis as R
import System.Periodic
import Test.Hspec
date :: Integer -> Int -> Int -> UTCTime
date y m d = UTCTime (fromGregorian y m d) 0
time :: UTCTime -> Int -> Int -> Int -> UTCTime
time t h m s = t { utctDayTime = fromIntegral $ h * 60 * 60 + m * 60 + s }
runNThreads :: Int -> Int -> Scheduler -> IO ()
runNThreads n delay sched = do threads <- replicateM n (forkIO $ run sched)
threadDelay delay
mapM_ killThread threads
createSch :: T.Text -> IO Scheduler
createSch n = createSch' n print
createSch' :: T.Text -> Logger -> IO Scheduler
createSch' n l = do rconn <- R.connect R.defaultConnectInfo
create (Name n) rconn (CheckInterval (Seconds 1)) (LockTimeout (Seconds 1000)) l
main :: IO ()
main = hspec $
do describe "shouldRun" $
do it "should run if it's never been run, not locked and time just passed" $
shouldRun (LockTimeout (Seconds 1000)) (Daily (Time 5)) Nothing Nothing (time (date 2016 5 5) 0 0 6)
it "shouldn't run if it was locked recently" $
not $ shouldRun (LockTimeout (Seconds 1000)) (Daily (Time 5)) (Just (time (date 2016 5 4) 23 45 0)) (Just (time (date 2016 5 4) 23 45 0)) (time (date 2016 5 5) 0 0 6)
it "should run if it was locked a long time ago" $
shouldRun (LockTimeout (Seconds 1000))
(Daily (Time 5))
(Just (date 2016 5 4))
(Just (date 2016 5 4))
(date 2016 5 5)
describe "simple job" $
do it "should run " $
do mvar <- newMVar 0
scheduler <- createSch "simple-1"
addTask scheduler "job" (Every (Seconds 100)) (modifyMVarMasked_ mvar (return . (+1)))
runNThreads 1 30000 scheduler
destroy scheduler
v <- takeMVar mvar
1 `shouldBe` v
it "should only run once per scheduled time" $
do mvar <- newMVar 0
scheduler <- createSch "simple-2"
addTask scheduler "job" (Every (Seconds 100)) (modifyMVarMasked_ mvar (return . (+1)))
runNThreads 3 100000 scheduler
destroy scheduler
v <- takeMVar mvar
v `shouldBe` 1
it "should run at each time point" $
do mvar <- newMVar 0
scheduler <- createSch "simple-3"
addTask scheduler "job" (Every (Seconds 2)) (modifyMVarMasked_ mvar (return . (+1)))
runNThreads 3 4000000 scheduler
destroy scheduler
v <- takeMVar mvar
-- NOTE(dbp 2016-05-26): Precise timing is hard
-- without making the tests super slow.
(v == 3 || v == 2) `shouldBe` True
it "should run at scheduled time" $
do mvar <- newMVar 0
scheduler <- createSch "simple-4"
seconds <- utctDayTime <$> getCurrentTime
addTask scheduler "job" (Daily (Time seconds)) (modifyMVarMasked_ mvar (return . (+1)))
runNThreads 1 4000000 scheduler
destroy scheduler
v <- takeMVar mvar
v `shouldBe` 1
it "should not run at an unscheduled time" $
do mvar <- newMVar 0
scheduler <- createSch "simple-4"
now <- getCurrentTime
let seconds = utctDayTime $ addUTCTime 3600 now
addTask scheduler "job" (Daily (Time seconds)) (modifyMVarMasked_ mvar (return . (+1)))
runNThreads 1 2000000 scheduler
destroy scheduler
v <- takeMVar mvar
v `shouldBe` 0
describe "error handling" $
do it "should keep running jobs if one throws an exception" $
do mvar <- newMVar 0
scheduler <- createSch' "error-1" (const $ return ())
addTask scheduler "job-error" (Every (Seconds 100)) (error "blowing up")
thread <- forkIO $ run scheduler
threadDelay 300000
addTask scheduler "job-success" (Every (Seconds 100)) (modifyMVarMasked_ mvar (return . (+1)))
threadDelay 2000000
killThread thread
destroy scheduler
v <- takeMVar mvar
1 `shouldBe` v
it "should send exception to logger" $
do mvar <- newMVar []
scheduler <- createSch' "error-1" (\t -> modifyMVarMasked_ mvar (return . (t:)))
addTask scheduler "job-error" (Every (Seconds 100)) (error "blowing up")
thread <- forkIO $ run scheduler
threadDelay 500000
killThread thread
destroy scheduler
v <- takeMVar mvar
length v `shouldBe` 1