module Test.HUnitPlus.Filter(
Selector(..),
Filter(..),
combineTags,
passFilter,
allSelector,
combineSelectors,
suiteSelectors,
parseFilter,
parseFilterFile,
parseFilterFileContent
) where
import Control.Exception
import Data.Foldable(foldl)
import Data.Either
import Data.Map(Map)
import Data.Maybe
import Data.Set(Set)
import Prelude hiding (foldl, elem)
import System.IO.Error
import Text.ParserCombinators.Parsec hiding (try)
import qualified Data.Set as Set
import qualified Data.Map as Map
data Selector =
Selector {
selectorInners :: Map String Selector,
selectorTags :: !(Maybe (Set String))
}
deriving (Eq, Ord, Show)
data Filter =
Filter {
filterSuites :: !(Set String),
filterSelector :: !Selector
}
deriving (Ord, Eq, Show)
combineTags :: Maybe (Set String) -> Maybe (Set String) -> Maybe (Set String)
combineTags Nothing t = t
combineTags t Nothing = t
combineTags (Just a) (Just b)
| a == Set.empty || b == Set.empty = Just $! Set.empty
| otherwise = Just $! Set.union a b
diffTags :: Maybe (Set String) -> Maybe (Set String) -> Maybe (Set String)
diffTags Nothing _ = Nothing
diffTags t Nothing = t
diffTags (Just a) (Just b)
| a == Set.empty = Just Set.empty
| b == Set.empty = Nothing
| otherwise =
let
diff = Set.difference a b
in
if diff == Set.empty
then Nothing
else Just $! diff
passFilter :: Filter
passFilter = Filter { filterSuites = Set.empty, filterSelector = allSelector }
allSelector :: Selector
allSelector = Selector { selectorInners = Map.empty,
selectorTags = Just Set.empty }
reduceSelector :: Maybe (Set String) -> Selector -> Maybe Selector
reduceSelector parentTags Selector { selectorInners = inners,
selectorTags = tags } =
let
newTags = diffTags tags parentTags
newParentTags = combineTags parentTags tags
newInners = Map.mapMaybe (reduceSelector newParentTags) inners
in
if newTags == Nothing && newInners == Map.empty
then Nothing
else Just $! Selector { selectorInners = inners, selectorTags = tags }
combineSelectors :: Selector -> Selector -> Selector
combineSelectors selector1 selector2 =
let
combineSelectors' :: Maybe (Set String) -> Selector -> Selector ->
Maybe Selector
combineSelectors' parentTags
s1 @ Selector { selectorInners = inners1,
selectorTags = tags1 }
s2 @ Selector { selectorInners = inners2,
selectorTags = tags2 }
| s1 == allSelector || s2 == allSelector = Just allSelector
| otherwise =
let
combinedTags = combineTags tags1 tags2
newTags = diffTags combinedTags parentTags
newParentTags = combineTags combinedTags parentTags
firstpass :: Map String Selector -> String -> Selector ->
Map String Selector
firstpass accum elem inner =
case Map.lookup elem inners1 of
Just inner' -> case combineSelectors' newParentTags inner inner' of
Just entry -> Map.insert elem entry accum
Nothing -> accum
Nothing -> case reduceSelector newParentTags inner of
Just entry -> Map.insert elem entry accum
Nothing -> accum
secondpass :: Map String Selector -> String -> Selector ->
Map String Selector
secondpass accum elem inner =
case Map.lookup elem accum of
Nothing -> case reduceSelector newParentTags inner of
Just entry -> Map.insert elem entry accum
Nothing -> accum
Just _ -> accum
firstPassMap = Map.foldlWithKey firstpass Map.empty inners2
newInners = Map.foldlWithKey secondpass firstPassMap inners1
in
if newTags == Nothing && newInners == Map.empty
then Nothing
else Just $! Selector { selectorInners = newInners,
selectorTags = newTags }
in
case combineSelectors' Nothing selector1 selector2 of
Just out -> out
Nothing -> error ("Got Nothing back from combineSelectors " ++
show selector1 ++ " " ++ show selector2)
collectUniversals :: Filter -> Set Selector -> Set Selector
collectUniversals Filter { filterSuites = suites,
filterSelector = selector } accum
| suites == Set.empty = Set.insert selector accum
| otherwise = accum
collectSelectors :: Filter
-> Map String (Set Selector)
-> Map String (Set Selector)
collectSelectors Filter { filterSuites = suites, filterSelector = selector }
suitemap =
foldl (\suitemap' suite -> Map.insertWith Set.union suite
(Set.singleton selector)
suitemap')
suitemap suites
suiteSelectors :: [String]
-> [Filter]
-> Map String Selector
suiteSelectors allsuites filters
| filters == [] =
foldl (\suitemap suite -> Map.insert suite allSelector suitemap)
Map.empty allsuites
| otherwise =
let
universals = foldr collectUniversals Set.empty filters
initMap =
if universals /= Set.empty
then foldl (\suitemap suite -> Map.insert suite universals suitemap)
Map.empty allsuites
else Map.empty
suiteMap :: Map String (Set Selector)
suiteMap = foldr collectSelectors initMap filters
in
Map.map (foldl1 combineSelectors . Set.elems) suiteMap
namesParser :: GenParser Char st [String]
namesParser = sepBy1 (many1 alphaNum) (string ",")
pathParser :: GenParser Char st [String]
pathParser = sepBy (many1 alphaNum) (string ".")
suitesParser :: GenParser Char st [String]
suitesParser = between (string "[") (string "]") namesParser
tagsParser :: GenParser Char st [String]
tagsParser = char '@' >> namesParser
filterParser :: GenParser Char st ([String], [String], [String])
filterParser =
do
suites <- option [] (suitesParser)
path <- pathParser
tagselector <- option [] tagsParser
return (suites, path, tagselector)
makeFilter :: ([String], [String], [String]) -> Filter
makeFilter (suites, path, tags) =
let
withTags = case tags of
[] -> allSelector
_ -> allSelector { selectorTags = Just $! Set.fromList tags }
genPath [] = withTags
genPath (elem : rest) =
Selector { selectorInners = Map.singleton elem $! genPath rest,
selectorTags = Nothing }
withPath = genPath path
in
Filter { filterSuites = Set.fromList suites, filterSelector = withPath }
parseFilter :: String
-> String
-> Either String Filter
parseFilter sourcename input =
case parse filterParser sourcename input of
Left e -> Left (show e)
Right res -> Right (makeFilter res)
commentParser :: GenParser Char st ()
commentParser =
do
_ <- char '#'
_ <- many (noneOf "\n")
return $ ()
lineParser :: GenParser Char st (Maybe Filter)
lineParser =
do
_ <- many space
content <- filterParser
_ <- many space
optional commentParser
case content of
([], [], []) -> return $ Nothing
_ -> return $ (Just (makeFilter content))
parseFilterFileContent :: String
-> String
-> Either [String] [Filter]
parseFilterFileContent sourcename input =
let
inputlines = lines input
results = map (parse lineParser sourcename) inputlines
in case partitionEithers results of
([], maybes) -> Right (catMaybes maybes)
(errs, _) -> Left (map show errs)
parseFilterFile :: FilePath -> IO (Either [String] [Filter])
parseFilterFile filename =
do
input <- try (readFile filename)
case input of
Left e
| isAlreadyInUseError e ->
return (Left ["Error reading testlist file " ++ filename ++
": File is already in use"])
| isDoesNotExistError e ->
return (Left ["Error reading testlist file " ++ filename ++
": File does not exist"])
| isPermissionError e ->
return (Left ["Error reading testlist file " ++ filename ++
": Permission denied"])
| otherwise ->
return (Left ["Cannot read testlist file " ++ filename ++
": Miscellaneous error"])
Right contents ->
case parseFilterFileContent filename contents of
Left errs -> return (Left errs)
Right out -> return (Right out)