packages feed

mealstrom-0.0.1.0: test/Timeout.hs

{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE TypeSynonymInstances #-}
{-# LANGUAGE MultiParamTypeClasses #-}

{-|
Module      : Timeout
Description : Make sure that recovery actually uses a timeout
Copyright   : (c) Max Amanshauser, 2016
License     : MIT
Maintainer  : max@lambdalifting.org
-}

module Timeout(runTimeoutTests) where

import Test.Tasty
import Test.Tasty.HUnit

import Control.Concurrent
import Data.Typeable
import Data.Aeson
import GHC.Generics
import Data.Text
import Data.IORef
import Data.UUID
import Data.UUID.V4

import Mealstrom
import Mealstrom.PostgresJSONStore as PGJSON
import Mealstrom.MemoryStore       as MemStore

type TimeoutKey    = UUID
data TimeoutState  = TimeoutState  deriving (Eq,Show,Generic,Typeable,ToJSON,FromJSON)
data TimeoutEvent  = TimeoutEvent  deriving (Eq,Show,Generic,Typeable,ToJSON,FromJSON)
data TimeoutAction = TimeoutAction deriving (Eq,Show,Generic,Typeable,ToJSON,FromJSON)

instance MealyInstance TimeoutKey TimeoutState TimeoutEvent TimeoutAction

timeoutTransition :: (TimeoutState,TimeoutEvent) -> (TimeoutState,[TimeoutAction])
timeoutTransition (TimeoutState,TimeoutEvent) = (TimeoutState,[TimeoutAction])

timeoutEffects :: IORef Bool -> MVar () -> Msg TimeoutAction -> IO Bool
timeoutEffects b sync _a = do
    bb <- readIORef b

    if   bb
    then putMVar sync ()               -- if this is the second run, we proceed normally
    else seq (sum [1..]) (return ())   -- else we timeout

    return True

runTimeoutTests :: String -> TestTree
runTimeoutTests c = testGroup "Timeout" [
    testCase "TimeoutPG" (runTest $ PGJSON.mkStore c),
    testCase "TimeoutMem" (runTest (MemStore.mkStore :: Text -> IO (MemoryStore TimeoutKey TimeoutState TimeoutEvent TimeoutAction)))
    ]

runTest c = do
    st     <- c "TimeoutTest"

    b      <- newIORef False
    sync   <- newEmptyMVar

    let t   = FSMTable timeoutTransition (timeoutEffects b sync)
    let fsm = FSMHandle st st t 1 2    -- timeout of 1 second and we only try once

    i      <- nextRandom

    post fsm i TimeoutState
    mkMsgs [TimeoutEvent] >>= patch fsm i

    -- action is run for the first time and should timeout
    threadDelay (1*10^6)
    writeIORef b True

    -- action is run again
    takeMVar sync

    assertBool "Recovery after timeout successful" True