packages feed

mealstrom-0.0.1.0: test/Recovery.hs

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

{-|
Module      : Recovery
Description : Test that recovery works
Copyright   : (c) Max Amanshauser, 2016
License     : MIT
Maintainer  : max@lambdalifting.org
-}
module Recovery(runRecoveryTests) 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 RecoveryKey    = UUID
data RecoveryState  = RecoveryState1  | RecoveryState2  deriving (Eq,Show,Generic,Typeable,ToJSON,FromJSON)
data RecoveryEvent  = RecoveryEvent1  | RecoveryEvent2  deriving (Eq,Show,Generic,Typeable,ToJSON,FromJSON)
data RecoveryAction = RecoveryAction1 | RecoveryAction2 deriving (Eq,Show,Generic,Typeable,ToJSON,FromJSON)

instance MealyInstance RecoveryKey RecoveryState RecoveryEvent RecoveryAction

recoveryTransition :: (RecoveryState,RecoveryEvent) -> (RecoveryState,[RecoveryAction])
recoveryTransition (RecoveryState1,RecoveryEvent1) =
    (RecoveryState2,[RecoveryAction1])

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

    -- indicate that we read the IORef and are running the action now
    putMVar sync ()

    return bb

runRecoveryTests :: String -> TestTree
runRecoveryTests c = testGroup "Recovery" [
    testCase "RecoveryPG" (runTest $ PGJSON.mkStore c),
    testCase "RecoveryMem" (runTest (MemStore.mkStore :: Text -> IO (MemoryStore RecoveryKey RecoveryState RecoveryEvent RecoveryAction)))
    ]

runTest c = do
    st     <- c "RecoveryTest"

    b      <- newIORef False
    sync   <- newEmptyMVar

    let t   = FSMTable recoveryTransition (recoveryEffects b sync)
    let fsm = FSMHandle st st t 1 3    -- we have a timeout of 1 second for actions

    i <- nextRandom

    post fsm i RecoveryState1
    mkMsgs [RecoveryEvent1] >>= patch fsm i

    -- action is run for the first time
    takeMVar sync

    -- wait two seconds, so that the action is definitely recoverable
    threadDelay (2 * 10^6)
    writeIORef b True
    recoverAll fsm

    -- action is run again
    takeMVar sync

    -- If we reach this, then the recovery definitely ran, yet the entry in the DB
    -- might still be wrong. That's ok.
    assertBool "Failed action recovers successfully" True