nomyx-core-1.0.0: src/Nomyx/Core/Engine/Test.hs
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE GADTs #-}
module Nomyx.Core.Engine.Test where
import Nomyx.Language.Types
import Nomyx.Language.Variables
import Nomyx.Language.Rules
import Nomyx.Language.Events
import Nomyx.Language.Outputs
import Nomyx.Language.Messages
import Nomyx.Core.Engine.Evaluation
import Nomyx.Core.Engine.Types
import Nomyx.Library.Examples
import Nomyx.Library.Vote
import Nomyx.Library.Victory
import Control.Monad.State
import Data.Typeable
import Data.Function hiding ((.))
import Data.Maybe
import Control.Lens
import System.Random
import Imprevu.Test.TestMgt
import Imprevu.Evaluation hiding (events)
import Debug.NoTrace -- .Helpers (traceM)
testGame :: Game
testGame = Game { _gameName = "test",
_gameDesc = GameDesc "test" "test",
_rules = [],
_players = [PlayerInfo 1 "coco" Nothing],
_variables = [],
_events = [],
_outputs = [],
_victory = Nothing,
_logs = [],
_currentTime = date1,
_randomGen = mkStdGen 0}
testRule :: RuleInfo
testRule = RuleInfo { _rNumber = 0,
_rProposedBy = 0,
_rRule = return (),
_rStatus = Pending,
_rAssessedBy = Nothing,
_rModules = [],
_rRuleTemplate = RuleTemplate {_rName = "test",
_rDescription = "test",
_rRuleCode = "",
_rAuthor = "",
_rPicture = Nothing,
_rCategory = [],
_rDeclarations = []}}
execRuleGame :: Nomex a -> Game -> Game
execRuleGame r g = execState (runSystemEval' $ void $ evalNomex r) g
execRuleEventGame :: (Show e, Typeable e, Eq e, Eq s, Show s, Typeable s) => Nomex a -> Signal s e -> e -> Game -> Game
execRuleEventGame r f d g = execState (runSystemEval' $ evalNomex r >> (triggerEvent f d)) g
execRule :: Nomex a -> Game
execRule r = execRuleGame r testGame
addActivateRule :: Rule -> RuleNumber -> Evaluate ()
addActivateRule rf rn = do
let rule = testRule & (rRuleTemplate . rName) .~ "testRule"
& rRule .~ rf
& rNumber .~ rn
& rStatus .~ Pending
evAddRule rule
evActivateRule (_rNumber rule)
return ()
--Test variable creation
testVar1 :: Rule
testVar1 = do
NewVar "toto" (1::Integer)
return ()
testVarEx1 :: Bool
testVarEx1 = True --(variables ^$ execRuleFunc testVar1) == [(Var 0 "toto" (1::Integer))]
--Test variable deleting
testVar2 :: Rule
testVar2 = do
var <- newVar_ "toto" (1::Int)
delVar var
return ()
testVarEx2 :: Bool
testVarEx2 = True --_variables (execRuleFunc testVar2) == []
--Test variable reading
testVar3 :: Rule
testVar3 = do
var <- newVar_ "toto" (1::Int)
a <- readVar var
case a of
Just (1::Int) -> void $ newOutput (Just 1) (return "ok")
_ -> void $ newOutput (Just 1) (return "nok")
testVarEx3 :: Bool
testVarEx3 = isOutput "ok" (execRule testVar3)
--Test variable writing
testVar4 :: Rule
testVar4 = do
var <- newVar_ "toto" (1::Int)
writeVar var (2::Int)
a <- readVar var
case a of
Just (2::Int) -> void $ newOutput (Just 1) (return "ok")
_ -> void $ newOutput (Just 1) (return "nok")
testVarEx4 :: Bool
testVarEx4 = isOutput "ok" (execRule testVar4)
--Test variable writing
testVar5 :: Rule
testVar5 = do
var <- newVar_ "toto" ([]::[Int])
writeVar var ([1]::[Int])
a <- readVar var
case a of
Just (a::[Int]) -> void $ writeVar var (2:a)
Nothing -> void $ newOutput (Just 1) (return "nok")
testVarEx5 :: Bool
testVarEx5 = (show $ head $ _variables $ execRule testVar5) == "Rule number = 0, Name = \"toto\", Value = [2,1]\n"
-- Test rule activation
testActivateRule :: Rule
testActivateRule = do
a <- GetRules
when (_rStatus (head a) == Pending) $ void $ ActivateRule $ _rNumber (head a)
testActivateRuleEx :: Bool
testActivateRuleEx = _rStatus (head $ _rules (execRuleGame testActivateRule testGame {_rules=[testRule]})) == Active
testAutoActivateEx :: Bool
testAutoActivateEx = _rStatus (head $ _rules (execRuleEventGame autoActivate (Signal Proposed) testRule (testGame {_rules=[testRule]}))) == Active
--testAutoDelete :: Rule
--testAutoDelete = do
-- outputAll_ "before"
-- autoDelete
-- outputAll_ "after"
--
--testAutoDeleteEx = isOutput "ok" (execRuleGame testAutoDelete testGame {_rules=[testRule {_rNumber = 1}]})
-- Test deletes
testDeleteRule :: Rule
testDeleteRule = do
newVar_ "toto" (1::Int)
onMessage (Signal "msg" :: Msg ()) (const $ return ())
void $ newOutput (Just 1) (return "toto")
testDeleteGame :: Game
testDeleteGame = flip execState testGame {_players = []} $ runSystemEval 1 $ do
addActivateRule testDeleteRule 1
addActivateRule (void $ suppressRule 1) 2
testDeleteRuleEx1 :: Bool
testDeleteRuleEx1 = (_rStatus $ head $ drop 1 $ _rules testDeleteGame) == Reject &&
--(_variables testDeleteGame == []) &&
(_oStatus $ head $ _outputs testDeleteGame) == SDeleted &&
(_evStatus $ _erEventInfo $ head $ _events testDeleteGame) == SDeleted
-- Test victory
testVictoryGame :: Game
testVictoryGame = flip execState testGame $ runSystemEval' $ do
addActivateRule (victoryXRules 1) 1
addActivateRule (nothing) 2
testVictoryEx1 :: Bool
testVictoryEx1 = (length $ getVictorious testVictoryGame) == 1
-- Test votes
voteGameActions :: Int -> Int -> Int -> Bool -> Evaluate () -> Game
voteGameActions positives negatives total timeEvent actions = flip execState testGame {_players = []} $ runSystemEval' $ do
mapM_ (\x -> addPlayer (PlayerInfo x ("coco " ++ show x) Nothing)) [1..total]
actions
evProposeRule testRule
evs <- lift getChoiceEvents
traceM $ "choice events =" ++ (show evs)
mapM_ (triggerVote 1) (take positives evs) --issuing positive votes
mapM_ (triggerVote 0) (take negatives $ drop positives evs) --issuing negative votes
when timeEvent $ evTriggerTime date2
--Trigger a vote event (0 for positive, 1 for negative), using event details
triggerVote :: Int -> Input -> Evaluate ()
triggerVote res is = triggerInput is (RadioData res)
voteGame' :: Int -> Int -> Int -> Bool -> Rule -> Game
voteGame' positives negatives notVoted timeEvent rf = voteGameActions positives negatives notVoted timeEvent $ addActivateRule rf 1
voteGame :: Int -> Int -> Int -> Rule -> Game
voteGame positives negatives notVoted = voteGame' positives negatives notVoted False
voteGameTimed :: Int -> Int -> Int -> Rule -> Game
voteGameTimed positives negatives notVoted = voteGame' positives negatives notVoted True
testVoteAssessOnVoteComplete1, testVoteAssessOnVoteComplete2, testVoteAssessOnEveryVote1, testVoteAssessOnEveryVote2, testVoteAssessOnEveryVote3, testVoteAssessOnEveryVote4, testVoteMajorityWith, testVoteNumberPositiveVotes, testVoteWithQuorum1, testVoteAssessOnTimeLimit1, testVoteAssessOnTimeLimit2, testVoteAssessOnTimeLimit3, testVoteAssessOnTimeLimit4, testVoteAssessOnTimeLimit5 :: Bool
-- vote rules |Expected result |pos |neg |total |description of voting system
testVoteAssessOnVoteComplete1 = testVoteRule Active $ voteGame 10 0 10 $ onRuleProposed $ (callVoteRule majority oneDay)
testVoteAssessOnVoteComplete2 = testVoteRule Pending $ voteGame 9 0 10 $ onRuleProposed $ (callVoteRule unanimity oneDay)
testVoteAssessOnEveryVote1 = testVoteRule Active $ voteGame 10 0 10 $ onRuleProposed $ (callVoteRule unanimity oneDay)
testVoteAssessOnEveryVote2 = testVoteRule Active $ voteGame 6 0 10 $ onRuleProposed $ (callVoteRule majority oneDay)
testVoteAssessOnEveryVote3 = testVoteRule Pending $ voteGame 5 0 10 $ onRuleProposed $ (callVoteRule majority oneDay)
testVoteAssessOnEveryVote4 = testVoteRule Reject $ voteGame 0 5 10 $ onRuleProposed $ (callVoteRule majority oneDay)
testVoteMajorityWith = testVoteRule Active $ voteGame 6 0 10 $ onRuleProposed $ (callVoteRule (majorityWith 50) oneDay)
testVoteNumberPositiveVotes = testVoteRule Active $ voteGame 3 7 10 $ onRuleProposed $ (callVoteRule (numberVotes 3) oneDay)
testVoteWithQuorum1 = testVoteRule Active $ voteGame 7 3 10 $ onRuleProposed $ (callVoteRule (majority `withQuorum` 7) oneDay)
testVoteAssessOnTimeLimit1 = testVoteRule Active $ voteGameTimed 10 0 10 $ onRuleProposed $ (callVoteRule' unanimity date2)
testVoteAssessOnTimeLimit2 = testVoteRule Active $ voteGameTimed 1 0 10 $ onRuleProposed $ (callVoteRule' unanimity date2)
testVoteAssessOnTimeLimit3 = testVoteRule Reject $ voteGameTimed 1 0 10 $ onRuleProposed $ (callVoteRule' (unanimity `withQuorum` 5) date2)
testVoteAssessOnTimeLimit4 = testVoteRule Reject $ voteGameTimed 0 0 10 $ onRuleProposed $ (callVoteRule' (unanimity `withQuorum` 1) date2)
testVoteAssessOnTimeLimit5 = testVoteRule Active $ voteGameTimed 1 0 10 $ onRuleProposed $ (callVoteRule' (unanimity `withQuorum` 1) date2)
testVoteRule s g = (_rStatus $ head $ _rules g) == s
--Test with a player arriving in the middle of a vote (he should be able to vote)
testVotePlayerArrive :: Game
testVotePlayerArrive = flip execState testGame {_players = []} $ runSystemEval' $ do
addPlayer (PlayerInfo 1 "coco 1" Nothing)
addActivateRule (onRuleProposed $ callVoteRule (unanimity `withQuorum` 2) oneDay) 1
evProposeRule testRule
evs <- lift getChoiceEvents
mapM_ (triggerVote 1) evs --issuing positive vote player 1
addPlayer (PlayerInfo 2 "coco 2" Nothing) --new player
evs <- lift getChoiceEvents
mapM_ (triggerVote 1) evs --issuing positive vote player 2
testVotePlayerArriveEx = testVoteRule Active testVotePlayerArrive
--Test with a player leaving in the middle of a vote
--in some cases a player leaving can trigger the end of the vote (i.e when everybody voted except him)
testVotePlayerLeave :: Game
testVotePlayerLeave = flip execState testGame {_players = []} $ runSystemEval' $ do
addPlayer (PlayerInfo 1 "coco 1" Nothing)
addPlayer (PlayerInfo 2 "coco 2" Nothing)
addActivateRule (onRuleProposed $ callVoteRule unanimity oneDay) 1
evProposeRule testRule
evs <- lift getChoiceEvents
triggerVote 1 (last evs) -- issuing positive vote player 1
evDelPlayer 2 -- player 2 leaving
testVotePlayerLeaveEx :: Bool
testVotePlayerLeaveEx = testVoteRule Active testVotePlayerLeave
--Get all event numbers of type choice (radio button)
getChoiceEvents :: State EvalEnv [Input]
getChoiceEvents = do
evs <- use (evalEnv . eGame . events)
ee <- get
return $ concatMap (getInputChoices ee) evs
getInputChoices :: EvalEnv -> RuleEventInfo -> [Input]
getInputChoices ee (RuleEventInfo _ ei) = mapMaybe isInput (getRemainingSignals ei ee) where
isInput :: SomeSignal -> Maybe Input
isInput (SomeSignal (Signal s)) = cast s
addPlayer :: PlayerInfo -> Evaluate Bool
addPlayer pi = do
pls <- use (evalEnv . eGame . players)
let exists = any (((==) `on` _playerNumber) pi) pls
unless exists $ do
(evalEnv . eGame . players) %= (pi:)
triggerEvent (Signal Arrive) pi
return $ not exists
isOutput :: String -> Game -> Bool
isOutput s g = s `elem` allOutputs g