horizontal-rule-0.7.0.0: test/Test/HMock/Internal/ExpectSet.hs
{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE GADTSyntax #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TemplateHaskell #-}
-- | The internal core language of expectations in HMock.
module Test.HMock.Internal.ExpectSet where
import Test.HMock.Multiplicity
( Multiplicity,
between,
feasible, meetsMultiplicity
)
-- | A set of expected steps and their responses. This is the "core" language
-- of expectations for HMock. It's based roughly on Svenningsson, Svensson,
-- Smallbone, Arts, Norell, and Hughes' Expressive Semantics of Mocking.
-- However, there are a few small adjustments. We have two repetition operators
-- which respectively represent general repetition with interleaving, and
-- consecutive repetition. We also attach arbitrary multiplicities to
-- repetition.
data ExpectSet step where
ExpectStep :: step -> ExpectSet step
ExpectNothing :: ExpectSet step
ExpectSequence :: ExpectSet step -> ExpectSet step -> ExpectSet step
ExpectInterleave :: ExpectSet step -> ExpectSet step -> ExpectSet step
ExpectEither :: ExpectSet step -> ExpectSet step -> ExpectSet step
ExpectMulti :: Multiplicity -> ExpectSet step -> ExpectSet step
ExpectConsecutive :: Multiplicity -> ExpectSet step -> ExpectSet step
deriving (Show, Eq)
-- | Checks whether an ExpectSet is in an "accepting" state. In other words, is
-- it okay for the test to end here? If False, then there are still
-- expectations that must be satisfied before the test can succeed.
satisfied :: ExpectSet step -> Bool
satisfied (ExpectStep _) = False
satisfied ExpectNothing = True
satisfied (ExpectSequence e f) = satisfied e && satisfied f
satisfied (ExpectInterleave e f) = satisfied e && satisfied f
satisfied (ExpectEither e f) = satisfied e || satisfied f
satisfied (ExpectMulti mult e) =
feasible mult && (meetsMultiplicity mult 0 || satisfied e)
satisfied (ExpectConsecutive mult e) =
feasible mult && (meetsMultiplicity mult 0 || satisfied e)
-- | Computes the live steps of the ExpectSet. In other words: which individual
-- steps can be matched right now, and what are the remaining expectations in
-- each case?
liveSteps :: ExpectSet step -> [(step, ExpectSet step)]
liveSteps (ExpectStep step) = [(step, ExpectNothing)]
liveSteps ExpectNothing = []
liveSteps (ExpectSequence e f) =
(fmap (`ExpectSequence` f) <$> liveSteps e)
++ if satisfied e then liveSteps f else []
liveSteps (ExpectInterleave e f) =
(fmap (`ExpectInterleave` f) <$> liveSteps e)
++ (fmap (ExpectInterleave e) <$> liveSteps f)
liveSteps (ExpectEither e f) = liveSteps e ++ liveSteps f
liveSteps (ExpectMulti mult e)
| feasible (mult - 1) =
[ (step, ExpectInterleave f (ExpectMulti (mult - 1) e))
| (step, f) <- liveSteps e
]
| otherwise = []
liveSteps (ExpectConsecutive mult e)
| feasible (mult - 1) =
[ (step, ExpectSequence f (ExpectConsecutive (mult - 1) e))
| (step, f) <- liveSteps e
]
| otherwise = []
-- | Performs a complete simplification of the ExpectSet. This could be slow,
-- but we intend to do it only for error messages, so it need not be very fast.
simplify :: ExpectSet step -> ExpectSet step
simplify (ExpectSequence e f)
| ExpectNothing <- e' = f'
| ExpectNothing <- f' = e'
| ExpectSequence e1 e2 <- e' =
simplify (ExpectSequence e1 (ExpectSequence e2 f'))
| otherwise = ExpectSequence e' f'
where
e' = simplify e
f' = simplify f
simplify (ExpectInterleave e f)
| ExpectNothing <- e' = f'
| ExpectNothing <- f' = e'
| ExpectInterleave e1 e2 <- e' =
simplify (ExpectInterleave e1 (ExpectInterleave e2 f'))
| otherwise = ExpectInterleave e' f'
where
e' = simplify e
f' = simplify f
simplify (ExpectEither e f)
| ExpectNothing <- e', ExpectNothing <- f' = ExpectNothing
| ExpectNothing <- e' = simplify (ExpectEither f' ExpectNothing)
| ExpectEither e1 e2 <- e' =
simplify (ExpectEither e1 (ExpectEither e2 f'))
| ExpectNothing <- f', satisfied e' = e'
| ExpectNothing <- f' = simplify (ExpectMulti (between 0 1) e')
| otherwise = ExpectEither e' f'
where
e' = simplify e
f' = simplify f
simplify (ExpectMulti m e)
| not (feasible m) = ExpectMulti m ExpectNothing
| ExpectNothing <- e' = ExpectNothing
| m == 0 = ExpectNothing
| m == 1 = e'
| otherwise = ExpectMulti m e'
where
e' = simplify e
simplify (ExpectConsecutive m e)
| not (feasible m) = ExpectConsecutive m ExpectNothing
| ExpectNothing <- e' = ExpectNothing
| m == 0 = ExpectNothing
| m == 1 = e'
| otherwise = ExpectConsecutive m e'
where
e' = simplify e
simplify other = other
-- | Get a list of all steps mentioned by an 'ExpectSet'. This is used to
-- determine which classes need to be initialized before adding an expectation.
getSteps :: ExpectSet step -> [step]
getSteps ExpectNothing = []
getSteps (ExpectStep step) = [step]
getSteps (ExpectInterleave e f) = getSteps e ++ getSteps f
getSteps (ExpectSequence e f) = getSteps e ++ getSteps f
getSteps (ExpectEither e f) = getSteps e ++ getSteps f
getSteps (ExpectMulti _ e) = getSteps e
getSteps (ExpectConsecutive _ e) = getSteps e
-- | A higher-level intermediate form of an ExpectSet suitable for communication
-- with the user. Chains of binary operators are collected into sequences to
-- be displayed in lists rather than arbitrary nesting.
data CollectedSet step where
CollectedStep :: step -> CollectedSet step
CollectedNothing :: CollectedSet step
CollectedSequence :: [CollectedSet step] -> CollectedSet step
CollectedInterleave :: [CollectedSet step] -> CollectedSet step
CollectedChoice :: [CollectedSet step] -> CollectedSet step
CollectedMulti :: Multiplicity -> CollectedSet step -> CollectedSet step
CollectedConsecutive :: Multiplicity -> CollectedSet step -> CollectedSet step
-- | Collects an ExpectSet into the intermediate form for display. It's assumed
-- that the expression was simplified before this operation.
collect :: ExpectSet step -> CollectedSet step
collect (ExpectStep s) = CollectedStep s
collect ExpectNothing = CollectedNothing
collect (ExpectSequence e f) = CollectedSequence (collect e : fs)
where
fs = case collect f of
CollectedSequence f' -> f'
f' -> [f']
collect (ExpectInterleave e f) = CollectedInterleave (collect e : fs)
where
fs = case collect f of
CollectedInterleave f' -> f'
f' -> [f']
collect (ExpectEither e f) = CollectedChoice (collect e : fs)
where
fs = case collect f of
CollectedChoice f' -> f'
f' -> [f']
collect (ExpectMulti m e) = CollectedMulti m (collect e)
collect (ExpectConsecutive m e) = CollectedConsecutive m (collect e)
-- | Converts a set of expectations into a string that summarizes them, with
-- the given prefix (used to indent).
formatExpectSet :: (Show step) => ExpectSet step -> String
formatExpectSet = go "" . collect . simplify
where
go prefix CollectedNothing = prefix ++ "* nothing"
go prefix (CollectedStep step) = prefix ++ "* " ++ show step
go prefix (CollectedSequence cs) =
prefix ++ "* in sequence:\n" ++ unlines (map (go (" " ++ prefix)) cs)
go prefix (CollectedInterleave cs) =
prefix ++ "* in any order:\n" ++ unlines (map (go (" " ++ prefix)) cs)
go prefix (CollectedChoice cs) =
prefix ++ "* any of:\n" ++ unlines (map (go (" " ++ prefix)) cs)
go prefix (CollectedMulti m e) =
prefix ++ "* " ++ show m ++ ":\n" ++ go (" " ++ prefix) e
go prefix (CollectedConsecutive m e) =
prefix ++ "* " ++ show m ++ " consecutively:\n" ++ go (" " ++ prefix) e
-- | Reduces a set of expectations to the minimum steps that would be required
-- to satisfy the entire set. This weeds out unnecessary information before
-- reporting that there were unmet expectations at the end of the test.
excess :: ExpectSet step -> ExpectSet step
excess = simplify . go
where
go (ExpectSequence e f) = ExpectSequence (go e) (go f)
go (ExpectInterleave e f) = ExpectInterleave (go e) (go f)
go (ExpectEither e f)
| satisfied e || satisfied f = ExpectNothing
| otherwise = ExpectEither (go e) (go f)
go (ExpectMulti m e)
| meetsMultiplicity m 0 = ExpectNothing
| otherwise = ExpectMulti m (go e)
go (ExpectConsecutive m e)
| meetsMultiplicity m 0 = ExpectNothing
| otherwise = ExpectConsecutive m (go e)
go other = other