packages feed

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