packages feed

mealstrom-0.0.1.0: test/BasicFSM.hs

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

{-|
Module      : BasicFSM
Description : A simple example.
Copyright   : (c) Max Amanshauser, 2016
License     : MIT
Maintainer  : max@lambdalifting.org
-}

module BasicFSM (runBasicTests) where

import Test.Tasty
import Test.Tasty.HUnit

import Control.Concurrent
import Data.Aeson
import Data.Hashable
import Data.Text                   as Text
import Data.Typeable
import GHC.Generics

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


-- ####################
-- # Connection Example
-- ####################

-- This is a contrived example of how to use a custom Key type, instead of the recommended Text and UUID.
newtype ConnectionKey = ConnectionKey (Int,Int) deriving (Show,Eq,Generic,Hashable)

instance FSMKey ConnectionKey where
    toText (ConnectionKey (a,b)) = Text.pack $ "(" ++ show a ++ "," ++ show b ++ ")"
    fromText t = case fmap (\s -> read (unpack s) :: Int) (splitOn "," $ Text.dropEnd 1 (Text.drop 1 t)) of
        a:[b] -> ConnectionKey (a,b)
        _     -> error ""

data ConnectionState  = New | Open | Closed
    deriving (Eq,Show,Typeable,Generic,ToJSON,FromJSON)

data ConnectionEvent  = Create | Close | Reset
    deriving (Eq,Show,Typeable,Generic,ToJSON,FromJSON)

data ConnectionAction = PrintStatusOpened | PrintStatusClosed
    deriving (Eq,Typeable,Generic,ToJSON,FromJSON)

instance MealyInstance ConnectionKey ConnectionState ConnectionEvent ConnectionAction

connEffects :: MVar () -> Msg ConnectionAction -> IO Bool
connEffects mvar (Msg _i c)
    | c == PrintStatusOpened = putStrLn "OUTPUT: Connection opened" >> putMVar mvar () >> return True
    | c == PrintStatusClosed = putStrLn "OUTPUT: Connection closed" >> putMVar mvar () >> return True

connTransition :: (ConnectionState, ConnectionEvent) -> (ConnectionState, [ConnectionAction])
connTransition (s,e) =
    case (s,e) of
        (New, Create) -> (Open,  [PrintStatusOpened])
        (Open, Close) -> (Closed,[PrintStatusClosed])
        (Open, Reset) -> (Open,  [PrintStatusClosed, PrintStatusOpened])

runBasicTests :: String -> TestTree
runBasicTests c = testGroup "BasicFSM" [
    testCase "BasicPG" (runTest (PGJSON.mkStore c)),
    testCase "BasicMem0" (runTest (MemStore.mkStore :: Text -> IO (MemoryStore ConnectionKey ConnectionState ConnectionEvent ConnectionAction)))
    ]

runTest :: (FSMStore st ConnectionKey ConnectionState ConnectionEvent ConnectionAction,
            WALStore st ConnectionKey) => (Text -> IO st) -> IO ()
runTest c = do
    st       <- c "BasicFSMTest"
    sync     <- newEmptyMVar
    let t     = FSMTable connTransition (connEffects sync)
    let myFSM = FSMHandle st st t 90 3
    let firstId = ConnectionKey (1231231,21)   -- This represents a socket or something

    post myFSM firstId New
    Just fsmState1 <- get myFSM firstId
    fsmState1 @?= New

    msg1 <- mkMsgs [Create]
    _    <- patch myFSM firstId msg1

    takeMVar sync
    Just fsmState2 <- get myFSM firstId
    fsmState2 @?= Open

    msg2 <- mkMsgs [Close]
    _    <- patch myFSM firstId msg2

    takeMVar sync
    Just fsmState3 <- get myFSM firstId
    fsmState3 @?= Closed