Improved the printing of Patterns so that more helpful and readable output is produced when a test fails

This commit is contained in:
Neil Brown 2007-08-16 14:11:58 +00:00
parent b9a78e99e4
commit e6d4425e82
2 changed files with 31 additions and 12 deletions

View File

@ -2,7 +2,7 @@
-- This ought to use a class (like show does), so that it can be extended -- This ought to use a class (like show does), so that it can be extended
-- properly without me needing to have Tock-specific cases in here -- see the -- properly without me needing to have Tock-specific cases in here -- see the
-- appropriate SYB paper. -- appropriate SYB paper.
module PrettyShow (pshow) where module PrettyShow (pshow, isTupleCtr) where
import Data.Generics import Data.Generics
import qualified Data.Map as Map import qualified Data.Map as Map

View File

@ -4,7 +4,8 @@ import Test.HUnit hiding (State)
import qualified Data.Map as Map import qualified Data.Map as Map
import Control.Monad.State import Control.Monad.State
import Data.Generics import Data.Generics
import qualified PrettyShow as PS
import Text.PrettyPrint.HughesPJ
data Pattern = data Pattern =
-- | We don't care what the value is -- will match against any item -- | We don't care what the value is -- will match against any item
@ -37,6 +38,20 @@ constr_DontCare = mkConstr ty_Pattern "DontCare" [] Prefix
constr_Named = mkConstr ty_Pattern "Named" [] Prefix constr_Named = mkConstr ty_Pattern "Named" [] Prefix
constr_Match = mkConstr ty_Pattern "Match" [] Prefix constr_Match = mkConstr ty_Pattern "Match" [] Prefix
--Print the data nicely for TreeUtil.Pattern, to make it look like a pattern match:
pshowPattern :: Pattern -> String
pshowPattern = render . pshowPattern'
where
pshowPattern' :: Pattern -> Doc
pshowPattern' (DontCare) = text "_"
pshowPattern' (Named s p) = (text (s ++ "@")) <> (pshowPattern' p)
pshowPattern' (Match c ps) =
--All a bit hacky, admittedly:
if PS.isTupleCtr (showConstr c) then parens $ sep $ punctuate (text ",") items
--TODO add some decent list unfolding (to display Match (:) [x,Match (:) [y,Match [] []]] as [x,y]
else parens $ (text (showConstr c)) $+$ (sep items)
where items = map pshowPattern' ps
type MatchErrors = [String] type MatchErrors = [String]
-- | A function for asserting equality over generic types. Like assertEqual, but it can take items of different types to compare -- | A function for asserting equality over generic types. Like assertEqual, but it can take items of different types to compare
@ -45,7 +60,7 @@ type MatchErrors = [String]
assertGenEqualPlain :: (Data a, Data b) => String -> a -> b -> MatchErrors assertGenEqualPlain :: (Data a, Data b) => String -> a -> b -> MatchErrors
assertGenEqualPlain s x y = do case (checkEqual x y) of assertGenEqualPlain s x y = do case (checkEqual x y) of
True -> [] True -> []
False -> [s ++ "; Items not equal, expected: " ++ (gshow x) ++ " but got: " ++ (gshow y)] False -> [s ++ " Items not equal, expected:\n" ++ (PS.pshow x) ++ "\n***but got:\n " ++ (PS.pshow y)]
where where
checkEqual x y = case (cast y) of checkEqual x y = case (cast y) of
-- | Same type, use the library-provided geq function to compare them -- | Same type, use the library-provided geq function to compare them
@ -68,7 +83,9 @@ checkItem :: Data z => String -> z -> State Items MatchErrors
checkItem key val checkItem key val
= do items <- get = do items <- get
case Map.lookup key items of case Map.lookup key items of
Just (ADI val') -> return (assertGenEqualPlain "Item of interest does not match prior value" val' val) Just (ADI val') -> return $
--show key will put quote marks around it, and escape any special characters (which seems appropriate here):
assertGenEqualPlain ("Item of interest does not match prior value for key " ++ (show key) ++ ": ") val' val
Nothing -> do {put (Map.insert key (ADI val) items) ; return [] } Nothing -> do {put (Map.insert key (ADI val) items) ; return [] }
-- | A function that takes an expected Pattern value, an actual Data value, and returns the appropriate checks -- | A function that takes an expected Pattern value, an actual Data value, and returns the appropriate checks
@ -80,16 +97,18 @@ checkMatch DontCare _ = return []
-- | Items of interest are delegated to the checkItem function that uses the Items state -- | Items of interest are delegated to the checkItem function that uses the Items state
checkMatch (Named s p) b = sequenceS [checkMatch p b, checkItem s b] checkMatch (Named s p) b = sequenceS [checkMatch p b, checkItem s b]
-- | Constructors are matched using the applyAll function (but we must also check the constructors are equal) -- | Constructors are matched using the applyAll function (but we must also check the constructors are equal)
checkMatch (Match con items) b checkMatch m@(Match con items) b
= do conEq <- checkEq con (toConstr b) = do conEq <- checkConsEq con (toConstr b) m b
case conEq of case conEq of
[] -> sequenceS $ (applyAll items b) [] -> sequenceS $ (applyAll items b)
_ -> return conEq --no point comparing fields if the constructors don't match _ -> return conEq --no point comparing fields if the constructors don't match
where where
checkEq :: Constr -> Constr -> State Items MatchErrors --The whole things are given as third/fourth parameters just so we can produce a more helpful error message:
checkEq a b = if (a == b) checkConsEq :: Data z => Constr -> Constr -> Pattern -> z -> State Items MatchErrors
checkConsEq a b a' b' = if (a == b)
then return [] then return []
else return ["Constructors not equal, expected: " ++ (show a) ++ " actual: " ++ (show b)] else return ["Constructors not equal, expected constructor: " ++ (show a) ++ " actual cons: " ++ (show b)
++ " while trying to match expected:\n" ++ (pshowPattern a') ++ "\n*** against actual:\n " ++ (PS.pshow b')]
-- | applyAll checks that the non-constructor items of an algebraic data type are matched: -- | applyAll checks that the non-constructor items of an algebraic data type are matched:
applyAll :: Data z => [Pattern] -> z -> [State Items MatchErrors] applyAll :: Data z => [Pattern] -> z -> [State Items MatchErrors]
@ -98,7 +117,7 @@ checkMatch (Match con items) b
applyAll' d (index,f) applyAll' d (index,f)
= if (index >= numIndexes) = if (index >= numIndexes)
then return ["Invalid index in applyAll: " ++ (show index) ++ ", numIndexes is: " ++ (show numIndexes) then return ["Invalid index in applyAll: " ++ (show index) ++ ", numIndexes is: " ++ (show numIndexes)
++ " trying to check, expected: " ++ (show f) ++ " actual: " ++ (gshow d)] ++ " trying to check, expected: " ++ (pshowPattern f) ++ " actual: " ++ (PS.pshow d)]
else (gmapQi index (checkMatch f) d) else (gmapQi index (checkMatch f) d)
--Possibly a better way? --Possibly a better way?
where where
@ -118,7 +137,7 @@ sequenceS x = (liftM concat) (sequence x)
assertPatternMatch :: (Data y, Data z) => String -> y -> z -> Assertion assertPatternMatch :: (Data y, Data z) => String -> y -> z -> Assertion
assertPatternMatch msg exp act = assertPatternMatch msg exp act =
-- Uncomment this line for debugging help: -- Uncomment this line for debugging help:
-- putStrLn ("Testing: " ++ (gshow a) ++ " vs " ++ (gshow b)) >> -- putStrLn ("Testing: " ++ (PS.pshow a) ++ " vs " ++ (PS.pshow b)) >>
sequence_ $ map (assertFailure . ((++) msg)) (evalState (checkMatch (mkPattern exp) act) (Map.empty)) sequence_ $ map (assertFailure . ((++) msg)) (evalState (checkMatch (mkPattern exp) act) (Map.empty))
-- | A function for getting the matched items from the patterns on the LHS -- | A function for getting the matched items from the patterns on the LHS