{-# LANGUAGE OverloadedStrings #-}

module Snap.Internal.Http.Server.TimeoutManager.Tests
  ( tests ) where

------------------------------------------------------------------------------
import           Control.Concurrent                       (newEmptyMVar, putMVar, takeMVar)
import           Control.Concurrent.Thread                (forkIO, result)
import qualified Control.Exception                        as E
import           Control.Monad                            (replicateM)
import           Data.IORef                               (newIORef, readIORef, writeIORef)
import           Data.Maybe                               (isJust)
------------------------------------------------------------------------------
import qualified Snap.Internal.Http.Server.Clock          as Clock
import qualified Snap.Internal.Http.Server.TimeoutManager as TM
import           System.Timeout                           (timeout)
import           Test.Framework                           (Test)
import           Test.Framework.Providers.HUnit           (testCase)
import           Test.HUnit                               (assertBool, assertEqual)

------------------------------------------------------------------------------
tests :: [Test]
tests = [ testOneTimeout
        , testSlowToDie
        , testOneTimeoutAfterInactivity
        , testCancel
        , testTickle ]


------------------------------------------------------------------------------
register :: IO () -> TM.TimeoutManager -> IO TM.TimeoutThread
register m t = TM.register t "test" $
               \restore -> restore (Clock.sleepSecs 9000)
                           `E.finally` m


------------------------------------------------------------------------------
testOneTimeout :: Test
testOneTimeout = testCase "timeout/oneTimeout" $ repeatedly $ do
    mgr <- TM.initialize 1 0.1 Clock.getClockTime
    oneTimeout mgr

------------------------------------------------------------------------------
testSlowToDie :: Test
testSlowToDie = testCase "timeout/slowToDie" $ repeatedly $ do
    mgr <- TM.initialize 1 0.1 Clock.getClockTime
    r   <- newIORef False
    s   <- newIORef False
    _   <- register (writeIORef r True >> Clock.sleepSecs 3 >> writeIORef s True) mgr
    Clock.sleepSecs 1.5
    readIORef r >>= assertEqual "started to die" True
    readIORef s >>= assertEqual "not dead yet" False
    Clock.sleepSecs 3
    readIORef s >>= assertEqual "dead" True


------------------------------------------------------------------------------
testOneTimeoutAfterInactivity :: Test
testOneTimeoutAfterInactivity =
    testCase "timeout/oneTimeoutAfterInactivity" $ repeatedly $ do
        mgr <- TM.initialize 1 0.1 Clock.getClockTime
        Clock.sleepSecs 3
        oneTimeout mgr

------------------------------------------------------------------------------
repeatedly :: IO () -> IO ()
repeatedly m = dieIfTimeout $ do
    results <- replicateM 40 (forkIO m) >>= sequence . map snd
    mapM_ result results


------------------------------------------------------------------------------
oneTimeout :: TM.TimeoutManager -> IO ()
oneTimeout mgr = do
    mv  <- newEmptyMVar
    _   <- register (putMVar mv ()) mgr
    m   <- timeout (3*seconds) $ takeMVar mv
    assertBool "timeout fired" $ isJust m
    Clock.sleepSecs 2
    TM.stop mgr


------------------------------------------------------------------------------
testTickle :: Test
testTickle = testCase "timeout/tickle" $ repeatedly $ do
    mgr <- TM.initialize 5 0.1 Clock.getClockTime
    ref <- newIORef (0 :: Int)
    h <- register (writeIORef ref 1) mgr
    E.evaluate (length $ show h)
    Clock.sleepSecs 1
    b0 <- readIORef ref
    assertEqual "b0" 0 b0
    TM.tickle h 3
    Clock.sleepSecs 1
    b1 <- readIORef ref
    assertEqual "b1" 0 b1
    Clock.sleepSecs 5
    b2 <- readIORef ref
    assertEqual "b2" 1 b2
    TM.stop mgr


------------------------------------------------------------------------------
testCancel :: Test
testCancel = testCase "timeout/cancel" $ repeatedly $ do
    mgr <- TM.initialize 3 0.1 Clock.getClockTime
    ref <- newIORef (0 :: Int)
    h <- register (writeIORef ref 1) mgr
    Clock.sleepSecs 1
    readIORef ref >>= assertEqual "b0" 0
    TM.cancel h
    TM.tickle h 10              -- make sure tickle ignores cancelled times
    Clock.sleepSecs 2
    readIORef ref >>= assertEqual "b1" 1
    Clock.sleepSecs 2
    h' <- register (writeIORef ref 2) mgr
    _ <- register (return ()) mgr
    TM.set h' 1
    Clock.sleepSecs 2
    readIORef ref >>= assertEqual "b2" 2
    _   <- register (writeIORef ref 3) mgr
    hs <- replicateM 1000 $! register (return ()) mgr
    mapM TM.cancel hs
    TM.stop mgr
    Clock.sleepSecs 1
    readIORef ref >>= assertEqual "b3" 3


------------------------------------------------------------------------------
seconds :: Int
seconds = (10::Int) ^ (6::Int)


------------------------------------------------------------------------------
dieIfTimeout :: IO a -> IO a
dieIfTimeout m = timeout (30 * seconds) m >>= maybe (error "timeout") return
