tasty-sugar-1.3.0.0: src/internal/Test/Tasty/Sugar/ExpectCheck.hs
{-# LANGUAGE LambdaCase #-}
{-# OPTIONS_GHC -fno-warn-deprecations #-}
-- | Function to find expected results files for a specific root file,
-- along with any parameter values identified by the root file.
module Test.Tasty.Sugar.ExpectCheck
(
findExpectation
, removeNonExplicitMatchingExpectations
)
where
import Control.Monad
import Control.Monad.Logic
import qualified Data.List as L
import Test.Tasty.Sugar.AssocCheck
import Test.Tasty.Sugar.ParamCheck
import Test.Tasty.Sugar.Types
-- | Finds the possible expected files matching the selected
-- source. There will be either one or none.
findExpectation :: CUBE
-> CandidateFile -- original name of source
-> [CandidateFile] -- all of the names to choose from
-> ([NamedParamMatch], CandidateFile, String) -- param constraints from the root name
-> Maybe ( Sweets, SweetExplanation )
findExpectation pat rootN allNames (rootPMatches, matchPrefix, _) =
let r = mkSweet $
trimExpectations $
observeAll $
do guard (not $ null candidates)
expectedSearch
matchPrefix
rootPMatches seps params expSuffix o
candidates
o = associatedNames pat
seps = separators pat
params = validParams pat
expSuffix = expectedSuffix pat
candidates = filter possible allNames
possible f = and [ candidateFile matchPrefix `L.isPrefixOf` candidateFile f
, rootN /= f
]
mkSweet e = Just $ Sweets { rootMatchName = candidateFile rootN
, rootBaseName = candidateFile matchPrefix
, rootFile = candidateToPath rootN
, cubeParams = validParams pat
, expected = e
}
-- The expectedSearch tries various combinations and ordering of
-- parameter values, separators, and such to find all valid
-- expected file matches. However, the result is an
-- over-sampling, so this function trims the excess and unwanted
-- expectations.
trimExpectations :: [Expectation] -> [Expectation]
trimExpectations =
-- If a parameter is Explicitly matched, discard any
-- Expectation with the same Assumed matches.
removeNonExplicitMatchingExpectations
-- remove duplicates (uses the Eq instance for Expectation
-- that ignores the order of the expParamsMatch and associated
-- to ensure that different ordering with the same values
-- doesn't cause multiple Expectation.
. L.nub
in case r of
Nothing -> Nothing
Just r' | [] <- expected r' -> Nothing
Just r' -> Just ( r'
, SweetExpl { rootPath = candidateToPath rootN
, base = candidateToPath matchPrefix
, expectedNames =
filter
(if null expSuffix then const True
else (expSuffix `L.isSuffixOf`))
(candidateToPath <$> candidates)
, results = r'
})
-- Find all Expectations matching this rootMatch
expectedSearch :: CandidateFile
-> [NamedParamMatch]
-> Separators
-> [ParameterPattern]
-> FileSuffix
-> [ (String, FileSuffix) ]
-> [CandidateFile]
-> Logic Expectation
expectedSearch rootPrefix rootPVMatches seps params expSuffix assocNames allNames =
do params' <- singlePVals rootPVMatches params
(expFile, pmatch, assocFiles) <-
let bestRanked :: (Eq a, Eq b, Eq c)
=> [((a, Int, [b]),c)] -> Logic (a, [b], c)
bestRanked l =
if null l then mzero
else let m = maximum $ fmap rankValue l
rankValue ((_,r,_),_) = r
rankMatching v ((_,r,_),_) = v == r
dropRank ((a,_,b),c) = (a,b,c)
in eachFrom $ L.nub $ fmap dropRank $ filter (rankMatching m) l
in bestRanked $
observeAll $
do pseq <- eachFrom $
([] :) $
filter (not . null) $
concatMap L.inits $
L.permutations params'
pvals <- getPVals pseq
let compatNames = filter (isCompatible seps params pvals) allNames
guard (not $ null compatNames)
e@(_,_,pmatch) <- getExp rootPrefix rootPVMatches seps params pvals
expSuffix compatNames
a <- (getAssoc rootPrefix seps pmatch assocNames compatNames)
return (e,a)
return $ Expectation { expectedFile = candidateToPath expFile
, associated = fmap candidateToPath <$> assocFiles
, expParamsMatch = L.sort pmatch
}
-- | Get all expected files for a particular sequence of param+value.
-- Returns the expected file, the sequence of parameter values that
-- match that expect file, and a ranking (the number of those paramter
-- values that actually appear in the expect file.
getExp :: CandidateFile
-> [NamedParamMatch]
-> Separators
-> [ParameterPattern]
-> [(String, Maybe String)]
-> FileSuffix
-> [CandidateFile]
-> Logic (CandidateFile, Int, [NamedParamMatch])
getExp rootPrefix rootPMatches seps params pvals expSuffix allNames =
do -- Some of the params may be encoded in the subdirectories instead of in the
-- target filename (each param value could appear in either). If a
-- rootPMatches value is in a subdirectory, no other values for that
-- parameter can appear, otherwise all possible values could appear. A
-- subset of the rootPMatches may appear in the subdirs, but only the
-- maximal subset can be considered.
let rootMatchesInSubdir :: CandidateFile -> [NamedParamMatch]
rootMatchesInSubdir f =
let chkRootMatch d r =
let chkRPMatch p r' =
case getExplicit $ snd p of
Just v -> if d == v then p : r' else r'
Nothing -> r'
in foldr chkRPMatch r rootPMatches
in foldr chkRootMatch mempty $ candidateSubdirs f
let inpDirMatches = fmap rootMatchesInSubdir <$> zip allNames allNames
(dirName, inpDirMatch) <- eachFrom inpDirMatches
let nonRootMatchPVals = removePVals pvals inpDirMatch
(otherMatchesInSubdir, _) <-
dirMatches dirName params $ (fmap (fmap (:[])) <$> nonRootMatchPVals)
let remPVals = removePVals nonRootMatchPVals otherMatchesInSubdir
let remRootMatches = removePVals rootPMatches inpDirMatch
let validNames = [ dirName ]
(fp, cnt, npm) <- getExpFileParams rootPrefix
remRootMatches
seps remPVals expSuffix validNames
-- Corner case: a wildcard parameter could be selected from both a subdir
-- and the filename... if the values are the same, that's OK, but if the
-- values are different it should be rejected.
let dpm = inpDirMatch <> otherMatchesInSubdir
let conflict = let chkNPM (pn,pv) acc =
acc || case lookup pn dpm of
Nothing -> False
Just v -> v /= pv
in foldr chkNPM False npm
guard (not conflict)
return (fp, length dpm + cnt, dpm <> npm)
getExpFileParams :: CandidateFile
-> [NamedParamMatch]
-> Separators
-> [(String, Maybe String)]
-> FileSuffix
-> [CandidateFile]
-> Logic (CandidateFile, Int, [NamedParamMatch])
getExpFileParams rootPrefix rootPMatches seps pvals expSuffix hereNames =
do let suffixSpecifiesSep = and [ not (null expSuffix)
, head expSuffix `elem` seps
]
(pm, pmcnt, pmstr) <- pvalMatch seps rootPMatches pvals
-- If the expSuffix starts with a separator then *only that*
-- separator is allowed for the suffix (other seps are still
-- allowed for parameter value separation).
let suffixSepMatch = not suffixSpecifiesSep
|| and [ not (null pmstr)
, last pmstr == head expSuffix
]
guard suffixSepMatch
let ending = if suffixSpecifiesSep then tail expSuffix else expSuffix
expFile <-
eachFrom
$ filter (((candidateFile rootPrefix <> pmstr <> ending) ==) . candidateFile)
$ hereNames
return (expFile, pmcnt, pm)
-- | Determines the best Expectations to use from a list of Expectations that may
-- have different parameter match status against an expected file. When two
-- Expectations differ only in an Explicit v.s. Assumed (or wildcard) the
-- Explicit is preferred. Expectations with more parameter matches are preferred
-- over those with less.
removeNonExplicitMatchingExpectations :: [Expectation] -> [Expectation]
removeNonExplicitMatchingExpectations =
let removeNonExplicits e l =
let (similarExpl, diffExpl) = L.partition (cmpPVals e) l
cmpPVals ref ps =
-- Compare the two on the intersection subset of parameters
if length (expParamsMatch ref) < length (expParamsMatch ps)
then expPVals ref ref == expPVals ref ps
else expPVals ps ps == expPVals ps ref
expPVals ref ps =
-- Compare parameters by comparing the values of matching names
let ps' = expParamsMatch ps
ref' = expParamsMatch ref
refNames = fst <$> ref'
in (\n -> lookup n ps' >>= getParamVal) <$> refNames
in if null similarExpl
then e : l
else (pmatchMax expParamsMatch e <$> similarExpl) <> diffExpl
in foldr removeNonExplicits mempty