HUnit-Plus 1.1.0 → 2.0.0
raw patch · 19 files changed
+3877/−428 lines, 19 filesdep +HUnit-Plusdep +textdep +unordered-containersdep −containersPVP ok
version bump matches the API change (PVP)
Dependencies added: HUnit-Plus, text, unordered-containers
Dependencies removed: containers
API changes (from Hackage documentation)
- Test.HUnitPlus.Filter: instance GHC.Classes.Ord Test.HUnitPlus.Filter.Filter
+ Test.HUnitPlus.Base: heartbeat :: IO ()
+ Test.HUnitPlus.Base: infix 1 @?
+ Test.HUnitPlus.Base: infixr 0 ~:
+ Test.HUnitPlus.Filter: [filterOptions] :: Filter -> !(HashMap Text Text)
+ Test.HUnitPlus.Filter: instance Data.Hashable.Class.Hashable Test.HUnitPlus.Filter.Selector
+ Test.HUnitPlus.Filter: type OptionMap = HashMap Text Text
- Test.HUnitPlus.Base: TestSuite :: !String -> !Bool -> ![(String, String)] -> ![Test] -> TestSuite
+ Test.HUnitPlus.Base: TestSuite :: !Text -> !Bool -> ![(Text, Text)] -> ![Test] -> TestSuite
- Test.HUnitPlus.Base: [suiteName] :: TestSuite -> !String
+ Test.HUnitPlus.Base: [suiteName] :: TestSuite -> !Text
- Test.HUnitPlus.Base: [suiteOptions] :: TestSuite -> ![(String, String)]
+ Test.HUnitPlus.Base: [suiteOptions] :: TestSuite -> ![(Text, Text)]
- Test.HUnitPlus.Base: abortError :: String -> Assertion
+ Test.HUnitPlus.Base: abortError :: Text -> Assertion
- Test.HUnitPlus.Base: abortFailure :: String -> Assertion
+ Test.HUnitPlus.Base: abortFailure :: Text -> Assertion
- Test.HUnitPlus.Base: assertBool :: String -> Bool -> Assertion
+ Test.HUnitPlus.Base: assertBool :: Text -> Bool -> Assertion
- Test.HUnitPlus.Base: assertEqual :: (Eq a, Show a) => String -> a -> a -> Assertion
+ Test.HUnitPlus.Base: assertEqual :: (Eq a, Show a) => Text -> a -> a -> Assertion
- Test.HUnitPlus.Base: assertFailure :: String -> Assertion
+ Test.HUnitPlus.Base: assertFailure :: Text -> Assertion
- Test.HUnitPlus.Base: assertStringWithPrefix :: String -> String -> Assertion
+ Test.HUnitPlus.Base: assertStringWithPrefix :: Text -> String -> Assertion
- Test.HUnitPlus.Base: class Testable t where testName testname t = testNameTags testname [] t testTags tagset t = testNameTags syntheticName tagset t test t = testNameTags syntheticName [] t
+ Test.HUnitPlus.Base: class Testable t where testName testname = testNameTags testname [] testTags = testNameTags syntheticName test = testNameTags syntheticName []
- Test.HUnitPlus.Base: getErrors :: IO (Maybe String)
+ Test.HUnitPlus.Base: getErrors :: IO (Maybe Text)
- Test.HUnitPlus.Base: getFailures :: IO (Maybe String)
+ Test.HUnitPlus.Base: getFailures :: IO (Maybe Text)
- Test.HUnitPlus.Base: logError :: String -> IO ()
+ Test.HUnitPlus.Base: logError :: Text -> IO ()
- Test.HUnitPlus.Base: logFailure :: String -> IO ()
+ Test.HUnitPlus.Base: logFailure :: Text -> IO ()
- Test.HUnitPlus.Base: logSyserr :: String -> IO ()
+ Test.HUnitPlus.Base: logSyserr :: Text -> IO ()
- Test.HUnitPlus.Base: logSysout :: String -> IO ()
+ Test.HUnitPlus.Base: logSysout :: Text -> IO ()
- Test.HUnitPlus.Base: test :: (Testable t, Testable t) => t -> Test
+ Test.HUnitPlus.Base: test :: Testable t => t -> Test
- Test.HUnitPlus.Base: withPrefix :: String -> IO () -> IO ()
+ Test.HUnitPlus.Base: withPrefix :: Text -> IO () -> IO ()
- Test.HUnitPlus.Execution: performTestSuite :: Reporter us -> Map String Selector -> us -> TestSuite -> IO (Counts, us)
+ Test.HUnitPlus.Execution: performTestSuite :: Reporter us -> HashMap Text (HashMap OptionMap Selector) -> us -> TestSuite -> IO (Counts, us)
- Test.HUnitPlus.Execution: performTestSuites :: Reporter us -> Map String Selector -> [TestSuite] -> IO (Counts, us)
+ Test.HUnitPlus.Execution: performTestSuites :: Reporter us -> HashMap Text (HashMap OptionMap Selector) -> [TestSuite] -> IO (Counts, us)
- Test.HUnitPlus.Filter: Filter :: !(Set String) -> !Selector -> Filter
+ Test.HUnitPlus.Filter: Filter :: !(HashSet Text) -> !(HashSet Selector) -> !(HashMap Text Text) -> Filter
- Test.HUnitPlus.Filter: Selector :: Map String Selector -> !(Maybe (Set String)) -> Selector
+ Test.HUnitPlus.Filter: Selector :: HashMap Text Selector -> !(Maybe (HashSet Text)) -> Selector
- Test.HUnitPlus.Filter: [filterSelector] :: Filter -> !Selector
+ Test.HUnitPlus.Filter: [filterSelector] :: Filter -> !(HashSet Selector)
- Test.HUnitPlus.Filter: [filterSuites] :: Filter -> !(Set String)
+ Test.HUnitPlus.Filter: [filterSuites] :: Filter -> !(HashSet Text)
- Test.HUnitPlus.Filter: [selectorInners] :: Selector -> Map String Selector
+ Test.HUnitPlus.Filter: [selectorInners] :: Selector -> HashMap Text Selector
- Test.HUnitPlus.Filter: [selectorTags] :: Selector -> !(Maybe (Set String))
+ Test.HUnitPlus.Filter: [selectorTags] :: Selector -> !(Maybe (HashSet Text))
- Test.HUnitPlus.Filter: combineTags :: Maybe (Set String) -> Maybe (Set String) -> Maybe (Set String)
+ Test.HUnitPlus.Filter: combineTags :: Maybe (HashSet Text) -> Maybe (HashSet Text) -> Maybe (HashSet Text)
- Test.HUnitPlus.Filter: parseFilter :: String -> String -> Either String Filter
+ Test.HUnitPlus.Filter: parseFilter :: String -> Text -> Either Text Filter
- Test.HUnitPlus.Filter: parseFilterFile :: FilePath -> IO (Either [String] [Filter])
+ Test.HUnitPlus.Filter: parseFilterFile :: FilePath -> IO (Either [Text] [Filter])
- Test.HUnitPlus.Filter: parseFilterFileContent :: String -> String -> Either [String] [Filter]
+ Test.HUnitPlus.Filter: parseFilterFileContent :: String -> Text -> Either [Text] [Filter]
- Test.HUnitPlus.Filter: suiteSelectors :: [String] -> [Filter] -> Map String Selector
+ Test.HUnitPlus.Filter: suiteSelectors :: [Text] -> [Filter] -> HashMap Text (HashMap OptionMap Selector)
- Test.HUnitPlus.Main: topLevel :: [TestSuite] -> Opts -> IO (Either [String] Bool)
+ Test.HUnitPlus.Main: topLevel :: [TestSuite] -> Opts -> IO (Either [Text] Bool)
- Test.HUnitPlus.Reporting: Label :: String -> Node
+ Test.HUnitPlus.Reporting: Label :: Text -> Node
- Test.HUnitPlus.Reporting: Reporter :: IO us -> (Double -> Counts -> us -> IO us) -> (State -> us -> IO us) -> (Double -> State -> us -> IO us) -> (State -> us -> IO us) -> (String -> State -> us -> IO us) -> (Double -> State -> us -> IO us) -> (State -> us -> IO us) -> (String -> State -> us -> IO us) -> (String -> State -> us -> IO us) -> (String -> State -> us -> IO us) -> (String -> State -> us -> IO us) -> Reporter us
+ Test.HUnitPlus.Reporting: Reporter :: IO us -> (Double -> Counts -> us -> IO us) -> (State -> us -> IO us) -> (Double -> State -> us -> IO us) -> (State -> us -> IO us) -> (Text -> State -> us -> IO us) -> (Double -> State -> us -> IO us) -> (State -> us -> IO us) -> (Text -> State -> us -> IO us) -> (Text -> State -> us -> IO us) -> (Text -> State -> us -> IO us) -> (Text -> State -> us -> IO us) -> Reporter us
- Test.HUnitPlus.Reporting: State :: !String -> !Path -> !Counts -> !(Map String String) -> ![OptionDescr] -> State
+ Test.HUnitPlus.Reporting: State :: !Text -> !Path -> !Counts -> !(HashMap Text Text) -> ![OptionDescr] -> State
- Test.HUnitPlus.Reporting: [reporterCaseProgress] :: Reporter us -> String -> State -> us -> IO us
+ Test.HUnitPlus.Reporting: [reporterCaseProgress] :: Reporter us -> Text -> State -> us -> IO us
- Test.HUnitPlus.Reporting: [reporterError] :: Reporter us -> String -> State -> us -> IO us
+ Test.HUnitPlus.Reporting: [reporterError] :: Reporter us -> Text -> State -> us -> IO us
- Test.HUnitPlus.Reporting: [reporterFailure] :: Reporter us -> String -> State -> us -> IO us
+ Test.HUnitPlus.Reporting: [reporterFailure] :: Reporter us -> Text -> State -> us -> IO us
- Test.HUnitPlus.Reporting: [reporterSystemErr] :: Reporter us -> String -> State -> us -> IO us
+ Test.HUnitPlus.Reporting: [reporterSystemErr] :: Reporter us -> Text -> State -> us -> IO us
- Test.HUnitPlus.Reporting: [reporterSystemOut] :: Reporter us -> String -> State -> us -> IO us
+ Test.HUnitPlus.Reporting: [reporterSystemOut] :: Reporter us -> Text -> State -> us -> IO us
- Test.HUnitPlus.Reporting: [stName] :: State -> !String
+ Test.HUnitPlus.Reporting: [stName] :: State -> !Text
- Test.HUnitPlus.Reporting: [stOptions] :: State -> !(Map String String)
+ Test.HUnitPlus.Reporting: [stOptions] :: State -> !(HashMap Text Text)
- Test.HUnitPlus.Reporting: showPath :: Path -> String
+ Test.HUnitPlus.Reporting: showPath :: Path -> Text
- Test.HUnitPlus.Reporting: showQualName :: State -> String
+ Test.HUnitPlus.Reporting: showQualName :: State -> Text
- Test.HUnitPlus.Text: PutText :: (String -> st -> IO st) -> st -> PutText st
+ Test.HUnitPlus.Text: PutText :: (Text -> st -> IO st) -> st -> PutText st
- Test.HUnitPlus.Text: putTextToShowS :: PutText ShowS
+ Test.HUnitPlus.Text: putTextToShowS :: PutText (Text -> Text)
- Test.HUnitPlus.Text: showCounts :: Counts -> String
+ Test.HUnitPlus.Text: showCounts :: Counts -> Text
- Test.HUnitPlus.XML: errorElem :: String -> Node String String
+ Test.HUnitPlus.XML: errorElem :: Text -> Node Text Text
- Test.HUnitPlus.XML: failureElem :: String -> Node String String
+ Test.HUnitPlus.XML: failureElem :: Text -> Node Text Text
- Test.HUnitPlus.XML: propertiesElem :: [(String, String)] -> Node String String
+ Test.HUnitPlus.XML: propertiesElem :: [(Text, Text)] -> Node Text Text
- Test.HUnitPlus.XML: propertyElem :: (String, String) -> Node String String
+ Test.HUnitPlus.XML: propertyElem :: (Text, Text) -> Node Text Text
- Test.HUnitPlus.XML: skippedTestElem :: String -> String -> Node String String
+ Test.HUnitPlus.XML: skippedTestElem :: Text -> Text -> Node Text Text
- Test.HUnitPlus.XML: systemErrElem :: String -> Node String String
+ Test.HUnitPlus.XML: systemErrElem :: Text -> Node Text Text
- Test.HUnitPlus.XML: systemOutElem :: String -> Node String String
+ Test.HUnitPlus.XML: systemOutElem :: Text -> Node Text Text
- Test.HUnitPlus.XML: testSuiteElem :: String -> Map String String -> Word -> Word -> Word -> Word -> String -> UTCTime -> Double -> [Node String String] -> Node String String
+ Test.HUnitPlus.XML: testSuiteElem :: Text -> HashMap Text Text -> Word -> Word -> Word -> Word -> Text -> UTCTime -> Double -> [Node Text Text] -> Node Text Text
- Test.HUnitPlus.XML: testSuitesElem :: Double -> [Node String String] -> Node String String
+ Test.HUnitPlus.XML: testSuitesElem :: Double -> [Node Text Text] -> Node Text Text
- Test.HUnitPlus.XML: testcaseElem :: String -> String -> Word -> Double -> [Node String String] -> Node String String
+ Test.HUnitPlus.XML: testcaseElem :: Text -> Text -> Word -> Double -> [Node Text Text] -> Node Text Text
- Test.HUnitPlus.XML: xmlReporter :: Reporter [[Node String String]]
+ Test.HUnitPlus.XML: xmlReporter :: Reporter [[Node Text Text]]
Files
- HUnit-Plus.cabal +19/−7
- src/Test/HUnitPlus/Base.hs +123/−94
- src/Test/HUnitPlus/Execution.hs +132/−70
- src/Test/HUnitPlus/Filter.hs +263/−120
- src/Test/HUnitPlus/Main.hs +19/−16
- src/Test/HUnitPlus/Reporting.hs +39/−30
- src/Test/HUnitPlus/Text.hs +73/−50
- src/Test/HUnitPlus/XML.hs +44/−41
- test/Tests.hs +7/−0
- test/Tests/Test.hs +7/−0
- test/Tests/Test/HUnitPlus.hs +15/−0
- test/Tests/Test/HUnitPlus/Base.hs +588/−0
- test/Tests/Test/HUnitPlus/Execution.hs +667/−0
- test/Tests/Test/HUnitPlus/Filter.hs +1006/−0
- test/Tests/Test/HUnitPlus/Main.hs +211/−0
- test/Tests/Test/HUnitPlus/ReporterUtils.hs +194/−0
- test/Tests/Test/HUnitPlus/Reporting.hs +115/−0
- test/Tests/Test/HUnitPlus/Text.hs +7/−0
- test/Tests/Test/HUnitPlus/XML.hs +348/−0
HUnit-Plus.cabal view
@@ -1,6 +1,6 @@ Name: HUnit-Plus Category: Testing, Test-Version: 1.1.0+Version: 2.0.0 License: BSD3 License-File: LICENSE Author: Eric McCorkle@@ -34,18 +34,30 @@ default-language: Haskell2010 type: exitcode-stdio-1.0 Main-Is: RunTests.hs- hs-source-dirs: src test+ hs-source-dirs: test build-depends: base >= 4.8 && < 5, Cabal >= 1.16.0, hexpat, timeit,- cmdargs, hashable, containers, time, hostname,- parsec, bytestring, directory- ghc-options: -fhpc+ cmdargs, hashable, unordered-containers, time,+ hostname, parsec, text, directory, HUnit-Plus,+ bytestring+ other-modules: Tests+ Tests.Test+ Tests.Test.HUnitPlus+ Tests.Test.HUnitPlus.Base+ Tests.Test.HUnitPlus.Execution+ Tests.Test.HUnitPlus.Filter+ Tests.Test.HUnitPlus.Main+ Tests.Test.HUnitPlus.ReporterUtils+ Tests.Test.HUnitPlus.Reporting+ Tests.Test.HUnitPlus.Text+ Tests.Test.HUnitPlus.XML + Library default-language: Haskell2010 hs-source-dirs: src build-depends: base >= 4.8 && < 5, Cabal >= 1.16.0, hexpat, timeit,- cmdargs, hashable, containers, time, hostname,- bytestring, parsec+ cmdargs, hashable, unordered-containers, time,+ hostname, text, parsec, bytestring exposed-modules: Test.HUnitPlus.Base Test.HUnitPlus.Execution Test.HUnitPlus.Filter
src/Test/HUnitPlus/Base.hs view
@@ -1,5 +1,5 @@ {-# OPTIONS_GHC -Wall -Werror -funbox-strict-fields #-}-{-# LANGUAGE FlexibleInstances, DeriveDataTypeable #-}+{-# LANGUAGE FlexibleInstances, DeriveDataTypeable, OverloadedStrings #-} -- | Basic definitions for the HUnitPlus library. --@@ -67,6 +67,7 @@ (@?), -- * Low-level Test Functions+ heartbeat, executeTest, logSyserr, logSysout,@@ -89,13 +90,15 @@ import System.TimeIt import Test.HUnitPlus.Reporting +import qualified Data.Text as Strict+ -- | An 'Exception' used to abort test execution immediately. data TestException = TestException { -- | Whether this is a failure or an error. teError :: !Bool, -- | The failure (or error) message.- teMsg :: !String+ teMsg :: !Strict.Text } deriving (Show, Typeable) instance Exception TestException@@ -107,13 +110,14 @@ -- | Current counts of assertions, tried, failed, and errors. tiAsserts :: !Word, -- | Events that have been logged- tiEvents :: ![(Word, String)],+ tiEvents :: ![(Word, Strict.Text)], -- | Whether or not the result of the test computation is already -- reflected here. This is used to differentiate between black -- box test and tests we've built with these tools. tiIgnoreResult :: !Bool,- -- | String to attach to every failure message as a prefix.- tiPrefix :: !String+ -- | 'Text' to attach to every failure message as a prefix.+ tiPrefix :: !Strict.Text,+ tiHeartbeat :: !Bool } errorCode :: Word@@ -130,7 +134,10 @@ {-# NOINLINE testinfo #-} testinfo :: IORef TestInfo-testinfo = unsafePerformIO $! newIORef undefined+testinfo = unsafePerformIO $! newIORef TestInfo { tiAsserts = 0, tiEvents = [],+ tiIgnoreResult = False,+ tiHeartbeat = False,+ tiPrefix = "" } -- | Does the actual work of executing a test. This maintains the -- necessary bookkeeping recording assertions and failures, It also@@ -156,20 +163,21 @@ Just TestException { teError = True, teMsg = msg } -> do logError msg- return (Finished (Error msg))+ return (Finished (Error (Strict.unpack msg))) Just TestException { teError = False, teMsg = msg } -> do logFailure msg- return (Finished (Fail msg))+ return (Finished (Fail (Strict.unpack msg))) Nothing -> do TestInfo { tiIgnoreResult = ignoreRes } <- readIORef testinfo if ignoreRes then do- logError ("Uncaught exception in test: " ++ show ex)+ logError (Strict.concat ["Uncaught exception in test: ",+ Strict.pack (show ex)]) return (Finished (Error ("Uncaught exception in test: " ++ show ex)))- else do+ else return (Finished (Error ("Uncaught exception in test: " ++ show ex))) @@ -179,7 +187,7 @@ case progress of Progress msg nextAction -> do- usNext <- reportCaseProgress msg ss us+ usNext <- reportCaseProgress (Strict.pack msg) ss us finishTestCase (time + inctime) usNext nextAction Finished res -> return (res, us, time + inctime) in do@@ -227,32 +235,32 @@ case result of Error msg | not ignoreRes -> do- finalUs <- reportError msg ss eventsUs- return $! (ss { stCounts =- c { cAsserts = asserts + fromIntegral currAsserts,- cCaseAsserts = fromIntegral currAsserts,- cErrors = errors + 1 } },- finalUs)+ finalUs <- reportError (Strict.pack msg) ss eventsUs+ return (ss { stCounts =+ c { cAsserts = asserts + fromIntegral currAsserts,+ cCaseAsserts = fromIntegral currAsserts,+ cErrors = errors + 1 } },+ finalUs) Fail msg | not ignoreRes -> do- finalUs <- reportFailure msg ss eventsUs- return $! (ss { stCounts =- c { cAsserts = asserts + fromIntegral currAsserts,- cCaseAsserts = fromIntegral currAsserts,- cFailures = failures + 1 } },- finalUs)- _ -> return $! (ss { stCounts =- c { cAsserts = asserts + fromIntegral currAsserts,- cCaseAsserts = fromIntegral currAsserts,- cFailures =- if hasFailure- then failures + 1- else failures,- cErrors =- if hasError- then errors + 1- else errors } },- eventsUs)+ finalUs <- reportFailure (Strict.pack msg) ss eventsUs+ return (ss { stCounts =+ c { cAsserts = asserts + fromIntegral currAsserts,+ cCaseAsserts = fromIntegral currAsserts,+ cFailures = failures + 1 } },+ finalUs)+ _ -> return (ss { stCounts =+ c { cAsserts = asserts + fromIntegral currAsserts,+ cCaseAsserts = fromIntegral currAsserts,+ cFailures =+ if hasFailure+ then failures + 1+ else failures,+ cErrors =+ if hasError+ then errors + 1+ else errors } },+ eventsUs) -- | Indicate that the result of a test is already reflected in the testinfo. ignoreResult :: IO ()@@ -262,27 +270,34 @@ resetTestInfo = writeIORef testinfo TestInfo { tiAsserts = 0, tiEvents = [], tiIgnoreResult = False,+ tiHeartbeat = False, tiPrefix = "" } +-- | Indicate test progress.+heartbeat :: IO ()+heartbeat = modifyIORef testinfo (\t -> t { tiHeartbeat = True })+ -- | Execute the given computation with a message prefix.-withPrefix :: String -> IO () -> IO ()+withPrefix :: Strict.Text -> IO () -> IO () withPrefix prefix c = do t @ TestInfo { tiPrefix = oldprefix } <- readIORef testinfo- writeIORef testinfo t { tiPrefix = prefix ++ oldprefix }+ writeIORef testinfo t { tiPrefix = Strict.concat [prefix, oldprefix] } c modifyIORef testinfo (\t' -> t' { tiPrefix = oldprefix }) -- | Record sysout output.-logSysout :: String -> IO ()+logSysout :: Strict.Text -> IO () logSysout msg =- modifyIORef testinfo (\t -> t { tiEvents = (sysOutCode, tiPrefix t ++ msg) :+ modifyIORef testinfo (\t -> t { tiEvents = (sysOutCode,+ Strict.concat [tiPrefix t, msg]) : tiEvents t }) -- | Record sysout output.-logSyserr :: String -> IO ()+logSyserr :: Strict.Text -> IO () logSyserr msg =- modifyIORef testinfo (\t -> t { tiEvents = (sysErrCode, tiPrefix t ++ msg) :+ modifyIORef testinfo (\t -> t { tiEvents = (sysErrCode,+ Strict.concat [tiPrefix t, msg]) : tiEvents t }) -- | Record that one assertion has been checked.@@ -290,34 +305,36 @@ logAssert = modifyIORef testinfo (\t -> t { tiAsserts = tiAsserts t + 1 }) -- | Record an error, along with a message.-logError :: String -> IO ()+logError :: Strict.Text -> IO () logError msg =- modifyIORef testinfo (\t -> t { tiEvents = (errorCode, tiPrefix t ++ msg) :+ modifyIORef testinfo (\t -> t { tiEvents = (errorCode,+ Strict.concat [tiPrefix t, msg]) : tiEvents t }) -- | Record a failure, along with a message.-logFailure :: String -> IO ()+logFailure :: Strict.Text -> IO () logFailure msg =- modifyIORef testinfo (\t -> t { tiEvents = (failureCode, tiPrefix t ++ msg) :+ modifyIORef testinfo (\t -> t { tiEvents = (failureCode,+ Strict.concat [tiPrefix t, msg]) : tiEvents t }) -- | Get a combined failure message, if there is one.-getFailures :: IO (Maybe String)+getFailures :: IO (Maybe Strict.Text) getFailures = do TestInfo { tiEvents = events } <- readIORef testinfo case map snd (filter ((== failureCode) . fst) events) of [] -> return $ Nothing- fails -> return $ (Just (concat (reverse fails)))+ fails -> return $ Just (Strict.concat (reverse fails)) -- | Get a combined failure message, if there is one.-getErrors :: IO (Maybe String)+getErrors :: IO (Maybe Strict.Text) getErrors = do TestInfo { tiEvents = events } <- readIORef testinfo case map snd (filter ((== errorCode) . fst) events) of [] -> return $ Nothing- errors -> return $ (Just (concat (reverse errors)))+ errors -> return $ Just (Strict.concat (reverse errors)) -- Assertion Definition -- ====================@@ -330,7 +347,7 @@ -- | Unconditionally signal that a failure has occurred. This will -- not stop execution, but will record the failure, resulting in a -- failed test.-assertFailure :: String+assertFailure :: Strict.Text -- ^ The failure message -> Assertion assertFailure msg = logAssert >> logFailure msg@@ -341,17 +358,17 @@ assertSuccess = logAssert -- | Signal than an error has occurred and stop the test immediately.-abortError :: String -> Assertion+abortError :: Strict.Text -> Assertion abortError msg = throw TestException { teError = True, teMsg = msg } -- | Signal that a failure has occurred and stop the test immediately. -- Note that if an error has been logged already, the test will be -- reported as an error.-abortFailure :: String -> Assertion+abortFailure :: Strict.Text -> Assertion abortFailure msg = throw TestException { teError = False, teMsg = msg } -- | Asserts that the specified condition holds.-assertBool :: String+assertBool :: Strict.Text -- ^ The message that is displayed if the assertion fails -> Bool -- ^ The condition@@ -361,38 +378,42 @@ -- | Signals an assertion failure if a non-empty message (i.e., a message -- other than @\"\"@) is passed. assertString :: String- -- ^ The message that is displayed with the assertion failure + -- ^ The message that is displayed with the assertion failure -> Assertion assertString = assertStringWithPrefix "" -- | Signals an assertion failure if a non-empty message (i.e., a -- message other than @\"\"@) is passed. Allows a prefix to be -- supplied for the assertion failure message.-assertStringWithPrefix :: String+assertStringWithPrefix :: Strict.Text -- ^ Prefix to attach to the string if not null -> String -- ^ String to assert is null -> Assertion-assertStringWithPrefix prefix s = assertBool (prefix ++ s) (null s)+assertStringWithPrefix prefix s =+ assertBool (Strict.concat [prefix, Strict.pack s]) (null s) -- | Asserts that the specified actual value is equal to the expected value.--- The output message will contain the prefix, the expected value, and the +-- The output message will contain the prefix, the expected value, and the -- actual value.--- +-- -- If the prefix is the empty string (i.e., @\"\"@), then the prefix is omitted -- and only the expected and actual values are output. assertEqual :: (Eq a, Show a)- => String+ => Strict.Text -- ^ The message prefix -> a- -- ^ The expected value + -- ^ The expected value -> a -- ^ The actual value -> Assertion assertEqual preface expected actual = let- msg = (if null preface then "" else preface ++ "\n") ++- "expected: " ++ show expected ++ "\nbut got: " ++ show actual+ msg = Strict.concat [if Strict.null preface+ then ""+ else Strict.concat [preface, "\n"],+ "expected: ", Strict.pack (show expected),+ "\nbut got: ", Strict.pack (show actual)] in assertBool msg (actual == expected) @@ -405,11 +426,13 @@ -> Assertion assertThrowsExact ex comp = let- runComp = comp >> assertFailure ("expected exception " ++ show ex ++- " but computation finished normally")+ normalmsg = Strict.concat ["expected exception ", Strict.pack (show ex),+ " but computation finished normally"]+ runComp = comp >> assertFailure normalmsg handler ex' = let- msg = "expected exception " ++ show ex ++ " but got " ++ show ex'+ msg = Strict.concat ["expected exception ", Strict.pack (show ex),+ " but got ", Strict.pack (show ex')] in if ex == ex' then assertSuccess@@ -438,12 +461,12 @@ -- ---------------------------- -- | Allows the extension of the assertion mechanism.--- +-- -- Since an 'Assertion' can be a sequence of @Assertion@s and @IO@ -- actions, there is a fair amount of flexibility of what can be -- achieved. As a rule, the resulting 'Assertion' should not assert -- multiple, independent conditions.--- +-- -- If more complex arrangements of assertions are needed, 'Test's and -- 'Testable' should be used. class Assertable t where@@ -458,21 +481,23 @@ assertWithMsg _ = return instance Assertable Bool where- assertWithMsg msg = assertBool msg+ assertWithMsg = assertBool . Strict.pack instance Assertable Result where assertWithMsg _ Pass = assertSuccess- assertWithMsg "" (Error errstr) = logError errstr- assertWithMsg prefix (Error errstr) = logError (prefix ++ errstr)- assertWithMsg "" (Fail failstr) = assertFailure failstr- assertWithMsg prefix (Fail failstr) = assertFailure (prefix ++ failstr)+ assertWithMsg "" (Error errstr) = logError (Strict.pack errstr)+ assertWithMsg prefix (Error errstr) =+ logError (Strict.pack (prefix ++ errstr))+ assertWithMsg "" (Fail failstr) = assertFailure (Strict.pack failstr)+ assertWithMsg prefix (Fail failstr) =+ assertFailure (Strict.pack (prefix ++ failstr)) instance Assertable Progress where assertWithMsg msg (Progress _ cont) = assertWithMsg msg cont assertWithMsg msg (Finished res) = assertWithMsg msg res instance (ListAssertable t) => Assertable [t] where- assertWithMsg msg = listAssert msg+ assertWithMsg = listAssert instance (Assertable t) => Assertable (IO t) where assertWithMsg msg t = t >>= assertWithMsg msg@@ -482,10 +507,10 @@ listAssert :: String -> [t] -> Assertion instance ListAssertable Char where- listAssert msg = assertStringWithPrefix msg+ listAssert = assertStringWithPrefix . Strict.pack instance ListAssertable Assertion where- listAssert msg asserts = withPrefix msg (sequence_ asserts)+ listAssert msg asserts = withPrefix (Strict.pack msg) (sequence_ asserts) -- Assertion Construction Operators -- --------------------------------@@ -536,12 +561,12 @@ data TestSuite = TestSuite { -- | The name of the test suite.- suiteName :: !String,+ suiteName :: !Strict.Text, -- | Whether or not to run the tests concurrently. suiteConcurrently :: !Bool, -- | A list of all options used by this suite, and the default -- values for those options.- suiteOptions :: ![(String, String)],+ suiteOptions :: ![(Strict.Text, Strict.Text)], -- | The tests in the suite. suiteTests :: ![Test] }@@ -553,7 +578,7 @@ -- ^ The tests in the suite. -> TestSuite testSuite suitename testlist =- TestSuite { suiteName = suitename, suiteConcurrently = True,+ TestSuite { suiteName = Strict.pack suitename, suiteConcurrently = True, suiteOptions = [], suiteTests = testlist } -- Overloaded `test` Function@@ -580,8 +605,8 @@ failures <- getFailures case failures of Nothing -> return $ (Finished Pass)- Just failstr -> return $ (Finished (Fail failstr))- Just errstr -> return $ (Finished (Error errstr))+ Just failstr -> return $ (Finished (Fail (Strict.unpack failstr)))+ Just errstr -> return $ (Finished (Error (Strict.unpack errstr))) -- | Provides a way to convert data into a @Test@ or set of @Test@. class Testable t where@@ -590,15 +615,15 @@ -- | Create a test with a given name and no tags from a @Testable@ value testName :: String -> t -> Test- testName testname t = testNameTags testname [] t+ testName testname = testNameTags testname [] -- | Create a test with a given name and no tags from a @Testable@ value testTags :: [String] -> t -> Test- testTags tagset t = testNameTags syntheticName tagset t+ testTags = testNameTags syntheticName -- | Create a test with a synthetic name and no tags from a @Testable@ value- test :: Testable t => t -> Test- test t = testNameTags syntheticName [] t+ test :: t -> Test+ test = testNameTags syntheticName [] instance Testable Test where testNameTags newname newtags g @ Group { groupTests = testlist } =@@ -624,9 +649,13 @@ instance (Assertable t) => Testable (IO t) where testNameTags testname testtags t =- Test TestInstance { name = testname, tags = testtags,- run = wrapTest (t >>= assert),- options = [], setOption = undefined }+ let+ unrecognized optname _ = Left ("Unrecognized option " ++ optname)+ out = TestInstance { name = testname, tags = testtags,+ run = wrapTest (t >>= assert),+ options = [], setOption = unrecognized }+ in+ Test out instance (Testable t) => Testable [t] where testNameTags testname testtags ts =@@ -639,7 +668,7 @@ infix 1 ~?, ~=?, ~?= infixr 0 ~: --- | Creates a test case resulting from asserting the condition obtained +-- | Creates a test case resulting from asserting the condition obtained -- from the specified 'AssertionPredicable'. (~?) :: (Assertable t) => t@@ -649,31 +678,31 @@ -> Test predi ~? msg = test (predi @? msg) --- | Shorthand for a test case that asserts equality (with the expected +-- | Shorthand for a test case that asserts equality (with the expected -- value on the left-hand side, and the actual value on the right-hand -- side). (~=?) :: (Eq a, Show a) => a- -- ^ The expected value + -- ^ The expected value -> a -- ^ The actual value -> Test expected ~=? actual = test (expected @=? actual) --- | Shorthand for a test case that asserts equality (with the actual +-- | Shorthand for a test case that asserts equality (with the actual -- value on the left-hand side, and the expected value on the right-hand -- side). (~?=) :: (Eq a, Show a) => a -- ^ The actual value -> a- -- ^ The expected value + -- ^ The expected value -> Test actual ~?= expected = test (actual @?= expected) --- | Creates a test from the specified 'Testable', with the specified +-- | Creates a test from the specified 'Testable', with the specified -- label attached to it.--- +-- -- Since 'Test' is @Testable@, this can be used as a shorthand way of -- attaching a 'TestLabel' to one or more tests. (~:) :: (Testable t) => String -> t -> Test
src/Test/HUnitPlus/Execution.hs view
@@ -1,4 +1,5 @@ {-# OPTIONS_GHC -Wall -Werror #-}+{-# LANGUAGE OverloadedStrings #-} -- | Functions for executing test cases, test paths, and test suites. -- These functions are provided for the sake of convenience and@@ -14,15 +15,20 @@ import Control.Monad (unless, foldM) import Distribution.TestSuite-import Data.Map(Map)+import Data.HashMap.Strict(HashMap)+import Data.Time+import Data.Version+import Network.HostName import Prelude hiding (elem)+import System.Info import System.TimeIt import Test.HUnitPlus.Base import Test.HUnitPlus.Filter import Test.HUnitPlus.Reporting -import qualified Data.Set as Set-import qualified Data.Map as Map+import qualified Data.HashSet as HashSet+import qualified Data.HashMap.Strict as HashMap+import qualified Data.Text as Strict -- | Execute an individual test case. performTestCase :: Reporter us@@ -50,26 +56,23 @@ -- Add the name to the state we use to run the tests -- Update the state before running- ssWithName = ss { stName = testname, stCounts = c { cTried = tried + 1,- cCases = cases + 1 } }+ ssWithName = ss { stName = Strict.pack testname,+ stCounts = c { cTried = tried + 1, cCases = cases + 1 } } -- Fold function for applying options- applyOptions (us, ti) OptionDescr { optionName = optname,- optionDefault = def } =+ applyOptions (us, ti) OptionDescr { optionName = optname } = let setresult :: Either String TestInstance setresult =- case Map.lookup optname optmap of- Just optval -> setopt optname optval- Nothing -> case def of- Just optval -> setopt optname optval- Nothing -> Right ti+ case HashMap.lookup (Strict.pack optname) optmap of+ Just optval -> setopt optname (Strict.unpack optval)+ Nothing -> Right ti in case setresult of Left errmsg -> do- newUs <- reportError errmsg ssWithName us- return $! (newUs, ti)- Right newTi -> return $! (us, newTi)+ newUs <- reportError (Strict.pack errmsg) ssWithName us+ return (newUs, ti)+ Right newTi -> return (us, newTi) in do -- Get all the rest of the information from the resulting test instance (usOpts, TestInstance { run = runTest }) <-@@ -81,7 +84,7 @@ -- Call the reporters end case function usEnded <- reportEndCase time ssFinal usFinal -- Restore the old name before returning- return $ (ssFinal { stName = oldname }, usEnded)+ return (ssFinal { stName = oldname }, usEnded) -- | Log a skipped test case. skipTestCase :: Reporter us@@ -100,10 +103,10 @@ TestInstance { name = testname } = let ss' = ss { stCounts = c { cSkipped = skipped + 1, cCases = cases + 1 },- stName = testname }+ stName = Strict.pack testname } in do us' <- reportSkipCase ss' us- return $! (ss' { stName = oldname }, us')+ return (ss' { stName = oldname }, us') -- | Execute a given test (which may be a group), with the specified -- selector and report generators. Only tests which match the@@ -119,7 +122,7 @@ -> Test -- ^ The test to be executed. -> IO (State, us)-performTest rep initSelector initState initialUs initialTest =+performTest rep initSelector initialState initialUs initialTest = let -- The recursive worker function that actually runs all the tests --@@ -129,17 +132,18 @@ -- -- We also have to keep a set of tags by which we're filtering. -- The empty tag set means we don't actually filter at all.- performTest' Selector { selectorInners = inners, selectorTags = currtags }+ performTest' s @ Selector { selectorInners = inners,+ selectorTags = currtags } ss us Group { groupTests = testlist, groupName = gname } = let -- Build the new selector selector' = -- Try looking up the group in the inners- case Map.lookup gname inners of+ case HashMap.lookup (Strict.pack gname) inners of -- If we don't find anything, we can only keep executing -- if our tag state allows it.- Nothing -> Selector { selectorInners = Map.empty,- selectorTags = currtags }+ Nothing -> s { selectorInners = HashMap.empty,+ selectorTags = currtags } -- Otherwise, combine the inner's tag state with ours and -- carry on. Just inner @ Selector { selectorTags = innertags } ->@@ -147,14 +151,14 @@ -- Update the path for running the group's tests oldpath = stPath ss- ssWithPath = ss { stPath = Label gname : oldpath }+ ssWithPath = ss { stPath = Label (Strict.pack gname) : oldpath } - foldfun (ss', us') t = performTest' selector' ss' us' t+ foldfun (ss', us') = performTest' selector' ss' us' in do -- Run the tests with the updated path (ssAfter, usAfter) <- foldM foldfun (ssWithPath, us) testlist -- Return the state, reset to the old path- return $! (ssAfter { stPath = oldpath }, usAfter)+ return (ssAfter { stPath = oldpath }, usAfter) performTest' Selector { selectorInners = inners, selectorTags = currtags } ss us (Test t @ TestInstance { name = testname, tags = testtags }) =@@ -162,7 +166,7 @@ -- Get the final tag state finaltags = -- Try looking up the group in the inners- case Map.lookup testname inners of+ case HashMap.lookup (Strict.pack testname) inners of -- If we don't find anything, we can only keep executing -- if our tag state allows it. Nothing -> currtags@@ -175,8 +179,9 @@ case finaltags of Nothing -> False Just set- | set == Set.empty -> True- | otherwise -> any (\tag -> Set.member tag set) testtags+ | HashSet.null set -> True+ | otherwise -> any (\tag -> HashSet.member tag set)+ (map Strict.pack testtags) in if canExecute then performTestCase rep ss us t@@ -185,45 +190,100 @@ us (ExtraOptions newopts inner) = performTest' selector ss { stOptionDescs = descs ++ newopts } us inner in do- (ss', us') <- performTest' initSelector initState initialUs initialTest+ (ss', us') <- performTest' initSelector initialState initialUs initialTest unless (null (stPath ss')) $ error "performTest: Final path is nonnull"- return $! (ss', us')+ return (ss', us') +-- | Run a test suite with a given set of options.+performTestSuiteInstance :: Reporter us+ -- ^ Report generator to use for running the+ -- test suite.+ -> OptionMap+ -- ^ The options for this instance.+ -> Selector+ -- ^ The selector to use.+ -> State+ -- ^ HUnit-Plus internal state.+ -> us+ -- ^ State for the report generator.+ -> TestSuite+ -- ^ Test suite to be run.+ -> IO (State, us)+performTestSuiteInstance rep @ Reporter { reporterStartSuite = reportStartSuite,+ reporterEndSuite = reportEndSuite }+ instopts selector+ st @ State { stOptions = stopts } initialUs+ TestSuite { suiteName = sname, suiteTests = testlist,+ suiteOptions = suiteOpts } =+ let+ makestate timestamp =+ let+ timestr = formatTime defaultTimeLocale "%c" timestamp+ withtime = HashMap.insert "timestamp" (Strict.pack timestr) stopts+ -- Don't allow people to override the system information+ withInstOpts = HashMap.union withtime instopts+ unioned = HashMap.union withInstOpts (HashMap.fromList suiteOpts)+ in+ return st { stOptions = unioned, stName = sname }++ foldfun (c, us) = performTest rep selector c us+ in do+ timestamp <- getCurrentTime+ state <- makestate timestamp+ startedUs <- reportStartSuite state initialUs+ (time, (finishedState @ State { stCounts = counts }, finishedUs)) <-+ timeItT (foldM foldfun (state, startedUs) testlist)+ endedUs <- reportEndSuite time finishedState finishedUs+ return (finishedState { stCounts = counts { cCaseAsserts = 0 } },+ endedUs)+ -- | Decide whether to execute a test suite based on a map from suite -- names to selectors. If the map contains a selector for the test -- suite, execute all tests matching the selector, and log the rest as -- skipped. If the map does not contain a selector, do not execute -- the suite, and do /not/ log its tests as skipped.+performTestSuiteInternal :: Reporter us+ -- ^ Report generator to use for running the+ -- test suite.+ -> HashMap Strict.Text (HashMap OptionMap Selector)+ -- ^ The map containing selectors for each suite.+ -> State+ -- ^ HUnit-Plus internal state.+ -> us+ -- ^ State for the report generator.+ -> TestSuite+ -- ^ Test suite to be run.+ -> IO (State, us)+performTestSuiteInternal rep filters initialSs initialUs+ suite @ TestSuite { suiteName = sname } =+ case HashMap.lookup sname filters of+ Just optmap ->+ let+ foldfun (ss, us) (opts, selector) =+ performTestSuiteInstance rep opts selector ss us suite+ in+ foldM foldfun (initialSs, initialUs) (HashMap.toList optmap)+ _ -> return (initialSs, initialUs)++initState :: State+initState = State { stCounts = zeroCounts, stName = "",+ stPath = [], stOptions = HashMap.empty,+ stOptionDescs = [] }+ performTestSuite :: Reporter us -- ^ Report generator to use for running the test suite.- -> Map String Selector+ -> HashMap Strict.Text (HashMap OptionMap Selector) -- ^ The map containing selectors for each suite. -> us -- ^ State for the report generator. -> TestSuite -- ^ Test suite to be run. -> IO (Counts, us)-performTestSuite rep @ Reporter { reporterStartSuite = reportStartSuite,- reporterEndSuite = reportEndSuite }- filters initialUs- TestSuite { suiteName = sname, suiteTests = testlist,- suiteOptions = suiteOpts } =- case Map.lookup sname filters of- Just selector ->- let- initState = State { stCounts = zeroCounts, stName = sname,- stPath = [], stOptions = Map.fromList suiteOpts,- stOptionDescs = [] }-- foldfun (c, us) testcase = performTest rep selector c us testcase- in do- startedUs <- reportStartSuite initState initialUs- (time, (finishedState, finishedUs)) <-- timeItT (foldM foldfun (initState, startedUs) testlist)- endedUs <- reportEndSuite time finishedState finishedUs- return $! (stCounts finishedState, endedUs)- _ ->- return $! (zeroCounts, initialUs)+performTestSuite rep filters us suite =+ do+ (State { stCounts = out }, _) <-+ performTestSuiteInternal rep filters initState us suite+ return (out, us) -- | Top-level function for a test run. Given a set of suites and a -- map from suite names to selectors, execute all suites that have@@ -233,7 +293,7 @@ -- their tests will /not/ be logged as skipped. performTestSuites :: Reporter us -- ^ Report generator to use for running the test suite.- -> Map String Selector+ -> HashMap Strict.Text (HashMap OptionMap Selector) -- ^ The processed filter to use. -> [TestSuite] -- ^ Test suite to be run.@@ -242,24 +302,26 @@ reporterEnd = reportEnd } filters suites = let- combineCounts Counts { cCases = cases1, cTried = tried1,- cErrors = errors1, cFailures = failures1,- cAsserts = asserts1, cSkipped = skipped1 }- Counts { cCases = cases2, cTried = tried2,- cErrors = errors2, cFailures = failures2,- cAsserts = asserts2, cSkipped = skipped2 } =- Counts { cCases = cases1 + cases2, cTried = tried1 + tried2,- cErrors = errors1 + errors2, cFailures = failures1 + failures2,- cAsserts = asserts1 + asserts2, cSkipped = skipped1 + skipped2,- cCaseAsserts = 0 }+ foldfun (accumState, accumUs) =+ performTestSuiteInternal rep filters accumState accumUs - foldfun (accumCounts, accumUs) suite =+ startstate = do- (suiteCounts, suiteUs) <- performTestSuite rep filters accumUs suite- return $! (combineCounts accumCounts suiteCounts, suiteUs)+ hostname <- getHostName+ return initState {+ stOptions = HashMap.fromList+ [("hostname", Strict.pack hostname),+ ("os", Strict.pack os),+ ("arch", Strict.pack arch),+ ("compiler-name",+ Strict.pack compilerName),+ ("compiler-version",+ Strict.pack (showVersion compilerVersion))]+ } in do initialUs <- reportStart- (time, (finishedCounts, finishedUs)) <-- timeItT (foldM foldfun (zeroCounts, initialUs) suites)+ st <- startstate+ (time, (State { stCounts = finishedCounts }, finishedUs)) <-+ timeItT (foldM foldfun (st, initialUs) suites) endedUs <- reportEnd time finishedCounts finishedUs- return $! (finishedCounts, endedUs)+ return (finishedCounts, endedUs)
src/Test/HUnitPlus/Filter.hs view
@@ -1,3 +1,4 @@+{-# LANGUAGE OverloadedStrings #-} {-# OPTIONS_GHC -Wall -Werror -funbox-strict-fields #-} -- | Sets HUnit-Plus tests can be specified using 'Filter's. These@@ -16,9 +17,10 @@ -- -- The format for filters is as follows: ----- \[/suite/\]\[/path/\]\[/tags/\]+-- > [suite][path][tags][options] ----- Where at least one of the /suite/, /path/, or /tags/ elements are present+-- Where at least one of the /suite/, /path/, /tags/, or /options/+-- elements are present -- -- The /suite/ element is a comma-separated list of suite names (alphanumeric, -- no spaces), enclosed in brackets ('[' ']').@@ -26,9 +28,12 @@ -- The /path/ element is a series of path elements (alphanumeric, no -- spaces), separated by dots ('.'). ----- The /tags/ element consists of a '\@' character, followed by a+-- The /tags/ element consists of a '@' character, followed by a -- comma-separated list of tag names (alphanumeric, no spaces). --+-- The /options/ element consists of a '?' character, followed by a+-- comma-separated list of \"name\"=\"value\" bindings.+-- -- The following are examples of textual filters, and their meanings: -- -- * @first.second.third@: Run all tests starting with the path@@ -55,12 +60,25 @@ -- 'whitebox' suite beginning with the path @network.protocol@ that -- have the 'security' tag. --+-- * @first.second.third?\"var\"=\"val\"@: Run all tests starting with the path+-- @first.second.third@, with the option \"var\" set to \"val\".+--+-- * @[unit]?\"timeout\"=\"5\"@: Run all tests in the suite 'unit'+-- with the option \"timeout\" set to \"\5".+--+-- * @[system]?\"timeout\"=\"5\",\"threads\"=\"4\"@: Run all tests in+-- the suite 'system' with the option 'timeout' set to '5' and+-- 'threads' set to '4'.+--+-- * ?\"user\"=\"jeffk\": Run all tests with the 'user' option set to 'jeffk'.+-- -- The most common use case of filters is to select a single failing -- test to run, as part of fixing it. In this case, a single filter -- consisting of the path to the test will have this effect. module Test.HUnitPlus.Filter( Selector(..), Filter(..),+ OptionMap, combineTags, passFilter, allSelector,@@ -74,16 +92,23 @@ import Control.Exception import Data.Foldable(foldl) import Data.Either-import Data.Map(Map)+import Data.Hashable+import Data.HashMap.Strict(HashMap) import Data.Maybe-import Data.Set(Set)+import Data.HashSet(HashSet)+import Data.List(sort) import Prelude hiding (foldl, elem) import System.IO.Error-import Text.ParserCombinators.Parsec hiding (try)+import Text.Parsec hiding (try)+import Text.Parsec.Text -import qualified Data.Set as Set-import qualified Data.Map as Map+import qualified Data.HashSet as HashSet+import qualified Data.HashMap.Strict as HashMap+import qualified Data.Text as Strict+import qualified Data.Text.IO as Strict +type OptionMap = HashMap Strict.Text Strict.Text+ -- | A tree-like structure that represents a set of tests within a -- given suite. data Selector =@@ -92,14 +117,14 @@ -- path element contains the @Selector@ to be used for that -- group (or test). An empty map actually means 'select all -- tests'.- selectorInners :: Map String Selector,+ selectorInners :: HashMap Strict.Text Selector, -- | Tags by which to filter all tests. The empty set actually -- means 'run all tests regardless of tags'. 'Nothing' means -- that all tests will be skipped (though this will be -- overridden by any @Selector@s in @selectorInners@.- selectorTags :: !(Maybe (Set String))+ selectorTags :: !(Maybe (HashSet Strict.Text)) }- deriving (Eq, Ord, Show)+ deriving (Eq, Show) -- | Specifies zero or more test suites, to which the given 'Selector' -- is then applied. If no test suites are specified, then the@@ -108,75 +133,126 @@ Filter { -- | The test suites to which the 'Selector' applies. The empty -- set actually means 'all suites'.- filterSuites :: !(Set String),+ filterSuites :: !(HashSet Strict.Text), -- | The 'Selector' to apply.- filterSelector :: !Selector+ filterSelector :: !(HashSet Selector),+ filterOptions :: !(HashMap Strict.Text Strict.Text) }- deriving (Ord, Eq, Show)+ deriving (Eq, Show) +instance Ord Selector where+ compare Selector { selectorInners = inners1, selectorTags = Just tags1 }+ Selector { selectorInners = inners2, selectorTags = Just tags2 } =+ let+ sortedtags1 = sort (HashSet.toList tags1)+ sortedtags2 = sort (HashSet.toList tags2)+ sortedinners1 = sort (HashMap.toList inners1)+ sortedinners2 = sort (HashMap.toList inners2)+ in+ case compare sortedtags1 sortedtags2 of+ EQ -> compare sortedinners1 sortedinners2+ out -> out+ compare Selector { selectorTags = Nothing }+ Selector { selectorTags = Just _ } = LT+ compare Selector { selectorTags = Just _ }+ Selector { selectorTags = Nothing } = GT+ compare Selector { selectorInners = inners1, selectorTags = Nothing }+ Selector { selectorInners = inners2, selectorTags = Nothing } =+ let+ sortedinners1 = sort (HashMap.toList inners1)+ sortedinners2 = sort (HashMap.toList inners2)+ in+ compare sortedinners1 sortedinners2++instance Hashable Selector where+ hashWithSalt s Selector { selectorInners = inners,+ selectorTags = Just tags } =+ let+ sortedtags = sort (HashSet.toList tags)+ sortedinners = sort (HashMap.toList inners)+ in+ s `hashWithSalt` sortedinners `hashWithSalt` sortedtags+ hashWithSalt s Selector { selectorInners = inners,+ selectorTags = Nothing } =+ let+ sortedinners = sort (HashMap.toList inners)+ in+ s `hashWithSalt` sortedinners+ -- | Combine two 'selectorTags' fields into one. This operation represents the -- union of the tests that are selected by the two fields.-combineTags :: Maybe (Set String) -> Maybe (Set String) -> Maybe (Set String)+combineTags :: Maybe (HashSet Strict.Text) -> Maybe (HashSet Strict.Text) ->+ Maybe (HashSet Strict.Text) -- Nothing means we can't execute, so if the other side says we can, -- we can. combineTags Nothing t = t combineTags t Nothing = t combineTags (Just a) (Just b) -- The empty set means we execute everything, so it absorbs- | a == Set.empty || b == Set.empty = Just $! Set.empty+ | HashSet.null a || HashSet.null b = Just $! HashSet.empty -- Otherwise, we do set union- | otherwise = Just $! Set.union a b+ | otherwise = Just $! HashSet.union a b -- | Take the difference of one set of tags from another.-diffTags :: Maybe (Set String) -> Maybe (Set String) -> Maybe (Set String)+diffTags :: Maybe (HashSet Strict.Text) -> Maybe (HashSet Strict.Text) ->+ Maybe (HashSet Strict.Text) -- Nothing means we can't execute, so if the other side says we can, -- we can. diffTags Nothing _ = Nothing diffTags t Nothing = t diffTags (Just a) (Just b)- | a == Set.empty = Just Set.empty- | b == Set.empty = Nothing+ | HashSet.null a = Just HashSet.empty+ | HashSet.null b = Nothing -- Otherwise, we do set union | otherwise = let- diff = Set.difference a b+ diff = HashSet.difference a b in- if diff == Set.empty+ if diff == HashSet.empty then Nothing else Just $! diff -- | A 'Filter' that selects all tests in all suites. passFilter :: Filter-passFilter = Filter { filterSuites = Set.empty, filterSelector = allSelector }+passFilter = Filter { filterSuites = HashSet.empty,+ filterSelector = HashSet.singleton allSelector,+ filterOptions = HashMap.empty } -- | A 'Selector' that selects all tests. allSelector :: Selector-allSelector = Selector { selectorInners = Map.empty,- selectorTags = Just Set.empty }+allSelector = Selector { selectorInners = HashMap.empty,+ selectorTags = Just HashSet.empty } -reduceSelector :: Maybe (Set String) -> Selector -> Maybe Selector-reduceSelector parentTags Selector { selectorInners = inners,- selectorTags = tags } =+noOptionsAllSelector :: HashMap OptionMap Selector+noOptionsAllSelector = HashMap.singleton HashMap.empty allSelector++-- | Eliminate redundant nested tags from a Selector.+reduceSelector :: Maybe (HashSet Strict.Text) -> Selector -> Maybe Selector+reduceSelector parentTags s @ Selector { selectorInners = inners,+ selectorTags = tags } = let newTags = diffTags tags parentTags newParentTags = combineTags parentTags tags- newInners = Map.mapMaybe (reduceSelector newParentTags) inners+ newInners = HashMap.mapMaybe (reduceSelector newParentTags) inners in- if newTags == Nothing && newInners == Map.empty+ -- This selector goes away if we eliminate all inners and all tags+ if isNothing newTags && HashMap.null newInners then Nothing- else Just $! Selector { selectorInners = inners, selectorTags = tags }+ else Just $! s { selectorInners = inners, selectorTags = tags } -- | Combine two 'Selector's into a single 'Selector'. 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 }+ tryCombineSelectors :: Maybe (HashSet Strict.Text) ->+ Selector -> Selector ->+ Maybe Selector+ tryCombineSelectors parentTags+ s1 @ Selector { selectorInners = inners1,+ selectorTags = tags1 }+ s2 @ Selector { selectorInners = inners2,+ selectorTags = tags2 }+ -- Short-circuit case for allSelector. | s1 == allSelector || s2 == allSelector = Just allSelector | otherwise = let@@ -184,150 +260,213 @@ newTags = diffTags combinedTags parentTags newParentTags = combineTags combinedTags parentTags - firstpass :: Map String Selector -> String -> Selector ->- Map String Selector+ -- | First pass: pull in everything from inners2. This will+ -- combine everything that can be combined and attempt to+ -- reduce the rest.+ firstpass :: HashMap Strict.Text Selector ->+ Strict.Text -> Selector ->+ HashMap Strict.Text 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+ case HashMap.lookup elem inners1 of+ -- If it exists in both, try to combine.+ Just inner' ->+ case tryCombineSelectors newParentTags inner inner' of+ -- If we get back an entry, insert it.+ Just entry -> HashMap.insert elem entry accum+ -- We might have reduced it to nothing.+ Nothing -> accum+ -- Otherwise, attempt to reduce. Nothing -> case reduceSelector newParentTags inner of- Just entry -> Map.insert elem entry accum+ -- If we get back an entry, insert it.+ Just entry -> HashMap.insert elem entry accum+ -- If we reduce it to nothing, leave it out. Nothing -> accum - secondpass :: Map String Selector -> String -> Selector ->- Map String Selector+ -- | Second pass: pull in everything from inners1.+ secondpass :: HashMap Strict.Text Selector ->+ Strict.Text -> Selector ->+ HashMap Strict.Text 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+ case HashMap.lookup elem accum of+ -- If there's nothing there, it means we either had+ -- nothing, or we combined and reduced to nothing in the+ -- first pass.+ Nothing -> case HashMap.lookup elem inners2 of+ -- If we find an entry in inners2, then we combined+ -- and reduced to nothing.+ Just _ -> accum+ -- Otherwise, try to reduce the entry and insert it+ Nothing -> case reduceSelector newParentTags inner of+ Just entry -> HashMap.insert elem entry accum+ Nothing -> accum+ -- If there's something already there, it's because we+ -- combined successfully in the first pass. Just _ -> accum - firstPassMap = Map.foldlWithKey firstpass Map.empty inners2- newInners = Map.foldlWithKey secondpass firstPassMap inners1+ firstPassMap = HashMap.foldlWithKey' firstpass HashMap.empty inners2+ newInners = HashMap.foldlWithKey' secondpass firstPassMap inners1 in- if newTags == Nothing && newInners == Map.empty+ if isNothing newTags && HashMap.null newInners then Nothing else Just $! Selector { selectorInners = newInners, selectorTags = newTags }- in- case combineSelectors' Nothing selector1 selector2 of+ in case tryCombineSelectors Nothing selector1 selector2 of Just out -> out Nothing -> error ("Got Nothing back from combineSelectors " ++ show selector1 ++ " " ++ show selector2) -- | Collect all the selectors from filters that apply to all suites.-collectUniversals :: Filter -> Set Selector -> Set Selector+collectUniversals :: Filter+ -> HashMap OptionMap (HashSet Selector)+ -> HashMap OptionMap (HashSet Selector) collectUniversals Filter { filterSuites = suites,+ filterOptions = options, filterSelector = selector } accum- | suites == Set.empty = Set.insert selector accum+ | HashSet.null suites =+ HashMap.insertWith HashSet.union options selector accum | otherwise = accum -- | Build a map from suite names to the selectors that get run on them. collectSelectors :: Filter -- ^ The current filter- -> Map String (Set Selector)- -- ^ The map from suites to - -> 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+ -> HashMap Strict.Text (HashMap OptionMap (HashSet Selector))+ -- ^ The map from suites to sets of selectors that+ -- run on them.+ -> HashMap Strict.Text (HashMap OptionMap (HashSet Selector))+collectSelectors Filter { filterSuites = suites, filterOptions = options,+ filterSelector = selector } suitemap =+ let+ foldfun accum suite =+ HashMap.insertWith (HashMap.unionWith HashSet.union) suite+ (HashMap.singleton options selector) accum+ in+ foldl foldfun suitemap suites -- | Take a list of test suite names and a list of 'Filter's, and--- build a 'Map' that says for each test suite, what (combined)+-- build a 'HashMap' that says for each test suite, what (combined) -- 'Selector' should be used to select tests.-suiteSelectors :: [String]+suiteSelectors :: [Strict.Text] -- ^ The names of all test suites. -> [Filter] -- ^ The list of 'Filter's from which to build the map.- -> Map String Selector+ -> HashMap Strict.Text (HashMap OptionMap Selector) suiteSelectors allsuites filters -- Short-circuit case if we have no filters, we run everything- | filters == [] =- foldl (\suitemap suite -> Map.insert suite allSelector suitemap)- Map.empty allsuites+ | null filters =+ foldl (\suitemap suite -> HashMap.insert suite noOptionsAllSelector+ suitemap)+ HashMap.empty allsuites | otherwise = let -- First, pull out all the universals- universals = foldr collectUniversals Set.empty filters+ universals = foldr collectUniversals HashMap.empty filters -- If we have any universals, then seed the initial map with them, -- otherwise, use the empty map. initMap =- if universals /= Set.empty- then foldl (\suitemap suite -> Map.insert suite universals suitemap)- Map.empty allsuites- else Map.empty+ if not (HashMap.null universals)+ then foldl (\suitemap suite ->+ HashMap.insert suite universals suitemap)+ HashMap.empty allsuites+ else HashMap.empty -- Now collect all the suite-specific selectors- suiteMap :: Map String (Set Selector)+ suiteMap :: HashMap Strict.Text (HashMap OptionMap (HashSet Selector)) suiteMap = foldr collectSelectors initMap filters in- Map.map (foldl1 combineSelectors . Set.elems) suiteMap+ HashMap.map (HashMap.map (foldl1 combineSelectors . HashSet.toList))+ suiteMap -nameParser :: GenParser Char st Char+nameParser :: Parser Strict.Text nameParser =- oneOf "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+-_"+ do+ out <- many1 (oneOf ("abcdefghijklmnopqrstuvwxyz" +++ "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+-_"))+ return $! Strict.pack out -namesParser :: GenParser Char st [String]-namesParser = sepBy1 (many1 nameParser) (string ",")+valueParser :: Parser Strict.Text+valueParser =+ do+ out <- between (char '\"') (char '\"')+ (many1 (oneOf ("abcdefghijklmnopqrstuvwxyz" +++ "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" +++ "~!@#$%^&*()[]{}<>:;,\'.+=-_?/\\|")))+ return $! Strict.pack out -pathParser :: GenParser Char st [String]-pathParser = sepBy (many1 nameParser) (string ".") -suitesParser :: GenParser Char st [String]+optionParser :: Parser (Strict.Text, Strict.Text)+optionParser =+ do+ key <- valueParser+ _ <- char '='+ val <- valueParser+ return (key, val)++optionsParser :: Parser [(Strict.Text, Strict.Text)]+optionsParser = char '?' >> many1 optionParser++namesParser :: Parser [Strict.Text]+namesParser = sepBy1 nameParser (string ",")++pathParser :: Parser [Strict.Text]+pathParser = sepBy nameParser (string ".")++suitesParser :: Parser [Strict.Text] suitesParser = between (string "[") (string "]") namesParser -tagsParser :: GenParser Char st [String]+tagsParser :: Parser [Strict.Text] tagsParser = char '@' >> namesParser -filterParser :: GenParser Char st ([String], [String], [String])+filterParser :: Parser ([Strict.Text], [Strict.Text], [Strict.Text],+ [(Strict.Text, Strict.Text)]) filterParser = do- suites <- option [] (suitesParser)+ suites <- option [] suitesParser path <- pathParser tagselector <- option [] tagsParser- return (suites, path, tagselector)+ options <- option [] optionsParser+ return (suites, path, tagselector, options) -makeFilter :: ([String], [String], [String]) -> Filter-makeFilter (suites, path, tags) =+makeFilter :: ([Strict.Text], [Strict.Text], [Strict.Text],+ [(Strict.Text, Strict.Text)]) -> Filter+makeFilter (suites, path, tags, options) = let withTags = case tags of [] -> allSelector- _ -> allSelector { selectorTags = Just $! Set.fromList tags }+ _ -> allSelector { selectorTags = Just $! HashSet.fromList tags } genPath [] = withTags genPath (elem : rest) =- Selector { selectorInners = Map.singleton elem $! genPath rest,- selectorTags = Nothing }+ let+ innermap = HashMap.singleton elem $! genPath rest+ in+ Selector { selectorInners = innermap, selectorTags = Nothing } withPath = genPath path in- Filter { filterSuites = Set.fromList suites, filterSelector = withPath }+ Filter { filterSuites = HashSet.fromList suites,+ filterSelector = HashSet.singleton withPath,+ filterOptions = HashMap.fromList options } -- | Parse a 'Filter' expression. The format for filter expressions is -- described in the module documentation. parseFilter :: String -- ^ The name of the source.- -> String+ -> Strict.Text -- ^ The input.- -> Either String Filter+ -> Either Strict.Text Filter parseFilter sourcename input = case parse filterParser sourcename input of- Left e -> Left (show e)+ Left e -> Left (Strict.pack (show e)) Right res -> Right (makeFilter res) -commentParser :: GenParser Char st ()+commentParser :: Parser () commentParser = do _ <- char '#' _ <- many (noneOf "\n")- return $ ()+ return () -lineParser :: GenParser Char st (Maybe Filter)+lineParser :: Parser (Maybe Filter) lineParser = do _ <- many space@@ -335,8 +474,8 @@ _ <- many space optional commentParser case content of- ([], [], []) -> return $ Nothing- _ -> return $ (Just (makeFilter content))+ ([], [], [], []) -> return Nothing+ _ -> return (Just $! makeFilter content) -- | Parse content from a testlist file. The file must contain one -- filter per line. Leading and trailing spaces are ignored, as are@@ -344,37 +483,41 @@ -- the rest of the line. parseFilterFileContent :: String -- ^ The name of the input file.- -> String+ -> Strict.Text -- ^ The file content.- -> Either [String] [Filter]+ -> Either [Strict.Text] [Filter] parseFilterFileContent sourcename input = let- inputlines = lines input+ inputlines = Strict.lines input results = map (parse lineParser sourcename) inputlines in case partitionEithers results of- ([], maybes) -> Right (catMaybes maybes)- (errs, _) -> Left (map show errs)+ ([], maybes) -> Right $! catMaybes maybes+ (errs, _) -> Left $! map (Strict.pack . show) errs -- | Given a 'FilePath', get the contents of the file and parse it as -- a testlist file.-parseFilterFile :: FilePath -> IO (Either [String] [Filter])+parseFilterFile :: FilePath -> IO (Either [Strict.Text] [Filter]) parseFilterFile filename = do- input <- try (readFile filename)+ input <- try (Strict.readFile filename) case input of Left e | isAlreadyInUseError e ->- return (Left ["Error reading testlist file " ++ filename ++- ": File is already in use"])+ return (Left [Strict.concat ["Error reading testlist file ",+ Strict.pack filename,+ ": File is already in use"]]) | isDoesNotExistError e ->- return (Left ["Error reading testlist file " ++ filename ++- ": File does not exist"])+ return (Left [Strict.concat ["Error reading testlist file ",+ Strict.pack filename,+ ": File does not exist"]]) | isPermissionError e ->- return (Left ["Error reading testlist file " ++ filename ++- ": Permission denied"])+ return (Left [Strict.concat ["Error reading testlist file ",+ Strict.pack filename,+ ": Permission denied"]]) | otherwise ->- return (Left ["Cannot read testlist file " ++ filename ++- ": Miscellaneous error"])+ return (Left [Strict.concat ["Cannot read testlist file ",+ Strict.pack filename,+ ": Miscellaneous error"]]) Right contents -> case parseFilterFileContent filename contents of Left errs -> return (Left errs)
src/Test/HUnitPlus/Main.hs view
@@ -1,5 +1,5 @@ {-# OPTIONS_GHC -Wall -Werror -funbox-strict-fields #-}-{-# LANGUAGE DeriveDataTypeable #-}+{-# LANGUAGE DeriveDataTypeable, OverloadedStrings #-} -- | A mostly-complete test selection and execution program for -- running HUnit-Plus tests. The only thing missing are the actual@@ -57,9 +57,8 @@ ) where import Control.Exception-import Data.ByteString.Lazy(hPut) import Data.Either-import Data.Map(Map)+import Data.HashMap.Strict(HashMap) import System.Console.CmdArgs hiding (Quiet) import System.Exit import System.IO@@ -72,6 +71,10 @@ import Text.XML.Expat.Format import Text.XML.Expat.Tree(Node) +import qualified Data.ByteString.Lazy as ByteString+import qualified Data.Text as Strict+import qualified Data.Text.IO as Strict+ -- | Console mode options. data ConsoleMode = -- | Do not generate any console output.@@ -126,11 +129,11 @@ xmlreport = [] &= typFile &= help "Output an XML report, with an optional filename for the report (default is \"report.xml\")"- &= opt "report.xml",+ &= opt ("report.xml" :: String), txtreport = [] &= typFile &= help "Output a plain text report, with an optional filename for the report (default is \"report.txt\")"- &= opt "report.txt",+ &= opt ("report.txt" :: String), consmode = [] &= explicit &= name "c"@@ -168,11 +171,11 @@ -- | Read and parse a single test list file parseTestLists :: Opts -- ^ The command line options- -> IO (Either [String] [Filter])+ -> IO (Either [Strict.Text] [Filter]) parseTestLists Opts { testlist = filenames, filters = cmdfilters } = let cmdresults = map (either (Left . (: [])) (Right . (: [])) .- parseFilter "command line") cmdfilters+ parseFilter "command line") (map Strict.pack cmdfilters) in do fileresults <- mapM parseFilterFile filenames case partitionEithers (fileresults ++ cmdresults) of@@ -180,12 +183,12 @@ (errs, _) -> return (Left (concat errs)) -- | Translate an @IOError@ into an error message-interpretException :: String+interpretException :: Strict.Text -- ^ Prefix to attach to error messages -> IOError -- ^ Exception to interpret- -> String-interpretException prefix e = prefix ++ show e+ -> Strict.Text+interpretException prefix e = Strict.concat [prefix, Strict.pack (show e)] -- | Get the file for reporting XML data withReportHandles :: Opts@@ -193,7 +196,7 @@ -> (Maybe Handle -> Maybe Handle -> IO a) -- ^ A monad parameterized by the xml report handle -- and the text report handle.- -> IO (Either [String] a)+ -> IO (Either [Strict.Text] a) withReportHandles Opts { xmlreport = [], txtreport = [] } cmd = cmd Nothing Nothing >>= return . Right withReportHandles Opts { xmlreport = [ xmlfile ], txtreport = [] } cmd =@@ -271,7 +274,7 @@ case res of Left errs -> do- mapM_ (putStr . (++ "\n")) errs+ mapM_ Strict.putStrLn errs exitFailure Right False -> exitFailure Right True -> exitSuccess@@ -280,7 +283,7 @@ -- simply a wrapper around this function. This function allows users -- to supply their own options, and to decide what to do with the -- result of test execution.-topLevel :: [TestSuite] -> Opts -> IO (Either [String] Bool)+topLevel :: [TestSuite] -> Opts -> IO (Either [Strict.Text] Bool) topLevel suites cmdopts @ Opts { consmode = cmodeopt } = let cmode = case cmodeopt of@@ -303,7 +306,7 @@ executeTests :: [TestSuite] -- ^ The test suites to run- -> Map String Selector+ -> HashMap Strict.Text (HashMap OptionMap Selector) -- ^ The filters to use -> ConsoleMode -- ^ The mode to use for console output@@ -317,8 +320,8 @@ textTerminalReporter = textReporter (putTextToHandle stdout) False verboseTerminalReporter = textReporter (putTextToHandle stdout) True - writeXML :: Handle -> [[Node String String]] -> IO ()- writeXML outhandle [[tree]] = hPut outhandle (format tree)+ writeXML :: Handle -> [[Node Strict.Text Strict.Text]] -> IO ()+ writeXML outhandle [[tree]] = ByteString.hPut outhandle (format tree) writeXML _ _ = error "Internal error in XML reporting: extra nodes on node stack"
src/Test/HUnitPlus/Reporting.hs view
@@ -1,4 +1,5 @@ {-# OPTIONS_GHC -Wall -Werror -funbox-strict-fields #-}+{-# LANGUAGE OverloadedStrings #-} -- | Reporting functionality for HUnit-Plus. Test reporting is now -- defined using a set of events. A 'Reporter' contains handlers for@@ -23,10 +24,11 @@ combinedReporter ) where -import Data.List-import Data.Map(Map)+import Data.HashMap.Strict(HashMap) import Distribution.TestSuite +import qualified Data.Text as Strict+ -- | A record that holds the results of tests that have been performed -- up until this point. data Counts =@@ -54,13 +56,13 @@ data State = State { -- | The name of the case or suite currently being run.- stName :: !String,+ stName :: !Strict.Text, -- | The path to the test case currently being run. stPath :: !Path, -- | The current test statistics. stCounts :: !Counts, -- | The current option values.- stOptions :: !(Map String String),+ stOptions :: !(HashMap Strict.Text Strict.Text), -- | The current option descriptions we know about. stOptionDescs :: ![OptionDescr] }@@ -71,7 +73,7 @@ type Path = [Node] -- | Composed into 'Path's.-data Node = Label String+data Node = Label Strict.Text deriving (Eq, Show, Read) -- | Report generator. This record type contains a number of@@ -108,7 +110,7 @@ -- The user state for this test reporter -> IO us, -- | Called to report progress of a test case run.- reporterCaseProgress :: String+ reporterCaseProgress :: Strict.Text -- A progress message -> State -- The HUnit internal state@@ -130,7 +132,7 @@ -- The user state for this test reporter -> IO us, -- | Called to report output printed to the system output stream.- reporterSystemOut :: String+ reporterSystemOut :: Strict.Text -- The content printed to system out -> State -- The HUnit internal state@@ -138,7 +140,7 @@ -- The user state for this test reporter -> IO us, -- | Called to report output printed to the system error stream.- reporterSystemErr :: String+ reporterSystemErr :: Strict.Text -- The content printed to system out -> State -- The HUnit internal state@@ -146,7 +148,7 @@ -- The user state for this test reporter -> IO us, -- | Called when a test fails.- reporterFailure :: String+ reporterFailure :: Strict.Text -- A message relating to the error -> State -- The HUnit internal state@@ -154,7 +156,7 @@ -- The user state for this test reporter -> IO us, -- | Called when a test reports an error.- reporterError :: String+ reporterError :: Strict.Text -- A message relating to the error -> State -- The HUnit internal state@@ -189,27 +191,34 @@ -- | Converts a test case path to a string, separating adjacent elements by -- a dot (\'.\'). An element of the path is quoted (as with 'show') when -- there is potential ambiguity.-showPath :: Path -> String+showPath :: Path -> Strict.Text showPath [] = "" showPath nodes = let- showNode (Label label) = safe label (show label)- safe s ss = if '.' `elem` s || "\"" ++ s ++ "\"" /= ss then ss else s+ showNode (Label label) = safe label label+ safe s ss =+ if Strict.any ('.' ==) s || Strict.concat ["\"", s, "\""] /= ss+ then ss+ else s in- intercalate "." (reverse (map showNode nodes))+ Strict.intercalate "." (reverse (map showNode nodes)) -- | Gewerate a string showing the entire qualified name from the -- reporting state.-showQualName :: State -> String+showQualName :: State -> Strict.Text showQualName st = case stPath st of [] -> stName st nodes -> let- showNode (Label label) = safe label (show label)- safe s ss = if '.' `elem` s || "\"" ++ s ++ "\"" /= ss then ss else s+ showNode (Label label) = safe label label+ safe s ss =+ if Strict.any ('.' ==) s || Strict.concat ["\"", s, "\""] /= ss+ then ss+ else s in- intercalate "." (reverse (map showNode nodes)) ++ "." ++ stName st+ Strict.concat [Strict.intercalate "." (reverse (map showNode nodes)),+ ".", stName st] -- | Combines two 'Reporter's into a single reporter that calls both.@@ -245,73 +254,73 @@ do us1 <- reportStart1 us2 <- reportStart2- return $! (us1, us2)+ return (us1, us2) reportEnd time counts (us1, us2) = do us1' <- reportEnd1 time counts us1 us2' <- reportEnd2 time counts us2- return $! (us1', us2')+ return (us1', us2') reportStartSuite ss (us1, us2) = do us1' <- reportStartSuite1 ss us1 us2' <- reportStartSuite2 ss us2- return $! (us1', us2')+ return (us1', us2') reportEndSuite time ss (us1, us2) = do us1' <- reportEndSuite1 time ss us1 us2' <- reportEndSuite2 time ss us2- return $! (us1', us2')+ return (us1', us2') reportStartCase ss (us1, us2) = do us1' <- reportStartCase1 ss us1 us2' <- reportStartCase2 ss us2- return $! (us1', us2')+ return (us1', us2') reportCaseProgress msg ss (us1, us2) = do us1' <- reportCaseProgress1 msg ss us1 us2' <- reportCaseProgress2 msg ss us2- return $! (us1', us2')+ return (us1', us2') reportEndCase time ss (us1, us2) = do us1' <- reportEndCase1 time ss us1 us2' <- reportEndCase2 time ss us2- return $! (us1', us2')+ return (us1', us2') reportSkipCase ss (us1, us2) = do us1' <- reportSkipCase1 ss us1 us2' <- reportSkipCase2 ss us2- return $! (us1', us2')+ return (us1', us2') reportSystemOut msg ss (us1, us2) = do us1' <- reportSystemOut1 msg ss us1 us2' <- reportSystemOut2 msg ss us2- return $! (us1', us2')+ return (us1', us2') reportSystemErr msg ss (us1, us2) = do us1' <- reportSystemErr1 msg ss us1 us2' <- reportSystemErr2 msg ss us2- return $! (us1', us2')+ return (us1', us2') reportFailure msg ss (us1, us2) = do us1' <- reportFailure1 msg ss us1 us2' <- reportFailure2 msg ss us2- return $! (us1', us2')+ return (us1', us2') reportError msg ss (us1, us2) = do us1' <- reportError1 msg ss us1 us2' <- reportError2 msg ss us2- return $! (us1', us2')+ return (us1', us2') in Reporter { reporterStart = reportStart,
src/Test/HUnitPlus/Text.hs view
@@ -1,4 +1,5 @@ {-# OPTIONS_GHC -Wall -Werror #-}+{-# LANGUAGE OverloadedStrings #-} -- | Text-based reporting functionality for reporting either as text, -- or to the terminal. This module is an adaptation of code from the@@ -32,11 +33,12 @@ import Test.HUnitPlus.Reporting import Control.Monad (when)-import System.IO (Handle, stderr, hPutStr, hPutStrLn)+import System.IO (Handle, stderr) import Text.Printf(printf) -import qualified Data.Map as Map-+import qualified Data.HashMap.Strict as HashMap+import qualified Data.Text as Strict+import qualified Data.Text.IO as Strict -- | The text-based reporters ('textReporter' and 'terminalReporter') -- construct strings and pass them to the function embodied in a@@ -44,24 +46,24 @@ -- ways. Two schemes are defined here. 'putTextToHandle' writes -- report lines to a given handle. 'putTextToShowS' accumulates lines -- for return as a whole.--- +-- -- The 'PutText' function is also passed, and returns, an arbitrary state -- value (called 'st' here). The initial state value is given in the -- 'PutText'; the final value is returned by 'runTestText'. -data PutText st = PutText (String -> st -> IO st) st+data PutText st = PutText (Strict.Text -> st -> IO st) st -- | Writes persistent lines to the given handle. putTextToHandle :: Handle -> PutText ()-putTextToHandle handle = PutText (\line () -> hPutStr handle line) ()+putTextToHandle handle = PutText (\line () -> Strict.hPutStr handle line) () -- | Accumulates lines for return by 'runTestText'. The -- accumulated lines are represented by a @'ShowS' ('String' -> -- 'String')@ function whose first argument is the string to be -- appended to the accumulated report lines.-putTextToShowS :: PutText ShowS+putTextToShowS :: PutText (Strict.Text -> Strict.Text) putTextToShowS =- PutText (\line func -> return (\rest -> func (line ++ rest))) id+ PutText (\line func -> return (\rest -> func (Strict.concat [line, rest]))) id -- | Create a 'Reporter' that outputs a textual report for -- non-terminal output.@@ -75,36 +77,36 @@ reportProblem prefix msg ss us = let path = showQualName ss- line = "### " ++ prefix ++ path ++ ": " ++ msg ++ "\n"+ line = Strict.concat ["### ", prefix, path, ": ", msg, "\n"] in put line us reportOutput prefix msg ss us = let path = showQualName ss- line = "### " ++ prefix ++ path ++ ": " ++ msg ++ "\n"+ line = Strict.concat ["### ", prefix, path, ": ", msg, "\n"] in if verbose then put line us else return us reportStartSuite ss us = let- line = "Test suite " ++ stName ss ++ " starting\n"+ line = Strict.concat ["Test suite ", stName ss, " starting\n"] in if verbose then put line us else return us reportEndSuite time ss us = let timestr = printf "%.6f" time- line = "Test suite" ++ stName ss ++ " completed in " ++- timestr ++ " sec\n"+ line = Strict.concat ["Test suite", stName ss, " completed in ",+ Strict.pack timestr, " sec\n"] in if verbose then put line us else return us reportStartCase ss us = let path = showQualName ss- line = if null path then "Test case starting\n"- else "Test case " ++ path ++ " starting\n"+ line = if Strict.null path then "Test case starting\n"+ else Strict.concat ["Test case ", path, " starting\n"] in if verbose then put line us else return us @@ -112,23 +114,26 @@ let path = showQualName ss timestr = printf "%.6f" time- line = if null path then "Test completed in " ++ timestr ++ " sec\n"- else "Test " ++ path ++ " completed in " ++ timestr ++ " sec\n"+ line = if Strict.null path+ then Strict.concat ["Test completed in ",+ Strict.pack timestr, " sec\n"]+ else Strict.concat ["Test ", path, " completed in ",+ Strict.pack timestr, " sec\n"] in if verbose then put line us else return us reportEnd time counts us = let- countstr = showCounts counts ++ "\n"+ countstr = Strict.concat [showCounts counts, "\n"] timestr = printf "%.6f" time- timeline = "Tests completed in " ++ timestr ++ " sec\n"+ timeline = Strict.concat ["Tests completed in ",+ Strict.pack timestr, " sec\n"] in do if verbose then do us' <- put timeline us put countstr us'- else- put countstr us+ else put countstr us in defaultReporter { reporterStart = return initUs,@@ -161,13 +166,13 @@ runTestText puttext @ (PutText put us0) verbose t = let initState = State { stCounts = zeroCounts, stName = "",- stPath = [], stOptions = Map.empty,+ stPath = [], stOptions = HashMap.empty, stOptionDescs = [] } reporter = textReporter puttext verbose in do (ss1, us1) <- ((performTest $! reporter) allSelector $! initState) us0 t- us2 <- put (showCounts (stCounts ss1) ++ "\n") us1+ us2 <- put (Strict.concat [showCounts (stCounts ss1), "\n"]) us1 return (stCounts ss1, us2) -- | Execute a test suite, processing text output according to the@@ -188,11 +193,12 @@ runSuiteText puttext @ (PutText put us0) verbose suite @ TestSuite { suiteName = sname } = let- selectorMap = Map.singleton sname allSelector+ selectorMap = HashMap.singleton sname (HashMap.singleton HashMap.empty+ allSelector) reporter = textReporter puttext verbose in do (counts, us1) <- ((performTestSuite $! reporter) $!selectorMap) us0 suite- us2 <- put (showCounts counts ++ "\n") us1+ us2 <- put (Strict.concat [showCounts counts, "\n"]) us1 return (counts, us2) -- | Execute the given test suites, processing text output according@@ -213,35 +219,50 @@ runSuitesText puttext @ (PutText put _) verbose suites = let suiteNames = map suiteName suites- selectorMap = foldl (\suitemap sname ->- Map.insert sname allSelector suitemap)- Map.empty suiteNames+ selectorMap =+ foldl (\suitemap sname ->+ HashMap.insert sname (HashMap.singleton HashMap.empty+ allSelector) suitemap)+ HashMap.empty suiteNames reporter = textReporter puttext verbose in do (counts, us1) <- ((performTestSuites $! reporter) $! selectorMap) suites- us2 <- put (showCounts counts ++ "\n") us1+ us2 <- put (Strict.concat [showCounts counts, "\n"]) us1 return (counts, us2) -- | Converts test execution counts to a string.-showCounts :: Counts -> String+showCounts :: Counts -> Strict.Text showCounts Counts { cCases = cases, cTried = tried, cErrors = errors, cFailures = failures, cAsserts = asserts, cSkipped = skipped } =- "Cases: " ++ show cases ++ " Tried: " ++ show tried ++- " Errors: " ++ show errors ++ " Failures: " ++ show failures ++- " Assertions: " ++ show asserts ++ " Skipped: " ++ show skipped+ Strict.concat ["Cases: ", Strict.pack (show cases), " Tried: ",+ Strict.pack (show tried), " Errors: ",+ Strict.pack (show errors), " Failures: ",+ Strict.pack (show failures), " Assertions: ",+ Strict.pack (show asserts), " Skipped: ",+ Strict.pack (show skipped)] -- | Terminal output function, used by the run*TT function and -- terminal reporters.-termPut :: String -> Bool -> Int -> IO Int-termPut line pers (-1) = do when pers (hPutStrLn stderr line); return (-1)-termPut line True cnt = do hPutStrLn stderr (erase cnt ++ line); return 0-termPut line False _ = do hPutStr stderr ('\r' : line); return (length line)+termPut :: Strict.Text -> Bool -> Int -> IO Int+termPut line pers (-1) =+ do+ when pers (Strict.hPutStrLn stderr line)+ return (-1)+termPut line True cnt =+ do+ Strict.hPutStrLn stderr (Strict.concat [erase cnt, line])+ return 0+termPut line False _ =+ do+ Strict.hPutStr stderr (Strict.concat ["\r", line])+ return (Strict.length line) -- The "erasing" strategy with a single '\r' relies on the fact that the -- lengths of successive summary lines are monotonically nondecreasing.-erase :: Int -> String-erase cnt = if cnt == 0 then "" else "\r" ++ replicate cnt ' ' ++ "\r"+erase :: Int -> Strict.Text+erase cnt =+ if cnt == 0 then "" else Strict.concat ["\r", Strict.replicate cnt " ", "\r"] -- | A reporter that outputs lines indicating progress to the -- terminal. Reporting is made to standard error, and progress@@ -251,16 +272,15 @@ let reportProblem prefix msg ss us = let- line = "### " ++ prefix ++ path ++ '\n' : msg+ line = Strict.concat ["### ", prefix, path, "\n", msg] path = showQualName ss in termPut line True us in defaultReporter { reporterStart = return 0,- reporterEnd = (\_ _ _ -> do hPutStr stderr "\n"; return 0),- reporterEndCase =- (\_ ss us -> termPut (showCounts (stCounts ss)) False us),+ reporterEnd = \_ _ _ -> do Strict.hPutStr stderr "\n"; return 0,+ reporterEndCase = \_ ss us -> termPut (showCounts (stCounts ss)) False us, reporterError = reportProblem "Error in: ", reporterFailure = reportProblem "Failure in: " }@@ -277,7 +297,7 @@ runTestTT t = let initState = State { stCounts = zeroCounts, stName = "",- stPath = [], stOptions = Map.empty,+ stPath = [], stOptions = HashMap.empty, stOptionDescs = [] } in do (ss1, us1) <- (performTest terminalReporter allSelector $! initState) 0 t@@ -295,10 +315,11 @@ runSuiteTT :: TestSuite -> IO Counts runSuiteTT suite @ TestSuite { suiteName = sname } = let- selectorMap = Map.singleton sname allSelector+ selectorMap = HashMap.singleton sname (HashMap.singleton HashMap.empty+ allSelector) in do (counts, us) <- (performTestSuite terminalReporter $! selectorMap) 0 suite- 0 <- termPut (showCounts counts ++ "\n") True us+ 0 <- termPut (Strict.concat [showCounts counts, "\n"]) True us return counts -- | Execute the given test suites, processing text output according@@ -313,10 +334,12 @@ runSuitesTT suites = let suiteNames = map suiteName suites- selectorMap = foldl (\suitemap sname ->- Map.insert sname allSelector suitemap)- Map.empty suiteNames+ selectorMap =+ foldl (\suitemap sname ->+ HashMap.insert sname (HashMap.singleton HashMap.empty+ allSelector) suitemap)+ HashMap.empty suiteNames in do (counts, us) <- (performTestSuites terminalReporter $! selectorMap) suites- 0 <- termPut (showCounts counts ++ "\n") True us+ 0 <- termPut (Strict.concat [showCounts counts, "\n"]) True us return counts
src/Test/HUnitPlus/XML.hs view
@@ -1,4 +1,5 @@ {-# OPTIONS_GHC -Wall -Werror -funbox-strict-fields #-}+{-# LANGUAGE OverloadedStrings #-} -- | 'Reporter' for running HUnit tests and reporting results as -- JUnit-style XML reports. This uses the hexpat library for XML@@ -20,84 +21,86 @@ xmlReporter ) where -import Data.Map(Map)+import Data.HashMap.Strict(HashMap)+import Data.List(sort) import Data.Time import Network.HostName import Test.HUnitPlus.Reporting(Reporter(..), State(..), Counts(..), defaultReporter, showPath) import Text.XML.Expat.Tree -import qualified Data.Map as Map+import qualified Data.HashMap.Strict as HashMap+import qualified Data.Text as Strict -- | Generate an element for a property definition-propertyElem :: (String, String)+propertyElem :: (Strict.Text, Strict.Text) -- ^ The name/value pair- -> Node String String+ -> Node Strict.Text Strict.Text propertyElem (name, value) = Element { eName = "property", eChildren = [], eAttributes = [("name", name), ("value", value)] } -- | Generate an element for a set of property definitions-propertiesElem :: [(String, String)]+propertiesElem :: [(Strict.Text, Strict.Text)] -- ^ A list of name/value pairs to make into properties- -> Node String String+ -> Node Strict.Text Strict.Text propertiesElem props = Element { eName = "properties", eAttributes = [], eChildren = map propertyElem props } -- | Generate an element representing output to stdout-systemOutElem :: String+systemOutElem :: Strict.Text -- ^ The stdout output- -> Node String String+ -> Node Strict.Text Strict.Text systemOutElem content = Element { eName = "system-out", eAttributes = [], eChildren = [Text content] } -- | Generate an element representing output to stderr-systemErrElem :: String+systemErrElem :: Strict.Text -- ^ The stderr output- -> Node String String+ -> Node Strict.Text Strict.Text systemErrElem content = Element { eName = "system-err", eAttributes = [], eChildren = [Text content] } -- | Generate an element representing a test failure.-failureElem :: String+failureElem :: Strict.Text -- ^ A message associated with the failure- -> Node String String+ -> Node Strict.Text Strict.Text failureElem message = Element { eAttributes = [("message", message)], eName = "failure", eChildren = [] } -- | Generate an element representing an error in a test.-errorElem :: String+errorElem :: Strict.Text -- ^ A message associated with the error- -> Node String String+ -> Node Strict.Text Strict.Text errorElem message = Element { eAttributes = [("message", message)], eName = "error", eChildren = [] } -- | Generate an element for a single test case.-testcaseElem :: String+testcaseElem :: Strict.Text -- ^ The name of the test- -> String+ -> Strict.Text -- ^ The path to the test (reported as \"classname\") -> Word -- ^ The number of assertions in the test -> Double -- ^ The execution time of the test- -> [Node String String]+ -> [Node Strict.Text Strict.Text] -- ^ Elements representing the events that happened -- during test execution.- -> Node String String+ -> Node Strict.Text Strict.Text testcaseElem name classname assertions time children = Element { eName = "testcase", eChildren = children, eAttributes = [("name", name), ("classname", classname),- ("assertions", show assertions),- ("time", show time)] }+ ("assertions", Strict.pack (show assertions)),+ ("time", Strict.pack (show time))] } -- | Generate an element for a skipped test case-skippedTestElem :: String+skippedTestElem :: Strict.Text -- ^ The name of the test- -> String+ -> Strict.Text -- ^ The path of the test- -> Node String String+ -> Node Strict.Text Strict.Text skippedTestElem name classname = let skippedElem = Element { eName = "skipped", eAttributes = [],@@ -107,9 +110,9 @@ eName = "testcase", eChildren = [skippedElem] } -- | Generate an element for a test suite run-testSuiteElem :: String+testSuiteElem :: Strict.Text -- ^ The name of the test suite- -> Map String String+ -> HashMap Strict.Text Strict.Text -- ^ The properties defined for this suite -> Word -- ^ The number of tests@@ -119,20 +122,20 @@ -- ^ The number of errors -> Word -- ^ The number of skipped tests- -> String+ -> Strict.Text -- ^ The hostname of the machine on which this was run -> UTCTime -- ^ The timestamp at which time this was run -> Double -- ^ The execution time for the test suite- -> [Node String String]+ -> [Node Strict.Text Strict.Text] -- ^ The testcases and output nodes for the test suite- -> Node String String+ -> Node Strict.Text Strict.Text testSuiteElem name propmap tests failures errors skipped hostname timestamp time content = let contentWithProps =- case Map.assocs propmap of+ case sort (HashMap.toList propmap) of [] -> content props -> propertiesElem props : content timestr = formatTime defaultTimeLocale "%c" timestamp@@ -140,25 +143,25 @@ Element { eName = "testsuite", eChildren = contentWithProps, eAttributes = [("name", name), ("hostname", hostname),- ("timestamp", timestr),- ("time", show time),- ("tests", show tests),- ("failures", show failures),- ("errors", show errors),- ("skipped", show skipped)] }+ ("timestamp", Strict.pack timestr),+ ("time", Strict.pack (show time)),+ ("tests", Strict.pack (show tests)),+ ("failures", Strict.pack (show failures)),+ ("errors", Strict.pack (show errors)),+ ("skipped", Strict.pack (show skipped))] } -- | Generate the top-level element containing all test suites testSuitesElem :: Double -- ^ The execution time of all suites- -> [Node String String]+ -> [Node Strict.Text Strict.Text] -- ^ Elements representing all the test suites- -> Node String String+ -> Node Strict.Text Strict.Text testSuitesElem time suites = Element { eName = "testsuites", eChildren = suites,- eAttributes = [("time", show time)] }+ eAttributes = [("time", Strict.pack (show time))] } -- | A reporter that generates JUnit XML reports-xmlReporter :: Reporter [[Node String String]]+xmlReporter :: Reporter [[Node Strict.Text Strict.Text]] xmlReporter = let reportStart = return [[]]@@ -178,8 +181,8 @@ hostname <- getHostName timestamp <- getCurrentTime return ((testSuiteElem name options cases failures errors skipped- hostname timestamp time (reverse events) :- rest) : stack)+ (Strict.pack hostname) timestamp time+ (reverse events) : rest) : stack) reportEndSuite _ _ stack = fail ("Node stack underflow in end suite.\n" ++ show stack)
+ test/Tests.hs view
@@ -0,0 +1,7 @@+module Tests where++import Distribution.TestSuite+import qualified Tests.Test as Test++tests :: IO [Test]+tests = return [Test.tests]
+ test/Tests/Test.hs view
@@ -0,0 +1,7 @@+module Tests.Test where++import Distribution.TestSuite+import qualified Tests.Test.HUnitPlus as HUnitPlus++tests :: Test+tests = testGroup "Test" [HUnitPlus.tests]
+ test/Tests/Test/HUnitPlus.hs view
@@ -0,0 +1,15 @@+module Tests.Test.HUnitPlus where++import Distribution.TestSuite+import qualified Tests.Test.HUnitPlus.Base as Base+import qualified Tests.Test.HUnitPlus.Execution as Execution+import qualified Tests.Test.HUnitPlus.Filter as Filter+import qualified Tests.Test.HUnitPlus.Main as Main+import qualified Tests.Test.HUnitPlus.Reporting as Reporting+import qualified Tests.Test.HUnitPlus.Text as Text+import qualified Tests.Test.HUnitPlus.XML as XML++tests :: Test+tests = testGroup "HUnitPlus" [Base.tests, Execution.tests, Filter.tests,+ Reporting.tests, Main.tests,+ Text.tests, XML.tests]
+ test/Tests/Test/HUnitPlus/Base.hs view
@@ -0,0 +1,588 @@+{-# LANGUAGE DeriveDataTypeable, OverloadedStrings #-}++module Tests.Test.HUnitPlus.Base(tests) where++import Control.Exception(Exception,+ throwIO)+import Data.List+import Data.Typeable+import Debug.Trace+import Distribution.TestSuite(Test(..),+ TestInstance(..),+ Result(..),+ Progress(..),+ testGroup)+import Test.HUnitPlus.Base+import Test.HUnitPlus.Reporting++import qualified Data.HashMap.Strict as HashMap+import qualified Data.HashSet as HashSet+import qualified Tests.Test.HUnitPlus.ReporterUtils as Utils++type ReportEvent = Utils.ReportEvent++loggingReporter = Utils.loggingReporter++makeTestCase :: (Test, String, [String], Counts, [ReportEvent]) -> Test+makeTestCase (Test TestInstance { name = actualName,+ tags = actualTags,+ run = runInnerTest },+ expectedName, expectedTags, expectedCounts, expectedEvents) =+ let+ initState = State { stCounts = zeroCounts, stName = "", stPath = [],+ stOptions = HashMap.empty, stOptionDescs = [] }++ genResult actualCounts actualEvents =+ let+ nameErr =+ if actualName /= expectedName+ then ["Expected name \"" ++ expectedName +++ "\" but got \"" ++ actualName ++ "\""]+ else []+ tagsErr =+ if HashSet.fromList expectedTags /= HashSet.fromList actualTags+ then ("Expected tags " ++ show expectedTags +++ " but got " ++ show actualTags) : nameErr+ else nameErr+ countsErr =+ if expectedCounts /= actualCounts+ then ("Expected counts " ++ show expectedCounts +++ " but got " ++ show actualCounts) : tagsErr+ else tagsErr+ eventsErr =+ if expectedEvents /= actualEvents+ then ("Expected reporting events " ++ show expectedEvents +++ " but got " ++ show actualEvents) : countsErr+ else countsErr+ in case eventsErr of+ [] -> Finished Pass+ _ -> Finished (Fail (intercalate "\n" eventsErr))++ runRealTest =+ do+ (_, State { stCounts = actualCounts }, actualEvents) <-+ executeTest loggingReporter initState [] runInnerTest+ return (genResult actualCounts actualEvents)++ testInstance = TestInstance { name = actualName, tags = [], options = [],+ setOption = (\_ _ -> Right testInstance),+ run = runRealTest }+ in+ Test testInstance++oneFail :: Counts+oneFail = zeroCounts { cFailures = 1 }++oneError :: Counts+oneError = zeroCounts { cErrors = 1 }++oneAssert :: Counts+oneAssert = zeroCounts { cAsserts = 1, cCaseAsserts = 1 }++twoFails :: Counts+twoFails = zeroCounts { cFailures = 2 }++twoErrors :: Counts+twoErrors = zeroCounts { cErrors = 2 }++twoAsserts :: Counts+twoAsserts = zeroCounts { cAsserts = 2, cCaseAsserts = 2 }++oneFailOneAssert :: Counts+oneFailOneAssert = zeroCounts { cFailures = 1, cAsserts = 1, cCaseAsserts = 1 }++twoFailsOneAssert :: Counts+twoFailsOneAssert = zeroCounts { cFailures = 2, cAsserts = 1, cCaseAsserts = 1 }++oneFailOneAssertOneError :: Counts+oneFailOneAssertOneError =+ zeroCounts { cFailures = 1, cErrors = 1, cAsserts = 1, cCaseAsserts = 1 }++oneErrorOneAssert :: Counts+oneErrorOneAssert = zeroCounts { cErrors = 1, cAsserts = 1, cCaseAsserts = 1 }++oneFailTwoAsserts :: Counts+oneFailTwoAsserts = zeroCounts { cFailures = 1, cAsserts = 2, cCaseAsserts = 2 }++twoFailsTwoAsserts :: Counts+twoFailsTwoAsserts = zeroCounts { cFailures = 2, cAsserts = 2, cCaseAsserts = 2 }++twoErrorsTwoAsserts :: Counts+twoErrorsTwoAsserts = zeroCounts { cErrors = 2, cAsserts = 2, cCaseAsserts = 2 }++externalTestPass :: Test+externalTestPass =+ let+ runTest = return (Finished Pass)++ testInstance = TestInstance { name = "externalTestPass", tags = [],+ options = [], run = runTest,+ setOption = (\_ _ -> return testInstance) }+ in+ Test testInstance++externalTestEventuallyPass :: Test+externalTestEventuallyPass =+ let+ runTest = return (eventually Pass)++ testInstance = TestInstance { name = "externalTestEventuallyPass",+ tags = [], options = [], run = runTest,+ setOption = (\_ _ -> return testInstance) }+ in+ Test testInstance++externalTestFail :: Test+externalTestFail =+ let+ runTest = return (Finished (Fail "External Test Failed"))++ testInstance = TestInstance { name = "externalTestFail", tags = [],+ options = [], run = runTest,+ setOption = (\_ _ -> return testInstance) }+ in+ Test testInstance++externalTestEventuallyFail :: Test+externalTestEventuallyFail =+ let+ runTest = return (eventually (Fail "External Test Failed"))++ testInstance = TestInstance { name = "externalTestEventuallyFail",+ tags = [], options = [], run = runTest,+ setOption = (\_ _ -> return testInstance) }+ in+ Test testInstance++externalTestError :: Test+externalTestError =+ let+ runTest = return (Finished (Error "External Test Error"))++ testInstance = TestInstance { name = "externalTestError", tags = [],+ options = [], run = runTest,+ setOption = (\_ _ -> return testInstance) }+ in+ Test testInstance++externalTestEventuallyError :: Test+externalTestEventuallyError =+ let+ runTest = return (eventually (Error "External Test Error"))++ testInstance = TestInstance { name = "externalTestEventuallyError",+ tags = [], options = [], run = runTest,+ setOption = (\_ _ -> return testInstance) }+ in+ Test testInstance++eventually :: Result -> Progress+eventually result =+ Progress "."+ (return (Progress ".."+ (return (Progress "..."+ (return (Finished result))))))++externalTestException :: Test+externalTestException =+ let+ runTest = error "Exception Message" >> return (Finished Pass)++ testInstance = TestInstance { name = "externalTestException",+ tags = [], options = [], run = runTest,+ setOption = (\_ _ -> return testInstance) }+ in+ Test testInstance++data TestException = TestException Bool+ deriving (Typeable, Show, Eq)++instance Exception TestException++testCases :: [(Test, String, [String], Counts, [ReportEvent])]+testCases = [+ -- Test low-level functions+ ("logAssert" ~: logAssert, "logAssert", [], oneAssert, []),+ ("logFailure" ~: logFailure "Fail Message", "logFailure", [],+ oneFail, [Utils.Failure "Fail Message"]),+ ("logError" ~: logError "Error Message", "logError", [],+ oneError, [Utils.Error "Error Message"]),+ ("logSysout" ~: logSysout "Sysout Message", "logSysout", [],+ zeroCounts, [Utils.SystemOut "Sysout Message"]),+ ("logSyserr" ~: logSyserr "Syserr Message", "logSyserr", [],+ zeroCounts, [Utils.SystemErr "Syserr Message"]),+ ("logAssert_twice" ~: do logAssert; logAssert,+ "logAssert_twice", [], twoAsserts, []),+ ("logFailure_twice" ~: do logFailure "Fail Message\n"+ logFailure "Fail Message 2\n",+ "logFailure_twice", [], oneFail,+ [Utils.Failure "Fail Message\n", Utils.Failure "Fail Message 2\n"]),+ ("logError_twice" ~: do logError "Error Message\n"+ logError "Error Message 2\n",+ "logError_twice", [], oneError,+ [Utils.Error "Error Message\n", Utils.Error "Error Message 2\n"]),+ -- Test assertion functions+ ("assertSuccess" ~: assertSuccess, "assertSuccess", [], oneAssert, []),+ ("assertSuccess_twice" ~: do assertSuccess; assertSuccess,+ "assertSuccess_twice", [], twoAsserts, []),+ ("assertFailure" ~: assertFailure "Fail Message", "assertFailure",+ [], oneFailOneAssert, [Utils.Failure "Fail Message"]),+ ("assertFailure_twice" ~: do assertFailure "Fail Message\n"+ assertFailure "Fail Message 2\n",+ "assertFailure_twice", [], oneFailTwoAsserts,+ [Utils.Failure "Fail Message\n", Utils.Failure "Fail Message 2\n"]),+ ("assertSuccess_assertFailure" ~: do assertSuccess+ assertFailure "Fail Message",+ "assertSuccess_assertFailure", [], oneFailTwoAsserts,+ [Utils.Failure "Fail Message"]),+ ("assertFailure_assertSuccess" ~: do assertFailure "Fail Message"+ assertSuccess,+ "assertFailure_assertSuccess", [], oneFailTwoAsserts,+ [Utils.Failure "Fail Message"]),+ ("abortError" ~: abortError "Error Message", "abortError", [], oneError,+ [Utils.Error "Error Message"]),+ ("abortFailure" ~: abortFailure "Failure Message", "abortFailure",+ [], oneFail, [Utils.Failure "Failure Message"]),+ ("assertFailure_abortError" ~: do assertFailure "Failure Message"+ abortError "Error Message",+ "assertFailure_abortError", [], oneFailOneAssertOneError,+ [Utils.Failure "Failure Message", Utils.Error "Error Message"]),+ ("assertFailure_abortFailure" ~: do assertFailure "Failure Message"+ abortFailure "Failure Message 2",+ "assertFailure_abortFailure", [], oneFailOneAssert,+ [Utils.Failure "Failure Message", Utils.Failure "Failure Message 2"]),+ ("abortError_assertFailure" ~: do abortError "Error Message"+ assertFailure "Failure Message",+ "abortError_assertFailure", [], oneError, [Utils.Error "Error Message"]),+ ("abortFailure_assertFailure" ~: do abortFailure "Failure Message"+ assertFailure "Failure Message 2",+ "abortFailure_assertFailure", [], oneFail,+ [Utils.Failure "Failure Message"]),+ ("assertBool_True" ~: assertBool "Fail Message" True,+ "assertBool_True", [], oneAssert, []),+ ("assertBool_False" ~: assertBool "Fail Message" False, "assertBool_False",+ [], oneFailOneAssert, [Utils.Failure "Fail Message"]),+ ("assertString_empty" ~: assertString "",+ "assertString_empty", [], oneAssert, []),+ ("assertString_nonempty" ~: assertString "non-empty",+ "assertString_nonempty", [], oneFailOneAssert, [Utils.Failure "non-empty"]),+ ("assertEqual_eq" ~: assertEqual "Prefix" 3 3,+ "assertEqual_eq", [], oneAssert, []),+ ("assertEqual_neq" ~: assertEqual "Prefix" 3 4,+ "assertEqual_neq", [], oneFailOneAssert,+ [Utils.Failure "Prefix\nexpected: 3\nbut got: 4"]),+ ("assertThrows_nothrow" ~: assertThrows (\(TestException _) -> assertSuccess)+ (return ()),+ "assertThrows_nothrow", [], oneFailOneAssert,+ [Utils.Failure "expected exception but computation finished normally"]),+ ("assertThrows_badthrow" ~: assertThrows (TestException False @=?)+ (throwIO (TestException True)),+ "assertThrows_badthrow", [], oneFailOneAssert,+ [Utils.Failure "expected: TestException False\nbut got: TestException True"]),+ ("assertThrows_goodthrow" ~: assertThrows (TestException True @=?)+ (throwIO (TestException True)),+ "assertThrows_goodthrow", [], oneAssert, []),+ ("assertThrowsExact_nothrow" ~: assertThrowsExact (TestException True)+ (return ()),+ "assertThrowsExact_nothrow", [], oneFailOneAssert,+ [Utils.Failure ("expected exception TestException True but computation finished normally")]),+ ("assertThrowsExact_badthrow" ~:+ assertThrowsExact (TestException False) (throwIO (TestException True)),+ "assertThrowsExact_badthrow", [], oneFailOneAssert,+ [Utils.Failure ("expected exception TestException False but got TestException True")]),+ ("assertThrowsExact_goodthrow" ~:+ assertThrowsExact (TestException True) (throwIO (TestException True)),+ "assertThrowsExact_goodthrow", [], oneAssert, []),+ -- Assertable instances+ ("assert_Unit" ~: assert (), "assert_Unit", [], zeroCounts, []),+ ("assert_True" ~: assert True, "assert_True", [], oneAssert, []),+ ("assert_False" ~: assert False, "assert_False",+ [], oneFailOneAssert, [Utils.Failure ""]),+ ("assertWithMsg_True" ~: assertWithMsg "Message" True,+ "assertWithMsg_True", [], oneAssert, []),+ ("assertWithMsg_False" ~: assertWithMsg "Message" False,+ "assertWithMsg_False", [], oneFailOneAssert, [Utils.Failure "Message"]),+ ("assert_String_empty" ~: assert ("" :: String),+ "assert_String_empty", [], oneAssert, []),+ ("assert_String_nonempty" ~: assert ("non-empty" :: String), "assert_String_nonempty",+ [], oneFailOneAssert, [Utils.Failure "non-empty"]),+ ("assertWithMsg_String_empty" ~: assertWithMsg "Prefix: " ("" :: String),+ "assertWithMsg_String_empty", [], oneAssert, []),+ ("assertWithMsg_String_nonempty" ~: assertWithMsg "Prefix: " ("non-empty" :: String),+ "assertWithMsg_String_nonempty", [], oneFailOneAssert,+ [Utils.Failure "Prefix: non-empty"]),+ ("assert_list_assertSuccess_twice" ~: assert [assertSuccess, assertSuccess],+ "assert_list_assertSuccess_twice", [], twoAsserts, []),+ ("assert_list_assertFailure_twice" ~:+ assert [assertFailure "Fail Message\n", assertFailure "Fail Message 2\n"],+ "assert_list_assertFailure_twice", [], oneFailTwoAsserts,+ [Utils.Failure "Fail Message\n", Utils.Failure "Fail Message 2\n"]),+ ("assert_list_assertSuccess_assertFailure" ~:+ assert [assertSuccess, assertFailure "Fail Message"],+ "assert_list_assertSuccess_assertFailure", [], oneFailTwoAsserts,+ [Utils.Failure "Fail Message"]),+ ("assert_list_assertFailure_assertSuccess" ~:+ assert [assertFailure "Fail Message", assertSuccess],+ "assert_list_assertFailure_assertSuccess", [], oneFailTwoAsserts,+ [Utils.Failure "Fail Message"]),+ ("assertWithMsg_list_assertSuccess_twice" ~:+ assertWithMsg "Prefix: " [assertSuccess, assertSuccess],+ "assertWithMsg_list_assertSuccess_twice", [], twoAsserts, []),+ ("assertWithMsg_list_assertFailure_twice" ~:+ assertWithMsg "Prefix: " [assertFailure "Fail Message\n",+ assertFailure "Fail Message 2\n"],+ "assertWithMsg_list_assertFailure_twice", [], oneFailTwoAsserts,+ [Utils.Failure "Prefix: Fail Message\n",+ Utils.Failure "Prefix: Fail Message 2\n"]),+ ("assertWithMsg_list_assertSuccess_assertFailure" ~:+ assertWithMsg "Prefix: " [assertSuccess, assertFailure "Fail Message"],+ "assertWithMsg_list_assertSuccess_assertFailure", [], oneFailTwoAsserts,+ [Utils.Failure "Prefix: Fail Message"]),+ ("assertWithMsg_list_assertFailure_assertSuccess" ~:+ assertWithMsg "Prefix: " [assertFailure "Fail Message", assertSuccess],+ "assertWithMsg_list_assertFailure_assertSuccess", [], oneFailTwoAsserts,+ [Utils.Failure "Prefix: Fail Message"]),+ ("assert_Pass" ~: assert Pass, "assert_Pass", [], oneAssert, []),+ ("assert_Fail" ~: assert (Fail "Fail Message"), "assert_Fail", [],+ oneFailOneAssert, [Utils.Failure "Fail Message"]),+ ("assert_Error" ~: assert (Error "Error Message"), "assert_Error", [],+ oneError, [Utils.Error "Error Message"]),+ ("assertWithMsg_Pass" ~: assertWithMsg "Prefix: " Pass,+ "assertWithMsg_Pass", [], oneAssert, []),+ ("assertWithMsg_Fail" ~: assertWithMsg "Prefix: " (Fail "Fail Message"),+ "assertWithMsg_Fail", [], oneFailOneAssert,+ [Utils.Failure "Prefix: Fail Message"]),+ ("assertWithMsg_Error" ~:+ assertWithMsg "Prefix: " (Error "Error Message"),+ "assertWithMsg_Error", [], oneError, [Utils.Error "Prefix: Error Message"]),+ ("assert_eventually_Pass" ~: assert (eventually Pass),+ "assert_eventually_Pass", [], oneAssert, []),+ ("assert_eventually_Fail" ~: assert (eventually (Fail "Fail Message")),+ "assert_eventually_Fail", [], oneFailOneAssert,+ [Utils.Failure "Fail Message"]),+ ("assert_eventually_Error" ~: assert (eventually (Error "Error Message")),+ "assert_eventually_Error", [], oneError, [Utils.Error "Error Message"]),+ ("assertWithMsg_eventually_Pass" ~: assert (eventually Pass),+ "assertWithMsg_eventually_Pass", [], oneAssert, []),+ ("assertWithMsg_eventually_Fail" ~:+ assertWithMsg "Prefix: " (eventually (Fail "Fail Message")),+ "assertWithMsg_eventually_Fail", [], oneFailOneAssert,+ [Utils.Failure "Prefix: Fail Message"]),+ ("assertWithMsg_eventually_Error" ~:+ assertWithMsg "Prefix: " (eventually (Error "Error Message")),+ "assertWithMsg_eventually_Error", [], oneError,+ [Utils.Error "Prefix: Error Message"]),+ -- Monadized assertable instances+ ("assert_return_Unit" ~: assert (return () :: IO ()),+ "assert_return_Unit", [], zeroCounts, []),+ ("assert_return_True" ~: assert (return True :: IO Bool),+ "assert_return_True", [], oneAssert, []),+ ("assert_return_False" ~: assert (return False :: IO Bool),+ "assert_return_False", [], oneFailOneAssert, [Utils.Failure ""]),+ ("assertWithMsg_return_True" ~:+ assertWithMsg "Message" (return True :: IO Bool),+ "assertWithMsg_return_True", [], oneAssert, []),+ ("assertWithMsg_return_False" ~:+ assertWithMsg "Message" (return False :: IO Bool),+ "assertWithMsg_return_False", [], oneFailOneAssert,+ [Utils.Failure "Message"]),+ ("assert_return_String_empty" ~: assert (return "" :: IO String),+ "assert_return_String_empty", [], oneAssert, []),+ ("assert_return_String_nonempty" ~: assert (return "non-empty" :: IO String),+ "assert_return_String_nonempty", [], oneFailOneAssert,+ [Utils.Failure "non-empty"]),+ ("assertWithMsg_return_String_empty" ~:+ assertWithMsg "Prefix: " (return "" :: IO String),+ "assertWithMsg_return_String_empty", [], oneAssert, []),+ ("assertWithMsg_return_String_nonempty" ~:+ assertWithMsg "Prefix: " (return "non-empty" :: IO String),+ "assertWithMsg_return_String_nonempty", [], oneFailOneAssert,+ [Utils.Failure "Prefix: non-empty"]),+ ("assert_return_list_assertSuccess_twice" ~:+ assert (return [assertSuccess, assertSuccess] :: IO [Assertion]),+ "assert_return_list_assertSuccess_twice", [], twoAsserts, []),+ ("assert_return_list_assertFailure_twice" ~:+ assert (return [assertFailure "Fail Message\n",+ assertFailure "Fail Message 2\n"] :: IO [Assertion]),+ "assert_return_list_assertFailure_twice", [], oneFailTwoAsserts,+ [Utils.Failure "Fail Message\n", Utils.Failure "Fail Message 2\n"]),+ ("assert_return_list_assertSuccess_assertFailure" ~:+ assert (return [assertSuccess,+ assertFailure "Fail Message"] :: IO [Assertion]),+ "assert_return_list_assertSuccess_assertFailure", [], oneFailTwoAsserts,+ [Utils.Failure "Fail Message"]),+ ("assert_return_list_assertFailure_assertSuccess" ~:+ assert (return [assertFailure "Fail Message",+ assertSuccess] :: IO [Assertion]),+ "assert_return_list_assertFailure_assertSuccess", [], oneFailTwoAsserts,+ [Utils.Failure "Fail Message"]),+ ("assertWithMsg_return_list_assertSuccess_twice" ~:+ assertWithMsg "Prefix: " (return [assertSuccess, assertSuccess]+ :: IO [Assertion]),+ "assertWithMsg_return_list_assertSuccess_twice", [], twoAsserts, []),+ ("assertWithMsg_return_list_assertFailure_twice" ~:+ assertWithMsg "Prefix: " (return [assertFailure "Fail Message\n",+ assertFailure "Fail Message 2\n"]+ :: IO [Assertion]),+ "assertWithMsg_return_list_assertFailure_twice", [], oneFailTwoAsserts,+ [Utils.Failure "Prefix: Fail Message\n",+ Utils.Failure "Prefix: Fail Message 2\n"]),+ ("assertWithMsg_return_list_assertSuccess_assertFailure" ~:+ assertWithMsg "Prefix: "(return [assertSuccess,+ assertFailure "Fail Message"]+ :: IO [Assertion]),+ "assertWithMsg_return_list_assertSuccess_assertFailure",+ [], oneFailTwoAsserts, [Utils.Failure "Prefix: Fail Message"]),+ ("assertWithMsg_return_list_assertFailure_assertSuccess" ~:+ assertWithMsg "Prefix: " (return [assertFailure "Fail Message",+ assertSuccess] :: IO [Assertion]),+ "assertWithMsg_return_list_assertFailure_assertSuccess",+ [], oneFailTwoAsserts, [Utils.Failure "Prefix: Fail Message"]),+ ("assert_return_Pass" ~: assert (return Pass :: IO Result),+ "assert_return_Pass", [], oneAssert, []),+ ("assert_return_Fail" ~: assert (return (Fail "Fail Message") :: IO Result),+ "assert_return_Fail", [], oneFailOneAssert, [Utils.Failure "Fail Message"]),+ ("assert_return_Error" ~:+ assert (return (Error "Error Message") :: IO Result),+ "assert_return_Error", [], oneError, [Utils.Error "Error Message"]),+ ("assertWithMsg_return_Pass" ~:+ assertWithMsg "Message" (return Pass :: IO Result),+ "assertWithMsg_return_Pass", [], oneAssert, []),+ ("assertWithMsg_return_Fail" ~:+ assertWithMsg "Prefix: " (return (Fail "Fail Message") :: IO Result),+ "assertWithMsg_return_Fail", [], oneFailOneAssert,+ [Utils.Failure "Prefix: Fail Message"]),+ ("assertWithMsg_return_Error" ~:+ assertWithMsg "Prefix: " (return (Error "Error Message") :: IO Result),+ "assertWithMsg_return_Error", [], oneError,+ [Utils.Error "Prefix: Error Message"]),+ ("assert_return_eventually_Pass" ~:+ assert (return (eventually Pass) :: IO Progress),+ "assert_return_eventually_Pass", [], oneAssert, []),+ ("assert_return_eventually_Fail" ~:+ assert (return (eventually (Fail "Fail Message")) :: IO Progress),+ "assert_return_eventually_Fail", [], oneFailOneAssert,+ [Utils.Failure "Fail Message"]),+ ("assert_return_eventually_Error" ~:+ assert (return (eventually (Error "Error Message")) :: IO Progress),+ "assert_return_eventually_Error", [], oneError,+ [Utils.Error "Error Message"]),+ ("assertWithMsg_return_eventually_Pass" ~:+ assertWithMsg "Prefix: " (return (eventually Pass) :: IO Progress),+ "assertWithMsg_return_eventually_Pass", [], oneAssert, []),+ ("assertWithMsg_return_eventually_Fail" ~:+ assertWithMsg "Prefix: " (return (eventually (Fail "Fail Message"))+ :: IO Progress),+ "assertWithMsg_return_eventually_Fail", [], oneFailOneAssert,+ [Utils.Failure "Prefix: Fail Message"]),+ ("assertWithMsg_return_eventually_Error" ~:+ assertWithMsg "Prefix: " (return (eventually (Error "Error Message"))+ :: IO Progress),+ "assertWithMsg_return_eventually_Error", [], oneError,+ [Utils.Error "Prefix: Error Message"]),+ -- Assertion operators+ ("assertWithMsg_operator_Pass" ~: True @? "Message",+ "assertWithMsg_operator_Pass", [], oneAssert, []),+ ("assertWithMsg_operator_False" ~: False @? "Message",+ "assertWithMsg_operator_False", [], oneFailOneAssert,+ [Utils.Failure "Message"]),+ ("assertWithMsg_operator_Fail" ~: Fail "Message" @? "Prefix: ",+ "assertWithMsg_operator_Fail", [], oneFailOneAssert,+ [Utils.Failure "Prefix: Message"]),+ ("assertWithMsg_operator_Error" ~: Error "Message" @? "Prefix: ",+ "assertWithMsg_operator_Error", [], oneError,+ [Utils.Error "Prefix: Message"]),+ ("assertEqual_operator_eq" ~: 10 @=? 10,+ "assertEqual_operator_eq", [], oneAssert, []),+ ("assertEqual_operator_neq" ~: 0 @=? 1, "assertEqual_operator_neq",+ [], oneFailOneAssert, [Utils.Failure "expected: 0\nbut got: 1"]),+ ("assertEqual_operator2_eq" ~: 7 @?= 7,+ "assertEqual_operator2_eq", [], oneAssert, []),+ ("assertEqual_operator2_neq" ~: 11 @=? 8, "assertEqual_operator2_neq",+ [], oneFailOneAssert, [Utils.Failure "expected: 11\nbut got: 8"]),+ -- Test-building functions+ ("HUnit_Test_empty" ~: (return () :: IO ()),+ "HUnit_Test_empty", [], zeroCounts, []),+ ("HUnit_Test_Progress_Pass" ~: (return (Finished Pass) :: IO Progress),+ "HUnit_Test_Progress_Pass", [], oneAssert, []),+ ("HUnit_Test_Progress_Fail" ~:+ (return (Finished (Fail "Fail Message")) :: IO Progress),+ "HUnit_Test_Progress_Fail", [], oneFailOneAssert,+ [Utils.Failure "Fail Message"]),+ ("HUnit_Test_Progress_Error" ~:+ (return (Finished (Error "Error Message")) :: IO Progress),+ "HUnit_Test_Progress_Error", [], oneError, [Utils.Error "Error Message"]),+ ("HUnit_Test_Progress_eventually_Pass" ~:+ (return (eventually Pass) :: IO Progress),+ "HUnit_Test_Progress_eventually_Pass", [], oneAssert, []),+ ("HUnit_Test_Progress_eventually_Fail" ~:+ (return (eventually (Fail "Fail Message")) :: IO Progress),+ "HUnit_Test_Progress_eventually_Fail", [], oneFailOneAssert,+ [Utils.Failure "Fail Message"]),+ ("HUnit_Test_Progress_eventually_Error" ~:+ (return (eventually (Error "Error Message")) :: IO Progress),+ "HUnit_Test_Progress_eventually_Error", [], oneError,+ [Utils.Error "Error Message"]),+ ("HUnit_Test_Bool_True" ~: (return True :: IO Bool),+ "HUnit_Test_Bool_True", [], oneAssert, []),+ ("HUnit_Test_Bool_False" ~: (return False :: IO Bool),+ "HUnit_Test_Bool_False", [], oneFailOneAssert, [Utils.Failure ""]),+ ("HUnit_Test_with_msg_True" ~: (True ~? "Failure Message"),+ "HUnit_Test_with_msg_True", [], oneAssert, []),+ ("HUnit_Test_with_msg_False" ~: (False ~? "Fail Message"),+ "HUnit_Test_with_msg_False", [], oneFailOneAssert,+ [Utils.Failure "Fail Message"]),+ ("HUnit_Eq_Test_eq" ~: (4 ~?= 4), "HUnit_Eq_Test_eq", [], oneAssert, []),+ ("HUnit_Eq_Test_neq" ~: (4 ~?= 5), "HUnit_Eq_Test_neq", [],+ oneFailOneAssert, [Utils.Failure "expected: 5\nbut got: 4"]),+ ("HUnit_Eq_Test2_eq" ~: (3 ~=? 3), "HUnit_Eq_Test2_eq", [], oneAssert, []),+ ("HUnit_Eq_Test2_neq" ~: (3 ~=? 2), "HUnit_Eq_Test2_neq", [],+ oneFailOneAssert, [Utils.Failure "expected: 3\nbut got: 2"]),+ (externalTestPass, "externalTestPass", [], zeroCounts, []),+ (externalTestEventuallyPass, "externalTestEventuallyPass", [], zeroCounts,+ [Utils.Progress ".", Utils.Progress "..", Utils.Progress "..."]),+ (externalTestFail, "externalTestFail", [], oneFail,+ [Utils.Failure "External Test Failed"]),+ (externalTestEventuallyFail, "externalTestEventuallyFail", [], oneFail,+ [Utils.Progress ".", Utils.Progress "..", Utils.Progress "...",+ Utils.Failure "External Test Failed"]),+ (externalTestError, "externalTestError", [], oneError,+ [Utils.Error "External Test Error"]),+ (externalTestEventuallyError, "externalTestEventuallyError", [], oneError,+ [Utils.Progress ".", Utils.Progress "..", Utils.Progress "...",+ Utils.Error "External Test Error"]),+ (externalTestException, "externalTestException", [], oneError,+ [Utils.Exception "Exception Message"]),+ -- Test manipulation of tags and test names+ (testName "newName" (test (return () :: IO ())),+ "newName", [], zeroCounts, []),+ (testTags ["tag", "gat"] ("newTags" ~: (return () :: IO ())),+ "newTags", ["tag", "gat"], zeroCounts, []),+ (testNameTags "newNameTags" ["tag", "gat"] (test (return () :: IO ())),+ "newNameTags", ["tag", "gat"], zeroCounts, [])+ ]+{-+miscTests :: [Test]+miscTests =+ let+ anonTest1 = test True+ anonTest2 = test True++ tagsTest1 = testTags ["tag"] True+ tagsTest2 = testTags ["tag", "gat"] True++ nameTest = testName "test1" True++ anonTestCreate =+ TestInstance { name = "anonTestCreate", tags = [], options = [],+ setOption = (\_ _ -> anonTestCreate),+ run = if (name anonTest1) /= (name anonTest2)+ then return (Finished Pass)+ else+ return (Finished (Fail "expected unique names")) }+ tagsTestCreate+ in+ [ Test anontestCreate, Test tagsTestCreate ]+-}+tests :: Test+tests = testGroup "Base" (map makeTestCase testCases)
+ test/Tests/Test/HUnitPlus/Execution.hs view
@@ -0,0 +1,667 @@+{-# LANGUAGE DeriveDataTypeable, OverloadedStrings #-}+module Tests.Test.HUnitPlus.Execution where++import Control.Exception(Exception, throwIO)+import Data.List+import Data.HashMap.Strict(HashMap)+import Data.Maybe+import Data.Typeable+import Distribution.TestSuite+import Test.HUnitPlus.Base+import Test.HUnitPlus.Execution+import Test.HUnitPlus.Filter+import Test.HUnitPlus.Reporting++import qualified Data.HashSet as HashSet+import qualified Data.HashMap.Strict as HashMap+import qualified Data.Text as Strict++data ReportEvent =+ EndEvent Counts+ | StartSuiteEvent State+ | EndSuiteEvent State+ | StartCaseEvent State+ | EndCaseEvent State+ | SkipEvent State+ | ProgressEvent Strict.Text State+ | FailureEvent Strict.Text State+ | ErrorEvent Strict.Text State+ | SystemErrEvent Strict.Text State+ | SystemOutEvent Strict.Text State+ deriving (Eq, Show)++fullLoggingReporter :: Reporter [ReportEvent]+fullLoggingReporter = defaultReporter {+ reporterStart = return [],+ reporterEnd =+ \_ ss events -> return $ (EndEvent ss :events),+ reporterStartSuite =+ \ss events -> return $ (StartSuiteEvent ss : events),+ reporterEndSuite =+ \_ ss events -> return $ (EndSuiteEvent ss : events),+ reporterStartCase =+ \ss events -> return $ (StartCaseEvent ss : events),+ reporterEndCase =+ \_ ss events -> return $ (EndCaseEvent ss : events),+ reporterSkipCase =+ \ss events -> return $ (SkipEvent ss : events),+ reporterCaseProgress =+ \msg ss events -> return $ (ProgressEvent msg ss : events),+ reporterFailure =+ \msg ss events -> return $ (FailureEvent msg ss : events),+ reporterError =+ \msg ss events -> return $ (ErrorEvent msg ss : events),+ reporterSystemErr =+ \msg ss events -> return $ (SystemErrEvent msg ss : events),+ reporterSystemOut =+ \msg ss events -> return $ (SystemOutEvent msg ss : events)+ }++makeTagName False False = "no_tag"+makeTagName True False = "tag1"+makeTagName False True = "tag2"+makeTagName True True = "tag12"++makeTagSet (False, False) = HashSet.empty+makeTagSet (True, False) = HashSet.singleton "tag1"+makeTagSet (False, True) = HashSet.singleton "tag2"+makeTagSet (True, True) = HashSet.fromList ["tag1", "tag2"]++data TestException = TestException+ deriving (Show, Typeable)++instance Exception TestException++data Behavior = Normal Result | Exception++makeResName (Normal Pass) = "pass"+makeResName (Normal (Fail _)) = "fail"+makeResName (Normal (Error _)) = "error"+makeResName Exception = "exception"++makeAssert (Normal Pass) = assertSuccess+makeAssert (Normal (Fail msg)) = assertFailure (Strict.pack msg)+makeAssert (Normal (Error msg)) = abortError (Strict.pack msg)+makeAssert Exception = throwIO TestException++updateCounts (Normal Pass) c @ Counts { cAsserts = asserts } =+ c { cAsserts = asserts + 1, cCaseAsserts = 1 }+updateCounts (Normal (Fail _)) c @ Counts { cFailures = fails,+ cAsserts = asserts } =+ c { cFailures = fails + 1, cAsserts = asserts + 1, cCaseAsserts = 1 }+updateCounts (Normal (Error _)) c @ Counts { cErrors = errors } =+ c { cErrors = errors + 1, cCaseAsserts = 0 }+updateCounts Exception c @ Counts { cErrors = errors } =+ c { cErrors = errors + 1, cCaseAsserts = 0 }++makeName :: (Bool, Bool, Behavior) -> Strict.Text+makeName (tag1, tag2, res) =+ Strict.concat [makeTagName tag1 tag2, "_", makeResName res]++makeTest :: String -> (Bool, Bool, Behavior) -> Test+makeTest prefix tdata @ (tag1, tag2, res) =+ let+ inittags = if tag1 then ["tag1"] else []+ tags = if tag2 then "tag2" : inittags else inittags+ testname = prefix ++ (Strict.unpack (makeName tdata))+ in+ testNameTags testname tags (makeAssert res)+{-++ testInstance = TestInstance { name = testname, tags = tags,+ setOption = (\_ _ -> Right testInstance),+ options = [], run = runTest }+ in+ Test testInstance+-}+makeTestData :: String -> ([Test], [ReportEvent], State) ->+ Either (Bool, Bool, Behavior) (Bool, Bool, Behavior) ->+ ([Test], [ReportEvent], State)+makeTestData prefix+ (tests, events,+ ss @ State { stName = oldname,+ stCounts = counts @ Counts { cCases = cases,+ cTried = tried } })+ (Right tdata @ (tag1, tag2, res)) =+ let+ startedCounts = counts { cCases = cases + 1, cTried = tried + 1 }+ finishedCounts = updateCounts res startedCounts+ ssWithName = ss { stName = Strict.concat [Strict.pack prefix, makeName tdata] }+ ssStarted = ssWithName { stCounts = startedCounts }+ ssFinished = ssWithName { stCounts = finishedCounts }+ -- Remember, the order is reversed for these, because we reverse+ -- the events list in the end.+ newevents =+ case res of+ Normal Pass ->+ EndCaseEvent ssFinished : StartCaseEvent ssStarted : events+ Normal (Fail msg) ->+ EndCaseEvent ssFinished : FailureEvent (Strict.pack msg) ssStarted :+ StartCaseEvent ssStarted : events+ Normal (Error msg) ->+ EndCaseEvent ssFinished : ErrorEvent (Strict.pack msg) ssStarted :+ StartCaseEvent ssStarted : events+ Exception ->+ EndCaseEvent ssFinished :+ ErrorEvent "Uncaught exception in test: TestException" ssStarted :+ StartCaseEvent ssStarted : events+ in+ (makeTest prefix tdata : tests, newevents,+ ssFinished { stName = oldname })+makeTestData prefix+ (tests, events, ss @ State { stCounts =+ c @ Counts { cSkipped = skipped,+ cCases = cases },+ stName = oldname })+ (Left tdata) =+ let+ newcounts = c { cCases = cases + 1, cSkipped = skipped + 1 }+ newstate = ss { stCounts = newcounts,+ stName = Strict.concat [Strict.pack prefix, makeName tdata] }+ in+ (makeTest prefix tdata : tests, SkipEvent newstate : events,+ newstate { stName = oldname })++resultVals :: [Behavior]+resultVals = [Normal Pass, Normal (Fail "Fail Message"),+ Normal (Error "Error Message"), Exception]+++tagVals :: [Bool]+tagVals = [True, False]++testData :: [(Bool, Bool, Behavior)]+testData = foldl (\accum tag1 ->+ foldl (\accum tag2 ->+ foldl (\accum res -> (tag1, tag2, res) : accum)+ accum resultVals)+ accum tagVals)+ [] tagVals++tag1Filter tdata @ (True, _, _) = Right tdata+tag1Filter tdata = Left tdata++tag2Filter tdata @ (_, True, _) = Right tdata+tag2Filter tdata = Left tdata++tag12Filter tdata @ (True, _, _) = Right tdata+tag12Filter tdata @ (_, True, _) = Right tdata+tag12Filter tdata = Left tdata++data ModFilter = All | WithTags (Bool, Bool) | None deriving Show++getTests :: ModFilter -> [Either (Bool, Bool, Behavior) (Bool, Bool, Behavior)]+getTests All = map Right testData+getTests (WithTags (True, False)) = map tag1Filter testData+getTests (WithTags (False, True)) = map tag2Filter testData+getTests (WithTags (True, True)) = map tag12Filter testData+getTests None = map Left testData++-- Generate a list of all mod filters we can use for a sub-module, and+-- the selectors we need for them+getSuperSet :: (Selector -> Selector) -> ModFilter ->+ [(ModFilter, Selector, Bool)]+-- If we're already running all tests, there's nothing else we can do+getSuperSet wrapinner All =+ [(All, wrapinner (allSelector { selectorTags = Nothing }), False)]+-- If we're running tests with both tags, we can do that, or we can+-- run all tests in the submodule.+getSuperSet wrapinner (WithTags (True, True)) =+ [(WithTags (True, True),+ wrapinner (allSelector { selectorTags = Nothing }), False),+ (All, wrapinner allSelector, True)]+-- If we're running tests with one of the tags, we can do that, or we+-- can run with both tags, or we can run all tests.+getSuperSet wrapinner (WithTags (False, True)) =+ [(WithTags (False, True),+ wrapinner (allSelector { selectorTags = Nothing }), False),+ (WithTags (True, True),+ wrapinner (allSelector { selectorTags =+ Just $! HashSet.fromList ["tag1", "tag2" ] }),+ True),+ (All, wrapinner allSelector, True) ]+getSuperSet wrapinner (WithTags (True, False)) =+ [(WithTags (True, False),+ wrapinner (allSelector { selectorTags = Nothing }), False),+ (WithTags (True, True),+ wrapinner (allSelector { selectorTags =+ Just $! HashSet.fromList ["tag1", "tag2" ] }),+ True),+ (All, wrapinner allSelector, True) ]+-- If we're not running any tests, we can do anything+getSuperSet wrapinner None =+ [(None, wrapinner (allSelector { selectorTags = Nothing }), False),+ (WithTags (True, False),+ wrapinner (allSelector { selectorTags = Just $! HashSet.singleton "tag1" }),+ True),+ (WithTags (False, True),+ wrapinner (allSelector { selectorTags = Just $! HashSet.singleton "tag2" }),+ True),+ (WithTags (True, True),+ wrapinner (allSelector { selectorTags =+ Just $! HashSet.fromList ["tag1", "tag2" ] }),+ True),+ (All, wrapinner allSelector, True) ]++-- Make the tests for a group, with a starting modfilter+makeLeafGroup :: String -> (Selector -> Selector) -> ModFilter ->+ ([Test], [ReportEvent], State, [Selector]) ->+ [([Test], [ReportEvent], State, [Selector])]+makeLeafGroup gname wrapinner mfilter initialTests =+ let+ mapfun :: ([Test], [ReportEvent], State, [Selector]) ->+ (ModFilter, Selector, Bool) ->+ ([Test], [ReportEvent], State, [Selector])+ mapfun (tests, events, ss @ State { stPath = oldpath }, selectors)+ (mfilter, selector, valid) =+ let+ ssWithPath = ss { stPath = Label (Strict.pack gname) : oldpath }+ (grouptests, events', ss') =+ foldl (makeTestData (gname ++ "_"))+ ([], events, ssWithPath)+ (getTests mfilter)+ tests' = Group { groupName = gname, groupTests = reverse grouptests,+ concurrently = True } : tests+ in+ if valid+ then (tests', events', ss' { stPath = oldpath }, selector : selectors)+ else (tests', events', ss' { stPath = oldpath }, selectors)+ in+ map (mapfun initialTests) (getSuperSet wrapinner mfilter)++makeOuterGroup :: ModFilter -> ([Test], [ReportEvent], State, [Selector]) ->+ [([Test], [ReportEvent], State, [Selector])]+makeOuterGroup mfilter initialTests =+ let+ wrapOuterPath inner =+ Selector { selectorInners = HashMap.singleton "Outer" inner,+ selectorTags = Nothing }++ mapfun :: ([Test], [ReportEvent], State, [Selector]) ->+ (ModFilter, Selector, Bool) ->+ [([Test], [ReportEvent], State, [Selector])]+ mapfun (tests, events, ss @ State { stPath = oldpath }, selectors)+ (mfilter, selector, valid) =+ let+ ssWithPath = ss { stPath = Label "Outer" : oldpath }++ mapfun :: ([Test], [ReportEvent], State, [Selector]) ->+ ([Test], [ReportEvent], State, [Selector])+ mapfun (innergroup : tests, events, ss, selectors) =+ let+ (grouptests, events', ss') = foldl (makeTestData "Outer_")+ ([innergroup], events, ss)+ (getTests mfilter)++ tests' = Group { groupName = "Outer",+ groupTests = reverse grouptests,+ concurrently = True } : tests+ in+ if valid+ then (tests', events', ss' { stPath = oldpath },+ selector : selectors)+ else (tests', events', ss' { stPath = oldpath }, selectors)++ wrapInnerPath inner =+ Selector {+ selectorInners =+ HashMap.singleton "Outer" Selector {+ selectorInners =+ HashMap.singleton "Inner" inner,+ selectorTags = Nothing+ },+ selectorTags = Nothing+ }++ withInner :: [([Test], [ReportEvent], State, [Selector])]+ withInner = makeLeafGroup "Inner" wrapInnerPath mfilter+ (tests, events, ssWithPath, selectors)+ in+ map mapfun withInner++ in+ concatMap (mapfun initialTests) (getSuperSet wrapOuterPath mfilter)++modfilters = [ All, WithTags (True, False), WithTags (False, True),+ WithTags (True, True), None ]++genFilter :: Strict.Text+ -> [(TestSuite, [ReportEvent],+ HashMap Strict.Text (HashMap OptionMap Selector), Counts)]+genFilter sname =+ let+ -- Take a root ModFilter and an initial (suite list, event list,+ -- selectors). We generate a stock suite, derive a selector from+ -- the root ModFilter, and produce a list of possible (suite list,+ -- event list, selectors)'s, one for each possibility.+ suiteTestInst :: ModFilter+ -> [(TestSuite, [ReportEvent],+ HashMap Strict.Text (HashMap OptionMap Selector),+ Counts)]+ suiteTestInst mfilter =+ let+ -- Initial state for a filter+ initState = State { stCounts = zeroCounts, stName = sname,+ stPath = [], stOptions = HashMap.empty,+ stOptionDescs = [] }++ -- The selectors for the root set+ rootSelectors :: [Selector]+ rootSelectors =+ case mfilter of+ All -> [allSelector]+ WithTags tags ->+ [allSelector { selectorTags = Just $! makeTagSet tags }]+ None -> []++ -- Result after executing the root tests.+ (rootTests, rootEvents, rootState) =+ foldl (makeTestData "") ([], [StartSuiteEvent initState], initState)+ (getTests mfilter)++ wrapOtherPath inner =+ Selector { selectorInners = HashMap.singleton "Other" inner,+ selectorTags = Nothing }++ -- Results after executing tests in the Other module+ withOther :: [([Test], [ReportEvent], State, [Selector])]+ withOther = makeLeafGroup "Other" wrapOtherPath mfilter+ (rootTests, rootEvents,+ rootState, rootSelectors)++ finalData = concatMap (makeOuterGroup mfilter) withOther++ -- Wrap up a test list, end state, and selector list into a+ -- test suite and a filter. Also add the EndSuite event to+ -- the events list.+ buildSuite :: ([Test], [ReportEvent], State, [Selector]) ->+ (TestSuite, [ReportEvent],+ HashMap Strict.Text (HashMap OptionMap Selector),+ Counts)+ buildSuite (tests, _, _, []) =+ let+ suite =+ TestSuite { suiteName = sname, suiteTests = reverse tests,+ suiteConcurrently = True, suiteOptions = [] }+ in+ (suite, [], HashMap.empty, zeroCounts)+ buildSuite (tests, events, state @ State { stCounts = counts },+ selectors) =+ let+ -- Build the test suite out of the name and test list, add+ -- it to the list of suites.+ suite =+ TestSuite { suiteName = sname, suiteTests = reverse tests,+ suiteConcurrently = True, suiteOptions = [] }++ -- Add an end suite event+ eventsWithEnd = EndSuiteEvent state : events++ -- Add an entry for this suite to the selector map+ selectormap :: HashMap Strict.Text (HashMap OptionMap Selector)+ selectormap =+ case selectors of+ [one] ->+ let+ optmap = HashMap.singleton HashMap.empty one+ in+ HashMap.singleton sname optmap+ _ ->+ let+ combined = foldl1 combineSelectors selectors+ optmap = HashMap.singleton HashMap.empty combined+ in+ HashMap.singleton sname optmap+ in+ (suite, reverse eventsWithEnd, selectormap, counts)+ in+ map buildSuite finalData+ in+ -- Create test data for this suite with all possible modfilters,+ -- and add it to the existing list of test instances.+ concatMap suiteTestInst modfilters++suite1Data :: [(TestSuite, [ReportEvent],+ HashMap Strict.Text (HashMap OptionMap Selector), Counts)]+suite1Data = genFilter "Suite1"++suite2Data :: [(TestSuite, [ReportEvent],+ HashMap Strict.Text (HashMap OptionMap Selector), Counts)]+suite2Data = genFilter "Suite2"++combineSuites :: (TestSuite, [ReportEvent],+ HashMap Strict.Text (HashMap OptionMap Selector), Counts) ->+ (TestSuite, [ReportEvent],+ HashMap Strict.Text (HashMap OptionMap Selector), Counts) ->+ ([TestSuite], [ReportEvent],+ HashMap Strict.Text (HashMap OptionMap Selector))+combineSuites (suite1, events1, selectormap1, Counts { cAsserts = asserts1,+ cCases = cases1,+ cErrors = errors1,+ cFailures = failures1,+ cSkipped = skipped1,+ cTried = tried1 })+ (suite2, events2, selectormap2, counts2) =+ let+ bumpCounts (EndEvent c @ Counts { cAsserts = asserts2,+ cCases = cases2,+ cErrors = errors2,+ cFailures = failures2,+ cSkipped = skipped2,+ cTried = tried2 }) =+ EndEvent c { cAsserts = asserts1 + asserts2,+ cErrors = errors1 + errors2,+ cCases = cases1 + cases2,+ cFailures = failures1 + failures2,+ cSkipped = skipped1 + skipped2,+ cTried = tried1 + tried2 }+ bumpCounts (StartSuiteEvent s @ State { stCounts =+ c @ Counts { cAsserts = asserts2,+ cCases = cases2,+ cErrors = errors2,+ cFailures = failures2,+ cSkipped = skipped2,+ cTried = tried2 } }) =+ StartSuiteEvent s { stCounts = c { cAsserts = asserts1 + asserts2,+ cErrors = errors1 + errors2,+ cCases = cases1 + cases2,+ cFailures = failures1 + failures2,+ cSkipped = skipped1 + skipped2,+ cTried = tried1 + tried2 } }+ bumpCounts (EndSuiteEvent s @ State { stCounts =+ c @ Counts { cAsserts = asserts2,+ cCases = cases2,+ cErrors = errors2,+ cFailures = failures2,+ cSkipped = skipped2,+ cTried = tried2 } }) =+ EndSuiteEvent s { stCounts = c { cAsserts = asserts1 + asserts2,+ cErrors = errors1 + errors2,+ cCases = cases1 + cases2,+ cFailures = failures1 + failures2,+ cSkipped = skipped1 + skipped2,+ cTried = tried1 + tried2 } }+ bumpCounts (StartCaseEvent s @ State { stCounts =+ c @ Counts { cAsserts = asserts2,+ cCases = cases2,+ cErrors = errors2,+ cFailures = failures2,+ cSkipped = skipped2,+ cTried = tried2 } }) =+ StartCaseEvent s { stCounts = c { cAsserts = asserts1 + asserts2,+ cErrors = errors1 + errors2,+ cCases = cases1 + cases2,+ cFailures = failures1 + failures2,+ cSkipped = skipped1 + skipped2,+ cTried = tried1 + tried2 } }+ bumpCounts (EndCaseEvent s @ State { stCounts =+ c @ Counts { cAsserts = asserts2,+ cCases = cases2,+ cErrors = errors2,+ cFailures = failures2,+ cSkipped = skipped2,+ cTried = tried2 } }) =+ EndCaseEvent s { stCounts = c { cAsserts = asserts1 + asserts2,+ cErrors = errors1 + errors2,+ cCases = cases1 + cases2,+ cFailures = failures1 + failures2,+ cSkipped = skipped1 + skipped2,+ cTried = tried1 + tried2 } }+ bumpCounts (SkipEvent s @ State { stCounts =+ c @ Counts { cAsserts = asserts2,+ cCases = cases2,+ cErrors = errors2,+ cFailures = failures2,+ cSkipped = skipped2,+ cTried = tried2 } }) =+ SkipEvent s { stCounts = c { cAsserts = asserts1 + asserts2,+ cErrors = errors1 + errors2,+ cCases = cases1 + cases2,+ cFailures = failures1 + failures2,+ cSkipped = skipped1 + skipped2,+ cTried = tried1 + tried2 } }+ bumpCounts (ProgressEvent msg s @ State { stCounts =+ c @ Counts { cAsserts = asserts2,+ cCases = cases2,+ cErrors = errors2,+ cFailures = failures2,+ cSkipped = skipped2,+ cTried = tried2 } }) =+ ProgressEvent msg s { stCounts = c { cAsserts = asserts1 + asserts2,+ cErrors = errors1 + errors2,+ cCases = cases1 + cases2,+ cFailures = failures1 + failures2,+ cSkipped = skipped1 + skipped2,+ cTried = tried1 + tried2 } }+ bumpCounts (FailureEvent msg s @ State { stCounts =+ c @ Counts { cAsserts = asserts2,+ cCases = cases2,+ cErrors = errors2,+ cFailures = failures2,+ cSkipped = skipped2,+ cTried = tried2 } }) =+ FailureEvent msg s { stCounts = c { cAsserts = asserts1 + asserts2,+ cErrors = errors1 + errors2,+ cCases = cases1 + cases2,+ cFailures = failures1 + failures2,+ cSkipped = skipped1 + skipped2,+ cTried = tried1 + tried2 } }+ bumpCounts (ErrorEvent msg s @ State { stCounts =+ c @ Counts { cAsserts = asserts2,+ cCases = cases2,+ cErrors = errors2,+ cFailures = failures2,+ cSkipped = skipped2,+ cTried = tried2 } }) =+ ErrorEvent msg s { stCounts = c { cAsserts = asserts1 + asserts2,+ cErrors = errors1 + errors2,+ cCases = cases1 + cases2,+ cFailures = failures1 + failures2,+ cSkipped = skipped1 + skipped2,+ cTried = tried1 + tried2 } }+ bumpCounts (SystemErrEvent msg s @ State { stCounts =+ c @ Counts { cAsserts = asserts2,+ cCases = cases2,+ cErrors = errors2,+ cFailures = failures2,+ cSkipped = skipped2,+ cTried = tried2 } }) =+ SystemErrEvent msg s { stCounts = c { cAsserts = asserts1 + asserts2,+ cErrors = errors1 + errors2,+ cCases = cases1 + cases2,+ cFailures = failures1 + failures2,+ cSkipped = skipped1 + skipped2,+ cTried = tried1 + tried2 } }+ bumpCounts (SystemOutEvent msg s @ State { stCounts =+ c @ Counts { cAsserts = asserts2,+ cCases = cases2,+ cErrors = errors2,+ cFailures = failures2,+ cSkipped = skipped2,+ cTried = tried2 } }) =+ SystemOutEvent msg s { stCounts = c { cAsserts = asserts1 + asserts2,+ cErrors = errors1 + errors2,+ cCases = cases1 + cases2,+ cFailures = failures1 + failures2,+ cSkipped = skipped1 + skipped2,+ cTried = tried1 + tried2 } }++ suites = [suite1, suite2]+ events = events1 ++ map bumpCounts events2 +++ [bumpCounts (EndEvent counts2 { cCaseAsserts = 0 })]+ selectormap = HashMap.union selectormap1 selectormap2+ in+ (suites, events, selectormap)+++suiteData :: [([TestSuite], [ReportEvent],+ HashMap Strict.Text (HashMap OptionMap Selector))]+suiteData = foldl (\accum suite1 ->+ foldl (\accum suite2 ->+ (combineSuites suite1 suite2) : accum)+ accum suite2Data)+ [] suite1Data++makeExecutionTest :: ([TestSuite], [ReportEvent],+ HashMap Strict.Text (HashMap OptionMap Selector)) ->+ (Int, [Test]) -> (Int, [Test])+makeExecutionTest (suites, expected, selectors) (index, tests) =+ let+ format events = intercalate "\n" (map show events)++ selectorStrs =+ intercalate "\n" (map (\(suite, selector) -> "[" ++ Strict.unpack suite +++ "]" ++ show selector)+ (HashMap.toList selectors))++ compstate State { stName = name1, stPath = path1, stCounts = counts1,+ stOptionDescs = descs1 }+ State { stName = name2, stPath = path2, stCounts = counts2,+ stOptionDescs = descs2 } =+ name1 == name2 && path1 == path2 && counts1 == counts2 && descs1 == descs2++ comp (EndEvent counts1) (EndEvent counts2) = counts1 == counts2+ comp (StartSuiteEvent st1) (StartSuiteEvent st2) = compstate st1 st2+ comp (EndSuiteEvent st1) (EndSuiteEvent st2) = compstate st1 st2+ comp (StartCaseEvent st1) (StartCaseEvent st2) = compstate st1 st2+ comp (EndCaseEvent st1) (EndCaseEvent st2) = compstate st1 st2+ comp (SkipEvent st1) (SkipEvent st2) = compstate st1 st2+ comp (ProgressEvent msg1 st1) (ProgressEvent msg2 st2) =+ msg1 == msg2 && compstate st1 st2+ comp (FailureEvent msg1 st1) (FailureEvent msg2 st2) =+ msg1 == msg2 && compstate st1 st2+ comp (ErrorEvent msg1 st1) (ErrorEvent msg2 st2) =+ msg1 == msg2 && compstate st1 st2+ comp (SystemErrEvent msg1 st1) (SystemErrEvent msg2 st2) =+ msg1 == msg2 && compstate st1 st2+ comp (SystemOutEvent msg1 st1) (SystemOutEvent msg2 st2) =+ msg1 == msg2 && compstate st1 st2+ comp _ _ = False++ check (e : expecteds) (a : actuals)+ | comp e a = check expecteds actuals+ | otherwise =+ return (Finished (Fail ("Selectors\n" ++ selectorStrs +++ "\nExpected\n************************\n" +++ show e +++ "\nbut got\n************************\n" +++ show a)))+ check [] [] = return (Finished Pass)+ check expected [] =+ return (Finished (Fail ("Missing output:\n" ++ format expected)))+ check [] actual =+ return (Finished (Fail ("Extra output:\n" ++ format actual)))++ runTest =+ do+ (_, actual) <- performTestSuites fullLoggingReporter selectors suites+ check expected (reverse actual)++ testInstance = TestInstance { name = "execution_test_" ++ show index,+ tags = [], options = [], run = runTest,+ setOption = (\_ _ -> Right testInstance) }+ in+ (index + 1, Test testInstance : tests)++tests :: Test+tests = testGroup "Execution" (snd (foldr makeExecutionTest (0, []) suiteData))
+ test/Tests/Test/HUnitPlus/Filter.hs view
@@ -0,0 +1,1006 @@+{-# LANGUAGE OverloadedStrings #-}++module Tests.Test.HUnitPlus.Filter(tests) where++import Data.List+import Data.HashMap.Strict(HashMap)+import Distribution.TestSuite+import Test.HUnitPlus.Filter++import qualified Data.HashMap.Strict as HashMap+import qualified Data.HashSet as HashSet+import qualified Data.Text as Strict++suiteNames :: [[String]]+suiteNames = [[], ["Suite1"], ["Suite1"], ["Suite1", "Suite2"]]++paths :: [[String]]+paths = [[], ["Outer"], ["Outer", "Middle"], ["Outer", "Middle", "Inner"],+ ["Underscore_path"], ["t__", "__u__", "__v"]]++tagNames :: [[String]]+tagNames = [[], ["tag1"], ["tag2"], ["tag1", "tag2"], ["underscore_tag"],+ ["t__", "_u__", "__v"]]++suiteString :: [String] -> String+suiteString [] = ""+suiteString suites = "[" ++ intercalate "," suites ++ "]"++pathString :: [String] -> String+pathString [] = ""+pathString path = intercalate "." path++tagsString :: [String] -> String+tagsString [] = ""+tagsString tags = '@' : intercalate "," tags++makeFilterString :: [String] -> [String] -> [String] -> String+makeFilterString suites path tags =+ suiteString suites ++ pathString path ++ tagsString tags++tagsSelector :: [String] -> Selector+tagsSelector [] = allSelector+tagsSelector tags =+ allSelector { selectorTags = Just $! HashSet.fromList (map Strict.pack tags) }++pathSelector :: Selector -> [Strict.Text] -> Selector+pathSelector inner [] = inner+pathSelector inner (elem : path) =+ Selector { selectorInners = HashMap.singleton elem (pathSelector inner path),+ selectorTags = Nothing }++makeFilter :: [Strict.Text] -> Selector -> Filter+makeFilter names selector =+ Filter { filterSuites = HashSet.fromList names, filterOptions = HashMap.empty,+ filterSelector = HashSet.singleton selector }++suitesName :: [String] -> String+suitesName [] = "no_suites"+suitesName suites = intercalate "_" suites++pathName :: [String] -> String+pathName [] = "no_path"+pathName path = intercalate "_" path++tagsName :: [String] -> String+tagsName [] = "no_tags"+tagsName tags = intercalate "_" tags++makeFilterParseTest :: [String] -> [String] -> [String] -> Test+makeFilterParseTest suites path tags =+ let+ name = suitesName suites ++ "__" ++ pathName path ++ "__" ++ tagsName tags+ string = suiteString suites ++ pathString path ++ tagsString tags+ expected = makeFilter (map Strict.pack suites)+ (pathSelector (tagsSelector tags)+ (map Strict.pack path))++ runTest :: IO Progress+ runTest =+ do case parseFilter "test input" (Strict.pack string) of+ Left e -> return (Finished (Fail ("Parse of " ++ string +++ " failed: " ++ (Strict.unpack e))))+ Right actual+ | expected == actual -> return (Finished Pass)+ | otherwise ->+ return (Finished (Fail ("In parse of " ++ string +++ "\nexpected " ++ show expected +++ "\nactual " ++ show actual)))++ testInstance = TestInstance { name = name, run = runTest,+ tags = [], options = [],+ setOption = (\_ _ -> Right testInstance) }+ in+ Test testInstance++filterComponents :: [([String], [String], [String])]+filterComponents =+ foldl (\tests suiteName ->+ foldl (\tests path ->+ foldl (\tests tag -> (suiteName, path, tag) : tests)+ tests tagNames)+ tests paths)+ [] suiteNames++whitespaceStrings :: [String]+whitespaceStrings = [ "", " ", "\t" ]++commentStrings = [ "#", "# [Suite]"]++filterParseTests :: [Test]+filterParseTests = map (\(suiteName, path, tag) ->+ makeFilterParseTest suiteName path tag)+ filterComponents++makeFileParserTest :: String -> String -> [Filter] -> Test+makeFileParserTest name content expected =+ let+ runTest :: IO Progress+ runTest =+ do case parseFilterFileContent name (Strict.pack content) of+ Left e -> return (Finished (Fail ("Parse of\n************\n" +++ content +++ "\n************\nfailed: " +++ concat (map Strict.unpack e))))+ Right actual+ | expected == actual -> return (Finished Pass)+ | otherwise ->+ return (Finished (Fail ("In parse of\n************\n" +++ content +++ "\n************\nexpected " +++ show expected ++ "\nactual " +++ show actual)))++ testInstance =+ TestInstance { name = name, tags = [], run = runTest, options = [],+ setOption = (\_ _ -> Right testInstance) }+ in+ Test testInstance++fileTests :: [(String, String, [Filter])]+fileTests =+ let+ simplePath =+ Selector {+ selectorTags = Nothing,+ selectorInners =+ HashMap.singleton "Outer"+ Selector { selectorTags = Nothing,+ selectorInners = HashMap.singleton "Inner" allSelector }+ }+ simplePathStr = "Outer.Inner"++ onlyTags = allSelector { selectorTags =+ Just $! HashSet.fromList ["tag1", "tag2"] }+ onlyTagsStr = "@tag1,tag2"+ pathTags =+ Selector {+ selectorTags = Nothing,+ selectorInners =+ HashMap.singleton "Outer"+ Selector { selectorTags = Nothing,+ selectorInners = HashMap.singleton "Inner" onlyTags }+ }+ pathTagsStr = "Outer.Inner@tag1,tag2"+ suiteAllFilter = Filter { filterSuites = HashSet.fromList ["Suite1",+ "Suite2"],+ filterOptions = HashMap.empty,+ filterSelector = HashSet.singleton allSelector }+ suiteFilterStr = "[Suite1,Suite2]"+ simplePathFilter = Filter { filterSuites = HashSet.empty,+ filterOptions = HashMap.empty,+ filterSelector = HashSet.singleton simplePath }+ suitePathFilter = Filter { filterSuites = HashSet.fromList ["Suite1",+ "Suite2"],+ filterOptions = HashMap.empty,+ filterSelector = HashSet.singleton simplePath }+ suitePathStr = suiteFilterStr ++ simplePathStr+ onlyTagsFilter = Filter { filterSuites = HashSet.empty,+ filterOptions = HashMap.empty,+ filterSelector = HashSet.singleton onlyTags }+ suiteTagsFilter = Filter { filterSuites = HashSet.fromList ["Suite1",+ "Suite2"],+ filterOptions = HashMap.empty,+ filterSelector = HashSet.singleton onlyTags }+ suiteTagsStr = suiteFilterStr ++ onlyTagsStr+ pathTagsFilter = Filter { filterSuites = HashSet.empty,+ filterOptions = HashMap.empty,+ filterSelector = HashSet.singleton pathTags }+ suitePathTagsFilter =+ Filter { filterSuites = HashSet.fromList ["Suite1", "Suite2"],+ filterOptions = HashMap.empty,+ filterSelector = HashSet.singleton pathTags }+ suitePathTagsStr = suiteFilterStr ++ pathTagsStr++ suiteSequenceStrs = [ simplePathStr,+ onlyTagsStr,+ pathTagsStr,+ suiteFilterStr,+ suitePathStr,+ suiteTagsStr,+ suitePathTagsStr ]+ suiteSequence = [ simplePathFilter,+ onlyTagsFilter,+ pathTagsFilter,+ suiteAllFilter,+ suitePathFilter,+ suiteTagsFilter,+ suitePathTagsFilter ]+ in+ [("empty", "", []),+ ("space", " ", []),+ ("tab", "\t", []),+ ("newline", "\n", []),+ ("comment", "# Outer@tag", []),+ ("comment_comment", "# Outer@tag # hello", []),+ ("comment_newline_comment", "# Outer@tag\n# hello", []),+ ("space_comment", " # Outer@tag", []),+ ("tab_comment", "\t# Outer@tag", []),+ ("newline_comment", "\n# [Suite]", []),+ ("newline_space_comment", "\n # [Suite]", []),+ ("newline_tab_comment", "\n\t# [Suite]", []),+ ("comment_newline", "# Outer@tag\n", []),+ ("comment_newline_space", "# Outer@tag\n ", []),+ ("comment_newline_tab", "# Outer@tag\n\t", []),+ ("space_comment_newline", " # Outer@tag\n", []),+ ("space_comment_newline_space", " # Outer@tag\n ", []),+ ("space_comment_newline_tab", " # Outer@tag\n\t", []),+ ("tab_comment_newline", "\t# Outer@tag\n", []),+ ("tab_comment_newline_space", "\t# Outer@tag\n ", []),+ ("tab_comment_newline_tab", "\t# Outer@tag\n\t", []),+ ("suiteAll", suiteFilterStr, [suiteAllFilter]),+ ("space_suiteAll", " " ++ suiteFilterStr, [suiteAllFilter]),+ ("tab_suiteAll", "\t" ++ suiteFilterStr, [suiteAllFilter]),+ ("newline_suiteAll", "\n" ++ suiteFilterStr, [suiteAllFilter]),+ ("comment_suiteAll", "# comment\n" ++ suiteFilterStr, [suiteAllFilter]),+ ("suiteAll_space", suiteFilterStr ++ " ", [suiteAllFilter]),+ ("suiteAll_tab", suiteFilterStr ++ "\t", [suiteAllFilter]),+ ("suiteAll_newline", suiteFilterStr ++ "\n", [suiteAllFilter]),+ ("suiteAll_comment", suiteFilterStr ++ "# comment\n", [suiteAllFilter]),+ ("suiteAll_newline_suiteAll",+ suiteFilterStr ++ "\n" ++ suiteFilterStr,+ [suiteAllFilter, suiteAllFilter]),+ ("suiteAll_comment_suiteAll",+ suiteFilterStr ++ "# comment\n" ++ suiteFilterStr,+ [suiteAllFilter, suiteAllFilter]),+ ("simplePath", simplePathStr, [simplePathFilter]),+ ("space_simplePath", " " ++ simplePathStr, [simplePathFilter]),+ ("tab_simplePath", "\t" ++ simplePathStr, [simplePathFilter]),+ ("newline_simplePath", "\n" ++ simplePathStr, [simplePathFilter]),+ ("comment_simplePath", "# comment\n" ++ simplePathStr, [simplePathFilter]),+ ("simplePath_space", simplePathStr ++ " ", [simplePathFilter]),+ ("simplePath_tab", simplePathStr ++ "\t", [simplePathFilter]),+ ("simplePath_newline", simplePathStr ++ "\n", [simplePathFilter]),+ ("simplePath_comment", simplePathStr ++ "# comment\n", [simplePathFilter]),+ ("simplePath_newline_simplePath",+ simplePathStr ++ "\n" ++ simplePathStr,+ [simplePathFilter, simplePathFilter]),+ ("simplePath_comment_simplePath",+ simplePathStr ++ "# comment\n" ++ simplePathStr,+ [simplePathFilter, simplePathFilter]),+ ("suitePath", suitePathStr, [suitePathFilter]),+ ("space_suitePath", " " ++ suitePathStr, [suitePathFilter]),+ ("tab_suitePath", "\t" ++ suitePathStr, [suitePathFilter]),+ ("newline_suitePath", "\n" ++ suitePathStr, [suitePathFilter]),+ ("comment_suitePath", "# comment\n" ++ suitePathStr, [suitePathFilter]),+ ("suitePath_space", suitePathStr ++ " ", [suitePathFilter]),+ ("suitePath_tab", suitePathStr ++ "\t", [suitePathFilter]),+ ("suitePath_newline", suitePathStr ++ "\n", [suitePathFilter]),+ ("suitePath_comment", suitePathStr ++ "# comment\n", [suitePathFilter]),+ ("suitePath_newline_suitePath",+ suitePathStr ++ "\n" ++ suitePathStr,+ [suitePathFilter, suitePathFilter]),+ ("suitePath_comment_suitePath",+ suitePathStr ++ "# comment\n" ++ suitePathStr,+ [suitePathFilter, suitePathFilter]),+ ("onlyTags", onlyTagsStr, [onlyTagsFilter]),+ ("space_onlyTags", " " ++ onlyTagsStr, [onlyTagsFilter]),+ ("tab_onlyTags", "\t" ++ onlyTagsStr, [onlyTagsFilter]),+ ("newline_onlyTags", "\n" ++ onlyTagsStr, [onlyTagsFilter]),+ ("comment_onlyTags", "# comment\n" ++ onlyTagsStr, [onlyTagsFilter]),+ ("onlyTags_space", onlyTagsStr ++ " ", [onlyTagsFilter]),+ ("onlyTags_tab", onlyTagsStr ++ "\t", [onlyTagsFilter]),+ ("onlyTags_newline", onlyTagsStr ++ "\n", [onlyTagsFilter]),+ ("onlyTags_comment", onlyTagsStr ++ "# comment\n", [onlyTagsFilter]),+ ("onlyTags_newline_onlyTags",+ onlyTagsStr ++ "\n" ++ onlyTagsStr,+ [onlyTagsFilter, onlyTagsFilter]),+ ("onlyTags_comment_onlyTags",+ onlyTagsStr ++ "# comment\n" ++ onlyTagsStr,+ [onlyTagsFilter, onlyTagsFilter]),+ ("suiteTags", suiteTagsStr, [suiteTagsFilter]),+ ("space_suiteTags", " " ++ suiteTagsStr, [suiteTagsFilter]),+ ("tab_suiteTags", "\t" ++ suiteTagsStr, [suiteTagsFilter]),+ ("newline_suiteTags", "\n" ++ suiteTagsStr, [suiteTagsFilter]),+ ("comment_suiteTags", "# comment\n" ++ suiteTagsStr, [suiteTagsFilter]),+ ("suiteTags_space", suiteTagsStr ++ " ", [suiteTagsFilter]),+ ("suiteTags_tab", suiteTagsStr ++ "\t", [suiteTagsFilter]),+ ("suiteTags_newline", suiteTagsStr ++ "\n", [suiteTagsFilter]),+ ("suiteTags_comment", suiteTagsStr ++ "# comment\n", [suiteTagsFilter]),+ ("suiteTags_newline_suiteTags",+ suiteTagsStr ++ "\n" ++ suiteTagsStr,+ [suiteTagsFilter, suiteTagsFilter]),+ ("suiteTags_comment_suiteTags",+ suiteTagsStr ++ "# comment\n" ++ suiteTagsStr,+ [suiteTagsFilter, suiteTagsFilter]),+ ("pathTags", pathTagsStr, [pathTagsFilter]),+ ("space_pathTags", " " ++ pathTagsStr, [pathTagsFilter]),+ ("tab_pathTags", "\t" ++ pathTagsStr, [pathTagsFilter]),+ ("newline_pathTags", "\n" ++ pathTagsStr, [pathTagsFilter]),+ ("comment_pathTags", "# comment\n" ++ pathTagsStr, [pathTagsFilter]),+ ("pathTags_space", pathTagsStr ++ " ", [pathTagsFilter]),+ ("pathTags_tab", pathTagsStr ++ "\t", [pathTagsFilter]),+ ("pathTags_newline", pathTagsStr ++ "\n", [pathTagsFilter]),+ ("pathTags_comment", pathTagsStr ++ "# comment\n", [pathTagsFilter]),+ ("pathTags_newline_pathTags",+ pathTagsStr ++ "\n" ++ pathTagsStr,+ [pathTagsFilter, pathTagsFilter]),+ ("pathTags_comment_pathTags",+ pathTagsStr ++ "# comment\n" ++ pathTagsStr,+ [pathTagsFilter, pathTagsFilter]),+ ("suitePathTags", suitePathTagsStr, [suitePathTagsFilter]),+ ("space_suitePathTags", " " ++ suitePathTagsStr, [suitePathTagsFilter]),+ ("tab_suitePathTags", "\t" ++ suitePathTagsStr, [suitePathTagsFilter]),+ ("newline_suitePathTags", "\n" ++ suitePathTagsStr, [suitePathTagsFilter]),+ ("comment_suitePathTags", "# comment\n" ++ suitePathTagsStr,+ [suitePathTagsFilter]),+ ("suitePathTags_space", suitePathTagsStr ++ " ", [suitePathTagsFilter]),+ ("suitePathTags_tab", suitePathTagsStr ++ "\t", [suitePathTagsFilter]),+ ("suitePathTags_newline", suitePathTagsStr ++ "\n", [suitePathTagsFilter]),+ ("suitePathTags_comment", suitePathTagsStr ++ "# comment\n",+ [suitePathTagsFilter]),+ ("suitePathTags_newline_suitePathTags",+ suitePathTagsStr ++ "\n" ++ suitePathTagsStr,+ [suitePathTagsFilter, suitePathTagsFilter]),+ ("suitePathTags_comment_suitePathTags",+ suitePathTagsStr ++ "# comment\n" ++ suitePathTagsStr,+ [suitePathTagsFilter, suitePathTagsFilter]),+ ("sequence", intercalate "\n" suiteSequenceStrs, suiteSequence),+ ("commented_sequence", intercalate "# comment \n" suiteSequenceStrs,+ suiteSequence),+ ("indented_sequence", concat (map (++ "\n ") suiteSequenceStrs),+ suiteSequence)+ ]++fileParserTests =+ map (\(name, content, expected) -> makeFileParserTest name content expected)+ fileTests++innerPath :: Selector -> Selector+innerPath inner = Selector { selectorInners = HashMap.singleton "Inner" inner,+ selectorTags = Nothing }++outerPath :: Selector -> Selector+outerPath inner = Selector { selectorInners = HashMap.singleton "Outer" inner,+ selectorTags = Nothing }++outerInnerPath :: Selector -> Selector+outerInnerPath = outerPath . innerPath++outerAndInnerPath :: Selector+outerAndInnerPath =+ Selector { selectorInners = HashMap.fromList [("Outer", allSelector),+ ("Inner", allSelector)],+ selectorTags = Nothing }++outerInnerAndInnerPath :: Selector+outerInnerAndInnerPath =+ Selector { selectorInners = HashMap.fromList [("Outer", innerPath allSelector),+ ("Inner", allSelector)],+ selectorTags = Nothing }++tag1OuterAndInnerPath :: Selector+tag1OuterAndInnerPath =+ Selector { selectorInners = HashMap.fromList [("Outer", allSelector),+ ("Inner", allSelector)],+ selectorTags = Just $! HashSet.singleton "tag1" }++tag1 :: Selector -> Selector+tag1 inner = inner { selectorTags = Just $! HashSet.singleton "tag1" }++tag2 :: Selector -> Selector+tag2 inner = inner { selectorTags = Just $! HashSet.singleton "tag2" }++tag12 :: Selector -> Selector+tag12 inner = inner { selectorTags = Just $! HashSet.fromList ["tag1", "tag2"] }+++combineSelectorTestCases :: [(String, Selector, Selector, Selector)]+combineSelectorTestCases =+ [("all_all", allSelector, allSelector, allSelector),+ ("all_Outer", allSelector, outerPath allSelector, allSelector),+ ("Outer_all", outerPath allSelector, allSelector, allSelector),+ ("all__Outer_Inner", allSelector, outerInnerPath allSelector, allSelector),+ ("Outer_Inner__all", outerInnerPath allSelector, allSelector, allSelector),+ ("Outer_Outer", outerPath allSelector, outerPath allSelector,+ outerPath allSelector),+ ("Outer_Inner", outerPath allSelector, innerPath allSelector,+ outerAndInnerPath),+ ("Inner_Outer", innerPath allSelector, outerPath allSelector,+ outerAndInnerPath),+ ("Outer__Outer_Inner", outerPath allSelector, outerInnerPath allSelector,+ outerPath allSelector),+ ("Outer_Inner__Outer", outerInnerPath allSelector, outerPath allSelector,+ outerPath allSelector),+ ("Inner__Outer_Inner", innerPath allSelector, outerInnerPath allSelector,+ outerInnerAndInnerPath),+ ("Outer_Inner__Inner", outerInnerPath allSelector, innerPath allSelector,+ outerInnerAndInnerPath),+ ("tag1_Outer", tag1 allSelector, outerPath allSelector,+ tag1 (outerPath allSelector)),+ ("Outer_tag1", outerPath allSelector, tag1 allSelector,+ tag1 (outerPath allSelector)),+ ("tag1__Outer_Inner", tag1 allSelector, outerInnerPath allSelector,+ tag1 (outerInnerPath allSelector)),+ ("Outer_Inner__tag1", outerInnerPath allSelector, tag1 allSelector,+ tag1 (outerInnerPath allSelector)),+ ("tag1_Outer__Outer", tag1 (outerPath allSelector), outerPath allSelector,+ tag1 (outerPath allSelector)),+ ("Outer__tag1_Outer", outerPath allSelector, tag1 (outerPath allSelector),+ tag1 (outerPath allSelector)),+ ("tag1_Inner__Outer", tag1 (innerPath allSelector), outerPath allSelector,+ tag1OuterAndInnerPath),+ ("Outer__tag1_Inner", outerPath allSelector, tag1 (innerPath allSelector),+ tag1OuterAndInnerPath),+ ("tag1_Outer__tag1_Inner", tag1 (innerPath allSelector),+ tag1 (outerPath allSelector),+ tag1 outerAndInnerPath),+ ("tag1_Inner__tag1_Outer", tag1 (outerPath allSelector),+ tag1 (innerPath allSelector),+ tag1 outerAndInnerPath),+ ("all_tag1", allSelector, tag1 allSelector, allSelector),+ ("tag1_all", tag1 allSelector, allSelector, allSelector),+ ("all_tag2", allSelector, tag2 allSelector, allSelector),+ ("tag2_all", tag2 allSelector, allSelector, allSelector),+ ("all_tag12", allSelector, tag12 allSelector, allSelector),+ ("tag12_all", tag12 allSelector, allSelector, allSelector),+ ("tag1_tag1", tag1 allSelector, tag1 allSelector, tag1 allSelector),+ ("tag1_tag2", tag1 allSelector, tag2 allSelector, tag12 allSelector),+ ("tag2_tag1", tag2 allSelector, tag1 allSelector, tag12 allSelector),+ ("tag1_tag12", tag1 allSelector, tag12 allSelector, tag12 allSelector),+ ("tag12_tag1", tag12 allSelector, tag1 allSelector, tag12 allSelector),+ ("tag1_tag2", tag1 allSelector, tag2 allSelector, tag12 allSelector),+ ("tag2_tag1", tag2 allSelector, tag1 allSelector, tag12 allSelector),+ ("tag2_tag2", tag2 allSelector, tag2 allSelector, tag2 allSelector),+ ("tag2_tag12", tag2 allSelector, tag12 allSelector, tag12 allSelector),+ ("tag12_tag2", tag12 allSelector, tag2 allSelector, tag12 allSelector),+ ("tag12_tag1", tag12 allSelector, tag1 allSelector, tag12 allSelector),+ ("tag1_tag12", tag1 allSelector, tag12 allSelector, tag12 allSelector),+ ("tag12_tag2", tag12 allSelector, tag2 allSelector, tag12 allSelector),+ ("tag2_tag12", tag2 allSelector, tag12 allSelector, tag12 allSelector),+ ("tag12_tag12", tag12 allSelector, tag12 allSelector, tag12 allSelector),+ ("Outer_tag1__Outer", outerPath (tag1 allSelector), outerPath allSelector,+ outerPath allSelector),+ ("Outer__Outer_tag1", outerPath allSelector, outerPath (tag1 allSelector),+ outerPath allSelector),+ ("Outer_tag1__Outer_tag1", outerPath (tag1 allSelector),+ outerPath (tag1 allSelector), outerPath (tag1 allSelector)),+ ("Outer_tag1__Outer_tag2", outerPath (tag1 allSelector),+ outerPath (tag2 allSelector), outerPath (tag12 allSelector)),+ ("Outer_tag2__Outer_tag1", outerPath (tag2 allSelector),+ outerPath (tag1 allSelector), outerPath (tag12 allSelector)),+ ("Outer_Inner_tag1__Outer_tag1", outerInnerPath (tag1 allSelector),+ outerPath (tag1 allSelector), outerPath (tag1 allSelector)),+ ("Outer_tag1__Outer_Inner_tag1", outerPath (tag1 allSelector),+ outerInnerPath (tag1 allSelector), outerPath (tag1 allSelector)),+ ("tag1_Outer__Outer_tag1", tag1 (outerPath allSelector),+ outerPath (tag1 allSelector), tag1 (outerPath allSelector)),+ ("Outer_tag1__tag1_Outer", outerPath (tag1 allSelector),+ tag1 (outerPath allSelector), tag1 (outerPath allSelector))+ ]++combineSelectorTests :: [Test]+combineSelectorTests =+ let+ makeTest :: (String, Selector, Selector, Selector) -> Test+ makeTest (name, input1, input2, expected) =+ let+ runTest =+ let+ actual = combineSelectors input1 input2+ in do+ if actual == expected+ then return (Finished Pass)+ else return (Finished (Fail ("Combining\n" ++ show input1 +++ "\nwith\n" ++ show input2 +++ "\nexpected\n" ++ show expected +++ "\ngot\n" ++ show actual)))++ testInstance = TestInstance { name = name, tags = [], options = [],+ setOption = (\_ _ -> return testInstance),+ run = runTest }+ in+ Test testInstance+ in+ map makeTest combineSelectorTestCases++onePath :: Selector+onePath = Selector { selectorInners = HashMap.singleton "One" allSelector,+ selectorTags = Nothing }++twoPath :: Selector+twoPath = Selector { selectorInners = HashMap.singleton "Two" allSelector,+ selectorTags = Nothing }++oneTwoPath :: Selector+oneTwoPath = Selector { selectorInners = HashMap.fromList [("One", allSelector),+ ("Two", allSelector)],+ selectorTags = Nothing }++emptyOneFilter :: Filter+emptyOneFilter = Filter { filterSuites = HashSet.empty,+ filterOptions = HashMap.empty,+ filterSelector = HashSet.singleton onePath }++emptyTwoFilter :: Filter+emptyTwoFilter = Filter { filterSuites = HashSet.empty,+ filterOptions = HashMap.empty,+ filterSelector = HashSet.singleton twoPath }++emptyOneTwoFilter :: Filter+emptyOneTwoFilter = Filter { filterSuites = HashSet.empty,+ filterOptions = HashMap.empty,+ filterSelector = HashSet.singleton oneTwoPath }++allAFilter :: Filter+allAFilter = Filter { filterSuites = HashSet.singleton "A",+ filterOptions = HashMap.empty,+ filterSelector = HashSet.singleton allSelector }++oneAFilter :: Filter+oneAFilter = Filter { filterSuites = HashSet.singleton "A",+ filterOptions = HashMap.empty,+ filterSelector = HashSet.singleton onePath }++oneBFilter :: Filter+oneBFilter = Filter { filterSuites = HashSet.singleton "B",+ filterOptions = HashMap.empty,+ filterSelector = HashSet.singleton onePath }++oneABFilter :: Filter+oneABFilter = Filter { filterSuites = HashSet.fromList ["A", "B"],+ filterOptions = HashMap.empty,+ filterSelector = HashSet.singleton onePath }++oneACFilter :: Filter+oneACFilter = Filter { filterSuites = HashSet.fromList ["A", "C"],+ filterOptions = HashMap.empty,+ filterSelector = HashSet.singleton onePath }++twoAFilter :: Filter+twoAFilter = Filter { filterSuites = HashSet.singleton "A",+ filterOptions = HashMap.empty,+ filterSelector = HashSet.singleton twoPath }++twoBFilter :: Filter+twoBFilter = Filter { filterSuites = HashSet.singleton "B",+ filterOptions = HashMap.empty,+ filterSelector = HashSet.singleton twoPath }++twoABFilter :: Filter+twoABFilter = Filter { filterSuites = HashSet.fromList ["A", "B"],+ filterOptions = HashMap.empty,+ filterSelector = HashSet.singleton twoPath }++twoACFilter :: Filter+twoACFilter = Filter { filterSuites = HashSet.fromList ["A", "C"],+ filterOptions = HashMap.empty,+ filterSelector = HashSet.singleton twoPath }++oneTwoAFilter :: Filter+oneTwoAFilter = Filter { filterSuites = HashSet.singleton "A",+ filterOptions = HashMap.empty,+ filterSelector = HashSet.singleton oneTwoPath }++oneTwoBFilter :: Filter+oneTwoBFilter = Filter { filterSuites = HashSet.singleton "B",+ filterOptions = HashMap.empty,+ filterSelector = HashSet.singleton oneTwoPath }++oneTwoABFilter :: Filter+oneTwoABFilter = Filter { filterSuites = HashSet.fromList ["A", "B"],+ filterOptions = HashMap.empty,+ filterSelector = HashSet.singleton oneTwoPath }++oneTwoACFilter :: Filter+oneTwoACFilter = Filter { filterSuites = HashSet.fromList ["A", "C"],+ filterOptions = HashMap.empty,+ filterSelector = HashSet.singleton oneTwoPath }++allBFilter :: Filter+allBFilter = Filter { filterSuites = HashSet.singleton "B",+ filterOptions = HashMap.empty,+ filterSelector = HashSet.singleton allSelector }++allCFilter :: Filter+allCFilter = Filter { filterSuites = HashSet.singleton "C",+ filterOptions = HashMap.empty,+ filterSelector = HashSet.singleton allSelector }++allABFilter :: Filter+allABFilter = Filter { filterSuites = HashSet.fromList ["A", "B"],+ filterOptions = HashMap.empty,+ filterSelector = HashSet.singleton allSelector }++allACFilter :: Filter+allACFilter = Filter { filterSuites = HashSet.fromList ["A", "C"],+ filterOptions = HashMap.empty,+ filterSelector = HashSet.singleton allSelector }++allBCFilter :: Filter+allBCFilter = Filter { filterSuites = HashSet.fromList ["B", "C"],+ filterOptions = HashMap.empty,+ filterSelector = HashSet.singleton allSelector }+++filterTestCases :: [(String, [String], [Filter],+ [(Strict.Text, HashMap OptionMap Selector)])]+filterTestCases = [+ ("A_nil", ["A"], [],+ [("A", HashMap.singleton HashMap.empty allSelector)]),+ ("AB_nil", ["A", "B"], [],+ [("A", HashMap.singleton HashMap.empty allSelector),+ ("B", HashMap.singleton HashMap.empty allSelector)]),+ ("ABC_nil", ["A", "B", "C"], [],+ [("A", HashMap.singleton HashMap.empty allSelector),+ ("B", HashMap.singleton HashMap.empty allSelector),+ ("C", HashMap.singleton HashMap.empty allSelector)]),+ ("A_emptyOne", ["A"], [emptyOneFilter],+ [("A", HashMap.singleton HashMap.empty onePath)]),+ ("AB_emptyOne", ["A", "B"], [emptyOneFilter],+ [("A", HashMap.singleton HashMap.empty onePath),+ ("B", HashMap.singleton HashMap.empty onePath)]),+ ("ABC_emptyOne", ["A", "B", "C"], [emptyOneFilter],+ [("A", HashMap.singleton HashMap.empty onePath),+ ("B", HashMap.singleton HashMap.empty onePath),+ ("C", HashMap.singleton HashMap.empty onePath)]),+ ("A_emptyOne_emptyTwo", ["A"], [emptyOneFilter, emptyTwoFilter],+ [("A", HashMap.singleton HashMap.empty oneTwoPath)]),+ ("AB_emptyOne_emptyTwo", ["A", "B"], [emptyOneFilter, emptyTwoFilter],+ [("A", HashMap.singleton HashMap.empty oneTwoPath),+ ("B", HashMap.singleton HashMap.empty oneTwoPath)]),+ ("ABC_emptyOne_emptyTwo", ["A", "B", "C"], [emptyOneFilter, emptyTwoFilter],+ [("A", HashMap.singleton HashMap.empty oneTwoPath),+ ("B", HashMap.singleton HashMap.empty oneTwoPath),+ ("C", HashMap.singleton HashMap.empty oneTwoPath)]),+ ("A_emptyOne_emptyOneTwo", ["A"], [emptyOneFilter, emptyOneTwoFilter],+ [("A", HashMap.singleton HashMap.empty oneTwoPath)]),+ ("AB_emptyOne_emptyOneTwo", ["A", "B"], [emptyOneFilter, emptyOneTwoFilter],+ [("A", HashMap.singleton HashMap.empty oneTwoPath),+ ("B", HashMap.singleton HashMap.empty oneTwoPath)]),+ ("ABC_emptyOne_emptyOneTwo", ["A", "B", "C"], [emptyOneFilter,+ emptyOneTwoFilter],+ [("A", HashMap.singleton HashMap.empty oneTwoPath),+ ("B", HashMap.singleton HashMap.empty oneTwoPath),+ ("C", HashMap.singleton HashMap.empty oneTwoPath)]),+ ("A_allA", ["A"], [allAFilter],+ [("A", HashMap.singleton HashMap.empty allSelector)]),+ ("A_OneA", ["A"], [oneAFilter],+ [("A", HashMap.singleton HashMap.empty onePath)]),+ ("A_OneA_OneA", ["A"], [oneAFilter, oneAFilter],+ [("A", HashMap.singleton HashMap.empty onePath)]),+ ("A_OneA_TwoA", ["A"], [oneAFilter, twoAFilter],+ [("A", HashMap.singleton HashMap.empty oneTwoPath)]),+ ("A_OneA_OneTwoA", ["A"], [oneAFilter, oneTwoAFilter],+ [("A", HashMap.singleton HashMap.empty oneTwoPath)]),+ ("A_OneA_allA", ["A"], [oneAFilter, allAFilter],+ [("A", HashMap.singleton HashMap.empty allSelector)]),+ ("A_allA_allA", ["A"], [allAFilter, allAFilter],+ [("A", HashMap.singleton HashMap.empty allSelector)]),+ ("A_OneA_emptyOne", ["A"], [oneAFilter, emptyOneFilter],+ [("A", HashMap.singleton HashMap.empty onePath)]),+ ("A_TwoA_emptyOne", ["A"], [twoAFilter, emptyOneFilter],+ [("A", HashMap.singleton HashMap.empty oneTwoPath)]),+ ("A_OneTwoA_emptyOne", ["A"], [oneTwoAFilter, emptyOneFilter],+ [("A", HashMap.singleton HashMap.empty oneTwoPath)]),+ ("A_allA_emptyOne", ["A"], [allAFilter, emptyOneFilter],+ [("A", HashMap.singleton HashMap.empty allSelector)]),+ ("A_OneA_emptyTwo", ["A"], [oneAFilter, emptyTwoFilter],+ [("A", HashMap.singleton HashMap.empty oneTwoPath)]),+ ("A_TwoA_emptyTwo", ["A"], [twoAFilter, emptyTwoFilter],+ [("A", HashMap.singleton HashMap.empty twoPath)]),+ ("A_OneTwoA_emptyTwo", ["A"], [oneTwoAFilter, emptyTwoFilter],+ [("A", HashMap.singleton HashMap.empty oneTwoPath)]),+ ("A_allA_emptyTwo", ["A"], [allAFilter, emptyTwoFilter],+ [("A", HashMap.singleton HashMap.empty allSelector)]),+ ("A_OneA_emptyOneTwo", ["A"], [oneAFilter, emptyOneTwoFilter],+ [("A", HashMap.singleton HashMap.empty oneTwoPath)]),+ ("A_TwoA_emptyOneTwo", ["A"], [twoAFilter, emptyOneTwoFilter],+ [("A", HashMap.singleton HashMap.empty oneTwoPath)]),+ ("A_OneTwoA_emptyOneTwo", ["A"], [oneTwoAFilter, emptyOneTwoFilter],+ [("A", HashMap.singleton HashMap.empty oneTwoPath)]),+ ("A_allA_emptyOneTwo", ["A"], [allAFilter, emptyOneTwoFilter],+ [("A", HashMap.singleton HashMap.empty allSelector)]),+ ("AB_allA", ["A", "B"], [allAFilter],+ [("A", HashMap.singleton HashMap.empty allSelector)]),+ ("AB_OneA", ["A", "B"], [oneAFilter],+ [("A", HashMap.singleton HashMap.empty onePath)]),+ ("AB_OneA_OneA", ["A", "B"], [oneAFilter, oneAFilter],+ [("A", HashMap.singleton HashMap.empty onePath)]),+ ("AB_OneA_TwoA", ["A", "B"], [oneAFilter, twoAFilter],+ [("A", HashMap.singleton HashMap.empty oneTwoPath)]),+ ("AB_OneA_OneTwoA", ["A", "B"], [oneAFilter, oneTwoAFilter],+ [("A", HashMap.singleton HashMap.empty oneTwoPath)]),+ ("AB_OneA_allA", ["A", "B"], [oneAFilter, allAFilter],+ [("A", HashMap.singleton HashMap.empty allSelector)]),+ ("AB_allA_allA", ["A", "B"], [allAFilter, allAFilter],+ [("A", HashMap.singleton HashMap.empty allSelector)]),+ ("AB_OneA_OneB", ["A", "B"], [oneAFilter, oneBFilter],+ [("A", HashMap.singleton HashMap.empty onePath),+ ("B", HashMap.singleton HashMap.empty onePath)]),+ ("AB_allA_OneB", ["A", "B"], [allAFilter, oneBFilter],+ [("A", HashMap.singleton HashMap.empty allSelector),+ ("B", HashMap.singleton HashMap.empty onePath)]),+ ("AB_OneA_allB", ["A", "B"], [oneAFilter, allBFilter],+ [("A", HashMap.singleton HashMap.empty onePath),+ ("B", HashMap.singleton HashMap.empty allSelector)]),+ ("AB_allA_allB", ["A", "B"], [allAFilter, allBFilter],+ [("A", HashMap.singleton HashMap.empty allSelector),+ ("B", HashMap.singleton HashMap.empty allSelector)]),+ ("AB_OneA_OneAB", ["A", "B"], [oneAFilter, oneABFilter],+ [("A", HashMap.singleton HashMap.empty onePath),+ ("B", HashMap.singleton HashMap.empty onePath)]),+ ("AB_OneA_TwoAB", ["A", "B"], [oneAFilter, twoABFilter],+ [("A", HashMap.singleton HashMap.empty oneTwoPath),+ ("B", HashMap.singleton HashMap.empty twoPath)]),+ ("AB_OneA_OneTwoAB", ["A", "B"], [oneAFilter, oneTwoABFilter],+ [("A", HashMap.singleton HashMap.empty oneTwoPath),+ ("B", HashMap.singleton HashMap.empty oneTwoPath)]),+ ("AB_allA_OneAB", ["A", "B"], [allAFilter, oneABFilter],+ [("A", HashMap.singleton HashMap.empty allSelector),+ ("B", HashMap.singleton HashMap.empty onePath)]),+ ("AB_allA_TwoAB", ["A", "B"], [allAFilter, twoABFilter],+ [("A", HashMap.singleton HashMap.empty allSelector),+ ("B", HashMap.singleton HashMap.empty twoPath)]),+ ("AB_allA_OneTwoAB", ["A", "B"], [allAFilter, oneTwoABFilter],+ [("A", HashMap.singleton HashMap.empty allSelector),+ ("B", HashMap.singleton HashMap.empty oneTwoPath)]),+ ("AB_OneA_allAB", ["A", "B"], [oneAFilter, allABFilter],+ [("A", HashMap.singleton HashMap.empty allSelector),+ ("B", HashMap.singleton HashMap.empty allSelector)]),+ ("AB_allA_allAB", ["A", "B"], [allAFilter, allABFilter],+ [("A", HashMap.singleton HashMap.empty allSelector),+ ("B", HashMap.singleton HashMap.empty allSelector)]),+ ("AB_OneA_emptyOne", ["A", "B"], [oneAFilter, emptyOneFilter],+ [("A", HashMap.singleton HashMap.empty onePath),+ ("B", HashMap.singleton HashMap.empty onePath)]),+ ("AB_TwoA_emptyOne", ["A", "B"], [twoAFilter, emptyOneFilter],+ [("A", HashMap.singleton HashMap.empty oneTwoPath),+ ("B", HashMap.singleton HashMap.empty onePath)]),+ ("AB_OneTwoA_emptyOne", ["A", "B"], [oneTwoAFilter, emptyOneFilter],+ [("A", HashMap.singleton HashMap.empty oneTwoPath),+ ("B", HashMap.singleton HashMap.empty onePath)]),+ ("AB_allA_emptyOne", ["A", "B"], [allAFilter, emptyOneFilter],+ [("A", HashMap.singleton HashMap.empty allSelector),+ ("B", HashMap.singleton HashMap.empty onePath)]),+ ("AB_OneA_emptyTwo", ["A", "B"], [oneAFilter, emptyTwoFilter],+ [("A", HashMap.singleton HashMap.empty oneTwoPath),+ ("B", HashMap.singleton HashMap.empty twoPath)]),+ ("AB_TwoA_emptyTwo", ["A", "B"], [twoAFilter, emptyTwoFilter],+ [("A", HashMap.singleton HashMap.empty twoPath),+ ("B", HashMap.singleton HashMap.empty twoPath)]),+ ("AB_OneTwoA_emptyTwo", ["A", "B"], [oneTwoAFilter, emptyTwoFilter],+ [("A", HashMap.singleton HashMap.empty oneTwoPath),+ ("B", HashMap.singleton HashMap.empty twoPath)]),+ ("AB_allA_emptyTwo", ["A", "B"], [allAFilter, emptyTwoFilter],+ [("A", HashMap.singleton HashMap.empty allSelector),+ ("B", HashMap.singleton HashMap.empty twoPath)]),+ ("AB_OneA_emptyOneTwo", ["A", "B"], [oneAFilter, emptyOneTwoFilter],+ [("A", HashMap.singleton HashMap.empty oneTwoPath),+ ("B", HashMap.singleton HashMap.empty oneTwoPath)]),+ ("AB_TwoA_emptyOneTwo", ["A", "B"], [twoAFilter, emptyOneTwoFilter],+ [("A", HashMap.singleton HashMap.empty oneTwoPath),+ ("B", HashMap.singleton HashMap.empty oneTwoPath)]),+ ("AB_OneTwoA_emptyOneTwo", ["A", "B"], [oneTwoAFilter, emptyOneTwoFilter],+ [("A", HashMap.singleton HashMap.empty oneTwoPath),+ ("B", HashMap.singleton HashMap.empty oneTwoPath)]),+ ("AB_allA_emptyOneTwo", ["A", "B"], [allAFilter, emptyOneTwoFilter],+ [("A", HashMap.singleton HashMap.empty allSelector),+ ("B", HashMap.singleton HashMap.empty oneTwoPath)]),+ ("ABC_allA", ["A", "B", "C"], [allAFilter],+ [("A", HashMap.singleton HashMap.empty allSelector)]),+ ("ABC_OneA", ["A", "B", "C"], [oneAFilter],+ [("A", HashMap.singleton HashMap.empty onePath)]),+ ("ABC_OneA_OneA", ["A", "B", "C"], [oneAFilter, oneAFilter],+ [("A", HashMap.singleton HashMap.empty onePath)]),+ ("ABC_OneA_TwoA", ["A", "B", "C"], [oneAFilter, twoAFilter],+ [("A", HashMap.singleton HashMap.empty oneTwoPath)]),+ ("ABC_OneA_OneTwoA", ["A", "B", "C"], [oneAFilter, oneTwoAFilter],+ [("A", HashMap.singleton HashMap.empty oneTwoPath)]),+ ("ABC_OneA_allA", ["A", "B", "C"], [oneAFilter, allAFilter],+ [("A", HashMap.singleton HashMap.empty allSelector)]),+ ("ABC_allA_allA", ["A", "B", "C"], [allAFilter, allAFilter],+ [("A", HashMap.singleton HashMap.empty allSelector)]),+ ("ABC_OneA_OneB", ["A", "B", "C"], [oneAFilter, oneBFilter],+ [("A", HashMap.singleton HashMap.empty onePath),+ ("B", HashMap.singleton HashMap.empty onePath)]),+ ("ABC_allA_OneB", ["A", "B", "C"], [allAFilter, oneBFilter],+ [("A", HashMap.singleton HashMap.empty allSelector),+ ("B", HashMap.singleton HashMap.empty onePath)]),+ ("ABC_OneA_allB", ["A", "B", "C"], [oneAFilter, allBFilter],+ [("A", HashMap.singleton HashMap.empty onePath),+ ("B", HashMap.singleton HashMap.empty allSelector)]),+ ("ABC_allA_allB", ["A", "B", "C"], [allAFilter, allBFilter],+ [("A", HashMap.singleton HashMap.empty allSelector),+ ("B", HashMap.singleton HashMap.empty allSelector)]),+ ("ABC_OneA_OneAB", ["A", "B", "C"], [oneAFilter, oneABFilter],+ [("A", HashMap.singleton HashMap.empty onePath),+ ("B", HashMap.singleton HashMap.empty onePath)]),+ ("ABC_OneA_TwoAB", ["A", "B", "C"], [oneAFilter, twoABFilter],+ [("A", HashMap.singleton HashMap.empty oneTwoPath),+ ("B", HashMap.singleton HashMap.empty twoPath)]),+ ("ABC_OneA_OneTwoAB", ["A", "B", "C"], [oneAFilter, oneTwoABFilter],+ [("A", HashMap.singleton HashMap.empty oneTwoPath),+ ("B", HashMap.singleton HashMap.empty oneTwoPath)]),+ ("ABC_OneA_OneAC", ["A", "B", "C"], [oneAFilter, oneACFilter],+ [("A", HashMap.singleton HashMap.empty onePath),+ ("C", HashMap.singleton HashMap.empty onePath)]),+ ("ABC_OneA_TwoAC", ["A", "B", "C"], [oneAFilter, twoACFilter],+ [("A", HashMap.singleton HashMap.empty oneTwoPath),+ ("C", HashMap.singleton HashMap.empty twoPath)]),+ ("ABC_OneA_OneTwoAC", ["A", "B", "C"], [oneAFilter, oneTwoACFilter],+ [("A", HashMap.singleton HashMap.empty oneTwoPath),+ ("C", HashMap.singleton HashMap.empty oneTwoPath)]),+ ("ABC_OneAB_OneAC", ["A", "B", "C"], [oneABFilter, oneACFilter],+ [("A", HashMap.singleton HashMap.empty onePath),+ ("B", HashMap.singleton HashMap.empty onePath),+ ("C", HashMap.singleton HashMap.empty onePath)]),+ ("ABC_OneAB_TwoAC", ["A", "B", "C"], [oneABFilter, twoACFilter],+ [("A", HashMap.singleton HashMap.empty oneTwoPath),+ ("B", HashMap.singleton HashMap.empty onePath),+ ("C", HashMap.singleton HashMap.empty twoPath)]),+ ("ABC_OneAB_OneTwoAC", ["A", "B", "C"], [oneABFilter, oneTwoACFilter],+ [("A", HashMap.singleton HashMap.empty oneTwoPath),+ ("B", HashMap.singleton HashMap.empty onePath),+ ("C", HashMap.singleton HashMap.empty oneTwoPath)]),+ ("ABC_allA_OneAB", ["A", "B", "C"], [allAFilter, oneABFilter],+ [("A", HashMap.singleton HashMap.empty allSelector),+ ("B", HashMap.singleton HashMap.empty onePath)]),+ ("ABC_allA_TwoAB", ["A", "B", "C"], [allAFilter, twoABFilter],+ [("A", HashMap.singleton HashMap.empty allSelector),+ ("B", HashMap.singleton HashMap.empty twoPath)]),+ ("ABC_allA_OneTwoAB", ["A", "B", "C"], [allAFilter, oneTwoABFilter],+ [("A", HashMap.singleton HashMap.empty allSelector),+ ("B", HashMap.singleton HashMap.empty oneTwoPath)]),+ ("ABC_allAC_OneAB", ["A", "B", "C"], [allACFilter, oneABFilter],+ [("A", HashMap.singleton HashMap.empty allSelector),+ ("B", HashMap.singleton HashMap.empty onePath),+ ("C", HashMap.singleton HashMap.empty allSelector)]),+ ("ABC_allAC_TwoAB", ["A", "B", "C"], [allACFilter, twoABFilter],+ [("A", HashMap.singleton HashMap.empty allSelector),+ ("B", HashMap.singleton HashMap.empty twoPath),+ ("C", HashMap.singleton HashMap.empty allSelector)]),+ ("ABC_allAC_OneTwoAB", ["A", "B", "C"], [allACFilter, oneTwoABFilter],+ [("A", HashMap.singleton HashMap.empty allSelector),+ ("B", HashMap.singleton HashMap.empty oneTwoPath),+ ("C", HashMap.singleton HashMap.empty allSelector)]),+ ("ABC_OneA_allAB", ["A", "B", "C"], [oneAFilter, allABFilter],+ [("A", HashMap.singleton HashMap.empty allSelector),+ ("B", HashMap.singleton HashMap.empty allSelector)]),+ ("ABC_allA_allAB", ["A", "B", "C"], [allAFilter, allABFilter],+ [("A", HashMap.singleton HashMap.empty allSelector),+ ("B", HashMap.singleton HashMap.empty allSelector)]),+ ("ABC_OneA_allAB", ["A", "B", "C"], [oneACFilter, allABFilter],+ [("A", HashMap.singleton HashMap.empty allSelector),+ ("B", HashMap.singleton HashMap.empty allSelector),+ ("C", HashMap.singleton HashMap.empty onePath)]),+ ("ABC_allA_allAB", ["A", "B", "C"], [allACFilter, allABFilter],+ [("A", HashMap.singleton HashMap.empty allSelector),+ ("B", HashMap.singleton HashMap.empty allSelector),+ ("C", HashMap.singleton HashMap.empty allSelector)]),+ ("ABC_OneA_emptyOne", ["A", "B", "C"], [oneAFilter, emptyOneFilter],+ [("A", HashMap.singleton HashMap.empty onePath),+ ("B", HashMap.singleton HashMap.empty onePath),+ ("C", HashMap.singleton HashMap.empty onePath)]),+ ("ABC_TwoA_emptyOne", ["A", "B", "C"], [twoAFilter, emptyOneFilter],+ [("A", HashMap.singleton HashMap.empty oneTwoPath),+ ("B", HashMap.singleton HashMap.empty onePath),+ ("C", HashMap.singleton HashMap.empty onePath)]),+ ("ABC_OneTwoA_emptyOne", ["A", "B", "C"], [oneTwoAFilter, emptyOneFilter],+ [("A", HashMap.singleton HashMap.empty oneTwoPath),+ ("B", HashMap.singleton HashMap.empty onePath),+ ("C", HashMap.singleton HashMap.empty onePath)]),+ ("ABC_allA_emptyOne", ["A", "B", "C"], [allAFilter, emptyOneFilter],+ [("A", HashMap.singleton HashMap.empty allSelector),+ ("B", HashMap.singleton HashMap.empty onePath),+ ("C", HashMap.singleton HashMap.empty onePath)]),+ ("ABC_OneAB_emptyOne", ["A", "B", "C"], [oneABFilter, emptyOneFilter],+ [("A", HashMap.singleton HashMap.empty onePath),+ ("B", HashMap.singleton HashMap.empty onePath),+ ("C", HashMap.singleton HashMap.empty onePath)]),+ ("ABC_TwoAB_emptyOne", ["A", "B", "C"], [twoABFilter, emptyOneFilter],+ [("A", HashMap.singleton HashMap.empty oneTwoPath),+ ("B", HashMap.singleton HashMap.empty oneTwoPath),+ ("C", HashMap.singleton HashMap.empty onePath)]),+ ("ABC_OneTwoAB_emptyOne", ["A", "B", "C"], [oneTwoABFilter, emptyOneFilter],+ [("A", HashMap.singleton HashMap.empty oneTwoPath),+ ("B", HashMap.singleton HashMap.empty oneTwoPath),+ ("C", HashMap.singleton HashMap.empty onePath)]),+ ("ABC_allAB_emptyOne", ["A", "B", "C"], [allABFilter, emptyOneFilter],+ [("A", HashMap.singleton HashMap.empty allSelector),+ ("B", HashMap.singleton HashMap.empty allSelector),+ ("C", HashMap.singleton HashMap.empty onePath)]),+ ("ABC_OneA_emptyTwo", ["A", "B", "C"], [oneAFilter, emptyTwoFilter],+ [("A", HashMap.singleton HashMap.empty oneTwoPath),+ ("B", HashMap.singleton HashMap.empty twoPath),+ ("C", HashMap.singleton HashMap.empty twoPath)]),+ ("ABC_TwoA_emptyTwo", ["A", "B", "C"], [twoAFilter, emptyTwoFilter],+ [("A", HashMap.singleton HashMap.empty twoPath),+ ("B", HashMap.singleton HashMap.empty twoPath),+ ("C", HashMap.singleton HashMap.empty twoPath)]),+ ("ABC_OneTwoA_emptyTwo", ["A", "B", "C"], [oneTwoAFilter, emptyTwoFilter],+ [("A", HashMap.singleton HashMap.empty oneTwoPath),+ ("B", HashMap.singleton HashMap.empty twoPath),+ ("C", HashMap.singleton HashMap.empty twoPath)]),+ ("ABC_allA_emptyTwo", ["A", "B", "C"], [allAFilter, emptyTwoFilter],+ [("A", HashMap.singleton HashMap.empty allSelector),+ ("B", HashMap.singleton HashMap.empty twoPath),+ ("C", HashMap.singleton HashMap.empty twoPath)]),+ ("ABC_OneAB_emptyTwo", ["A", "B", "C"], [oneABFilter, emptyTwoFilter],+ [("A", HashMap.singleton HashMap.empty oneTwoPath),+ ("B", HashMap.singleton HashMap.empty oneTwoPath),+ ("C", HashMap.singleton HashMap.empty twoPath)]),+ ("ABC_TwoAB_emptyTwo", ["A", "B", "C"], [twoABFilter, emptyTwoFilter],+ [("A", HashMap.singleton HashMap.empty twoPath),+ ("B", HashMap.singleton HashMap.empty twoPath),+ ("C", HashMap.singleton HashMap.empty twoPath)]),+ ("ABC_OneTwoAB_emptyTwo", ["A", "B", "C"], [oneTwoABFilter, emptyTwoFilter],+ [("A", HashMap.singleton HashMap.empty oneTwoPath),+ ("B", HashMap.singleton HashMap.empty oneTwoPath),+ ("C", HashMap.singleton HashMap.empty twoPath)]),+ ("ABC_allAB_emptyTwo", ["A", "B", "C"], [allABFilter, emptyTwoFilter],+ [("A", HashMap.singleton HashMap.empty allSelector),+ ("B", HashMap.singleton HashMap.empty allSelector),+ ("C", HashMap.singleton HashMap.empty twoPath)]),+ ("ABC_OneA_emptyOneTwo", ["A", "B", "C"], [oneAFilter, emptyOneTwoFilter],+ [("A", HashMap.singleton HashMap.empty oneTwoPath),+ ("B", HashMap.singleton HashMap.empty oneTwoPath),+ ("C", HashMap.singleton HashMap.empty oneTwoPath)]),+ ("ABC_TwoA_emptyOneTwo", ["A", "B", "C"], [twoAFilter, emptyOneTwoFilter],+ [("A", HashMap.singleton HashMap.empty oneTwoPath),+ ("B", HashMap.singleton HashMap.empty oneTwoPath),+ ("C", HashMap.singleton HashMap.empty oneTwoPath)]),+ ("ABC_OneTwoA_emptyOneTwo", ["A", "B", "C"],+ [oneTwoAFilter, emptyOneTwoFilter],+ [("A", HashMap.singleton HashMap.empty oneTwoPath),+ ("B", HashMap.singleton HashMap.empty oneTwoPath),+ ("C", HashMap.singleton HashMap.empty oneTwoPath)]),+ ("ABC_allA_emptyOneTwo", ["A", "B", "C"], [allAFilter, emptyOneTwoFilter],+ [("A", HashMap.singleton HashMap.empty allSelector),+ ("B", HashMap.singleton HashMap.empty oneTwoPath),+ ("C", HashMap.singleton HashMap.empty oneTwoPath)]),+ ("ABC_OneAB_emptyOneTwo", ["A", "B", "C"], [oneABFilter, emptyOneTwoFilter],+ [("A", HashMap.singleton HashMap.empty oneTwoPath),+ ("B", HashMap.singleton HashMap.empty oneTwoPath),+ ("C", HashMap.singleton HashMap.empty oneTwoPath)]),+ ("ABC_TwoAB_emptyOneTwo", ["A", "B", "C"], [twoABFilter, emptyOneTwoFilter],+ [("A", HashMap.singleton HashMap.empty oneTwoPath),+ ("B", HashMap.singleton HashMap.empty oneTwoPath),+ ("C", HashMap.singleton HashMap.empty oneTwoPath)]),+ ("ABC_OneTwoAB_emptyOneTwo", ["A", "B", "C"],+ [oneTwoABFilter, emptyOneTwoFilter],+ [("A", HashMap.singleton HashMap.empty oneTwoPath),+ ("B", HashMap.singleton HashMap.empty oneTwoPath),+ ("C", HashMap.singleton HashMap.empty oneTwoPath)]),+ ("ABC_allAB_emptyOneTwo", ["A", "B", "C"], [allABFilter, emptyOneTwoFilter],+ [("A", HashMap.singleton HashMap.empty allSelector),+ ("B", HashMap.singleton HashMap.empty allSelector),+ ("C", HashMap.singleton HashMap.empty oneTwoPath)])+ ]++filterTests :: [Test]+filterTests =+ let+ makeTest :: (String, [String], [Filter],+ [(Strict.Text, HashMap OptionMap Selector)])+ -> Test+ makeTest (name, suites, filters, expected) =+ let+ runTest =+ let+ actual = HashMap.toList (suiteSelectors (map Strict.pack suites)+ filters)+ in do+ if actual == expected+ then return (Finished Pass)+ else return (Finished (Fail ("Combining\n" ++ show filters +++ "\nwith suites\n" ++ show suites +++ "\nexpected\n" ++ show expected +++ "\ngot\n" ++ show actual)))++ testInstance = TestInstance { name = name, tags = [], options = [],+ setOption = (\_ _ -> return testInstance),+ run = runTest }+ in+ Test testInstance+ in+ map makeTest filterTestCases+++testlist = [ testGroup "fileParser" fileParserTests,+ testGroup "filterParse" filterParseTests,+ testGroup "combineSelectors" combineSelectorTests,+ testGroup "suiteSelectors" filterTests ]++tests :: Test+tests = testGroup "Filter" testlist
+ test/Tests/Test/HUnitPlus/Main.hs view
@@ -0,0 +1,211 @@+{-# LANGUAGE OverloadedStrings #-}++module Tests.Test.HUnitPlus.Main where++import Data.List+import Distribution.TestSuite+import System.Directory+import Test.HUnitPlus.Main+import Test.HUnitPlus.Base+import qualified Data.Text as Strict++makeMainTest :: (String, IO (), IO (), Bool, [TestSuite], Opts) -> Test+makeMainTest (name, setup, cleanup, shouldPass, suites, opts) =+ let+ runTest =+ do+ setup+ out <- topLevel suites opts+ cleanup+ case out of+ Left msgs ->+ if not shouldPass+ then return (Finished Pass)+ else return (Finished (Fail ("Expected test to pass, " +++ "but failed with\n" +++ intercalate "\n" (map Strict.unpack msgs))))+ Right _ ->+ if shouldPass+ then return (Finished Pass)+ else return (Finished (Fail "Expected test to fail"))+ testInstance = TestInstance { name = name, tags = [], options = [],+ setOption = (\_ _ -> return testInstance),+ run = runTest }+ in+ Test testInstance++makeTestDir :: IO ()+makeTestDir = createDirectory "TestDir"++delTestDir :: IO ()+delTestDir = removeDirectory "TestDir"++delXMLReport :: IO ()+delXMLReport = removeFile "TestDir/report.xml" >> delTestDir++delTxtReport :: IO ()+delTxtReport = removeFile "TestDir/report.txt" >> delTestDir++delTxtXMLReport :: IO ()+delTxtXMLReport = removeFile "TestDir/report.xml" >>+ removeFile "TestDir/report.txt" >>+ delTestDir++quietOpts = opts { consmode = [Quiet] }++filterOpts = [ (False, False), (True, False), (False, True), (True, True) ]++makeSuiteData suitename =+ map (\filters -> (TestSuite { suiteName = "suitename",+ suiteConcurrently = False,+ suiteOptions = [],+ suiteTests = suiteTestList },+ filters))filterOpts++suiteCombos =+ foldl (\accum suite1case ->+ (foldl (\accum suite2case -> [suite1case, suite2case] : accum)+ accum (makeSuiteData "Suite2")))+ [] (makeSuiteData "Suite1")++suitePairCombos = foldl (\accum a ->+ foldl (\accum b -> (a, b) : accum)+ accum suiteCombos)+ [] suiteCombos++makeFilter :: String -> (Bool, Bool) -> [String]+makeFilter suitename (False, False) = []+makeFilter suitename (True, False) = ["[" ++ suitename ++ "]Pass"]+makeFilter suitename (False, True) = ["[" ++ suitename ++ "]Fail"]+makeFilter suitename (True, True) = ["[" ++ suitename ++ "]Pass",+ "[" ++ suitename ++ "]Fail"]++makeFilters suitedata =+ foldl (\accum (TestSuite { suiteName = suitename }, filters) ->+ makeFilter (Strict.unpack suitename) filters ++ accum) [] suitedata++shouldPass suitedata = not (all (\(_, (a, b)) -> not a && not b) suitedata) ||+ not (any (\(_, (_, fail)) -> fail) suitedata)++suiteTestList = [ "Pass" ~: assertSuccess, "Fail" ~: assertFailure "Fail" ]++makeName suitedata =+ intercalate "__"+ (foldl (\accum (TestSuite { suiteName = name }, (pass, fail)) ->+ (Strict.unpack name ++ "_" ++ show pass ++ "_" ++ show fail) : accum)+ [] suitedata)++makeCmdOptTest suitedata =+ ("cmdopt___" ++ makeName suitedata, return (), return (), shouldPass suitedata,+ map fst suitedata, quietOpts { filters = makeFilters suitedata })++makeTestlistTest suitedata =+ let+ createFilterFile =+ do+ createDirectory "TestDir"+ writeFile "TestDir/testlist" (intercalate "\n" (makeFilters suitedata))++ delFilterFile = removeFile "TestDir/testlist" >> delTestDir+ in+ ("testlist___" ++ makeName suitedata, createFilterFile, delFilterFile,+ shouldPass suitedata, map fst suitedata,+ quietOpts { testlist = ["TestDir/testlist"] })++makeCmdOptTestlistTest (cmdoptdata, testlistdata) =+ let+ createFilterFile =+ do+ createDirectory "TestDir"+ writeFile "TestDir/testlist"+ (intercalate "\n" (makeFilters testlistdata))++ delFilterFile = removeFile "TestDir/testlist" >> delTestDir+ in+ ("cmdopt_testlist____" ++ makeName cmdoptdata ++ "___" +++ makeName testlistdata, createFilterFile, delFilterFile,+ shouldPass cmdoptdata && shouldPass testlistdata, map fst cmdoptdata,+ quietOpts { testlist = ["TestDir/testlist"],+ filters = makeFilters cmdoptdata })++makeDualTestlistTest (suitedata1, suitedata2) =+ let+ createFilterFile =+ do+ createDirectory "TestDir"+ writeFile "TestDir/testlist1" (intercalate "\n" (makeFilters suitedata1))+ writeFile "TestDir/testlist2" (intercalate "\n" (makeFilters suitedata2))++ delFilterFile =+ do+ removeFile "TestDir/testlist1"+ removeFile "TestDir/testlist2"+ delTestDir+ in+ ("dual_testlist____" ++ makeName suitedata1 ++ "___" ++ makeName suitedata2,+ createFilterFile, delFilterFile,+ shouldPass suitedata1 && shouldPass suitedata2, [],+ quietOpts { testlist = ["TestDir/testlist1", "TestDir/testlist2"] })++mainTests = [+ ("multiple_console_mode", return (), return (), False, [],+ opts { consmode = [Quiet, Terminal] }),+ ("multiple_xml_report", return (), return (), False, [],+ opts { xmlreport = ["report1.xml", "report2.xml"] }),+ ("multiple_txt_report", return (), return (), False, [],+ opts { txtreport = ["report1.txt", "report2.txt"] }),+ ("multiple_xml_txt_report", return (), return (), False, [],+ opts { xmlreport = ["report1.xml", "report2.xml"],+ txtreport = ["report1.txt", "report2.txt"] }),+ ("nonexistent_xml_report", makeTestDir, delTestDir, False, [],+ opts { xmlreport = ["TestDir/nonexistent/report.xml"] }),+ ("nonexistent_txt_report", makeTestDir, delTestDir, False, [],+ opts { txtreport = ["TestDir/nonexistent/report.txt"] }),+ ("nonexistent_txt_xml_report", makeTestDir, delTestDir, False, [],+ opts { txtreport = ["TestDir/nonexistent/report.txt"],+ xmlreport = ["TestDir/nonexistent/report.xml"] }),+ ("nonexistent_testlist", makeTestDir, delTestDir, False, [],+ opts { xmlreport = ["TestDir/nonexistent/testlist"] }),+ ("run_quiet_no_xml_no_txt", return (), return (), True, [], quietOpts),+ ("run_terminal_no_xml_no_txt", return (), return (), True, [],+ opts { consmode = [Terminal] }),+ ("run_text_no_xml_no_txt", return (), return (), True, [],+ opts { consmode = [Text] }),+ ("run_verbose_no_xml_no_txt", return (), return (), True, [],+ opts { consmode = [Verbose] }),+ ("run_quiet_xml_no_txt", makeTestDir, delXMLReport, True, [],+ opts { consmode = [Quiet], xmlreport = ["TestDir/report.xml"] }),+ ("run_terminal_xml_no_txt", makeTestDir, delXMLReport, True, [],+ opts { consmode = [Terminal], xmlreport = ["TestDir/report.xml"] }),+ ("run_text_xml_no_txt", makeTestDir, delXMLReport, True, [],+ opts { consmode = [Text], xmlreport = ["TestDir/report.xml"] }),+ ("run_verbose_xml_no_txt", makeTestDir, delXMLReport, True, [],+ opts { consmode = [Verbose], xmlreport = ["TestDir/report.xml"] }),+ ("run_quiet_no_xml_txt", makeTestDir, delTxtReport, True, [],+ opts { consmode = [Quiet], txtreport = ["TestDir/report.txt"] }),+ ("run_terminal_no_xml_txt", makeTestDir, delTxtReport, True, [],+ opts { consmode = [Terminal], txtreport = ["TestDir/report.txt"] }),+ ("run_text_no_xml_txt", makeTestDir, delTxtReport, True, [],+ opts { consmode = [Text], txtreport = ["TestDir/report.txt"] }),+ ("run_verbose_no_xml_txt", makeTestDir, delTxtReport, True, [],+ opts { consmode = [Verbose], txtreport = ["TestDir/report.txt"] }),+ ("run_quiet_xml_no_txt", makeTestDir, delTxtXMLReport, True, [],+ opts { consmode = [Quiet], xmlreport = ["TestDir/report.xml"],+ txtreport = ["TestDir/report.txt"] }),+ ("run_terminal_xml_no_txt", makeTestDir, delTxtXMLReport, True, [],+ opts { consmode = [Terminal], xmlreport = ["TestDir/report.xml"],+ txtreport = ["TestDir/report.txt"] }),+ ("run_text_xml_no_txt", makeTestDir, delTxtXMLReport, True, [],+ opts { consmode = [Text], xmlreport = ["TestDir/report.xml"],+ txtreport = ["TestDir/report.txt"] }),+ ("run_verbose_xml_no_txt", makeTestDir, delTxtXMLReport, True, [],+ opts { consmode = [Verbose], xmlreport = ["TestDir/report.xml"],+ txtreport = ["TestDir/report.txt"] })+ ] +++ map makeCmdOptTest suiteCombos +++ map makeTestlistTest suiteCombos +++ map makeCmdOptTestlistTest suitePairCombos +++ map makeDualTestlistTest suitePairCombos++tests :: Test+tests = testGroup "Main" (map makeMainTest mainTests)
+ test/Tests/Test/HUnitPlus/ReporterUtils.hs view
@@ -0,0 +1,194 @@+{-# LANGUAGE OverloadedStrings #-}++module Tests.Test.HUnitPlus.ReporterUtils where++import Control.Monad+import Data.List+import Distribution.TestSuite(Result(Pass, Fail))+import Test.HUnitPlus.Reporting++import qualified Data.HashMap.Strict as HashMap+import qualified Data.Text as Strict++data ReportEvent =+ End Double+ | StartSuite+ | EndSuite Double+ | StartCase+ | EndCase Double+ | Skip+ | Progress Strict.Text+ | Failure Strict.Text+ | Error Strict.Text+ | Exception Strict.Text+ | SystemErr Strict.Text+ | SystemOut Strict.Text+ deriving (Show)++instance Eq ReportEvent where+ End e1 == End e2 = e1 == e2+ StartSuite == StartSuite = True+ EndSuite e1 == EndSuite e2 = e1 == e2+ StartCase == StartCase = True+ EndCase e1 == EndCase e2 = e1 == e2+ Skip == Skip = True+ Progress s1 == Progress s2 = s1 == s2+ Failure s1 == Failure s2 = s1 == s2+ Error s1 == Error s2 = s1 == s2+ Exception s1 == Error s2 = Strict.isInfixOf s1 s2+ Error s1 == Exception s2 = Strict.isInfixOf s2 s1+ Exception s1 == Exception s2 = s1 == s2+ SystemErr s1 == SystemErr s2 = s1 == s2+ SystemOut s1 == SystemOut s2 = s1 == s2+ _ == _ = False++type ReporterOp us = (State, us) -> IO (State, us)++loggingReporter :: Reporter [ReportEvent]+loggingReporter = defaultReporter {+ reporterStart = return [],+ reporterEnd = (\time _ events -> return (events ++ [End time])),+ reporterStartSuite = (\_ events -> return (events ++ [StartSuite])),+ reporterEndSuite = (\time _ events -> return (events ++ [EndSuite time])),+ reporterStartCase = (\_ events -> return (events ++ [StartCase])),+ reporterEndCase = (\time _ events -> return (events ++ [EndCase time])),+ reporterSkipCase = (\_ events -> return (events ++ [Skip])),+ reporterCaseProgress = (\msg _ events -> return (events ++ [Progress msg])),+ reporterFailure = (\msg _ events -> return (events ++ [Failure msg])),+ reporterError = (\msg _ events -> return (events ++ [Error msg])),+ reporterSystemErr = (\msg _ events -> return (events ++ [SystemErr msg])),+ reporterSystemOut = (\msg _ events -> return (events ++ [SystemOut msg]))+ }++initState :: State+initState = State { stName = "", stPath = [], stCounts = zeroCounts,+ stOptions = HashMap.empty, stOptionDescs = [] }++setName :: Strict.Text -> ReporterOp us+setName name (s @ State { stName = _ }, repstate) =+ return (s { stName = name }, repstate)++setOpt :: Strict.Text -> Strict.Text -> ReporterOp us+setOpt key value (s @ State { stOptions = opts }, repstate) =+ return (s { stOptions = HashMap.insert key value opts }, repstate)++pushPath :: Strict.Text -> ReporterOp us+pushPath name (s @ State { stPath = path }, repstate) =+ return (s { stPath = Label name : path }, repstate)++popPath :: ReporterOp us+popPath (s @ State { stPath = _ : path }, repstate) =+ return (s { stPath = path }, repstate)++addOption :: Strict.Text -> Strict.Text -> ReporterOp us+addOption key value (s @ State { stOptions = opts }, repstate) =+ return (s { stOptions = HashMap.insert key value opts }, repstate)++countAsserts :: Word -> ReporterOp us+countAsserts count (s @ State { stCounts = c @ Counts { cAsserts = n } },+ repstate) =+ return (s { stCounts = c { cAsserts = n + count,+ cCaseAsserts = count } }, repstate)++countTried :: Word -> ReporterOp us+countTried count (s @ State { stCounts = c @ Counts { cCases = cases,+ cTried = tried } },+ repstate) =+ return (s { stCounts = c { cCases = cases + count,+ cTried = tried + count } },+ repstate)++countSkipped :: Word -> ReporterOp us+countSkipped count (s @ State { stCounts = c @ Counts { cSkipped = skipped,+ cCases = cases } },+ repstate) =+ return (s { stCounts = c { cSkipped = skipped + count,+ cCases = cases + count } },+ repstate)++countErrors :: Word -> ReporterOp us+countErrors count (s @ State { stCounts = c @ Counts { cErrors = errors } },+ repstate) =+ return (s { stCounts = c { cErrors = errors + count } }, repstate)++countFailed :: Word -> ReporterOp us+countFailed count (s @ State { stCounts = c @ Counts { cFailures = failed } },+ repstate) =+ return (s { stCounts = c { cFailures = failed + count } }, repstate)++reportProgress :: Reporter us -> Strict.Text -> ReporterOp us+reportProgress reporter msg (state, repstate) =+ do+ repstate' <- (reporterCaseProgress reporter) msg state repstate+ return (state, repstate')++reportSystemErr :: Reporter us -> Strict.Text -> ReporterOp us+reportSystemErr reporter msg (state, repstate) =+ do+ repstate' <- (reporterSystemErr reporter) msg state repstate+ return (state, repstate')++reportSystemOut :: Reporter us -> Strict.Text -> ReporterOp us+reportSystemOut reporter msg (state, repstate) =+ do+ repstate' <- (reporterSystemOut reporter) msg state repstate+ return (state, repstate')++reportFailure :: Reporter us -> Strict.Text -> ReporterOp us+reportFailure reporter msg (state, repstate) =+ do+ repstate' <- (reporterFailure reporter) msg state repstate+ return (state, repstate')++reportError :: Reporter us -> Strict.Text -> ReporterOp us+reportError reporter msg (state, repstate) =+ do+ repstate' <- (reporterError reporter) msg state repstate+ return (state, repstate')++reportSkip :: Reporter us -> ReporterOp us+reportSkip reporter (state, repstate) =+ do+ repstate' <- (reporterSkipCase reporter) state repstate+ return (state, repstate')++reportStartCase :: Reporter us -> ReporterOp us+reportStartCase reporter (state, repstate) =+ do+ repstate' <- (reporterStartCase reporter) state repstate+ return (state, repstate')++reportEndCase :: Reporter us -> Double -> ReporterOp us+reportEndCase reporter time (state, repstate) =+ do+ repstate' <- (reporterEndCase reporter) time state repstate+ return (state, repstate')++reportStartSuite :: Reporter us -> ReporterOp us+reportStartSuite reporter (state, repstate) =+ do+ repstate' <- (reporterStartSuite reporter) state repstate+ return (state, repstate')++reportEndSuite :: Reporter us -> Double -> ReporterOp us+reportEndSuite reporter time (state, repstate) =+ do+ repstate' <- (reporterEndSuite reporter) time state repstate+ return (state, repstate')++reportEnd :: Reporter us -> Double -> ReporterOp us+reportEnd reporter time (state @ State { stCounts = counts }, repstate) =+ do+ repstate' <- (reporterEnd reporter) time counts repstate+ return (state, repstate')++runReporterTest :: Eq us => Reporter us -> [ReporterOp us] -> us ->+ (us -> String) -> IO Result+runReporterTest reporter tests expected format =+ do+ initrepstate <- reporterStart reporter+ (_, actual) <- foldM (\state op -> op state) (initState, initrepstate) tests+ if actual == expected+ then return Pass+ else return (Fail ("Expected " ++ format expected +++ "\nbut got " ++ format actual))
+ test/Tests/Test/HUnitPlus/Reporting.hs view
@@ -0,0 +1,115 @@+{-# LANGUAGE OverloadedStrings #-}++module Tests.Test.HUnitPlus.Reporting(tests) where++import Data.List+import Distribution.TestSuite(Test(..),+ TestInstance(..),+ Progress(Finished),+ Result(Pass, Fail),+ testGroup)+import Test.HUnitPlus.Reporting+import Tests.Test.HUnitPlus.ReporterUtils(ReportEvent(..))++import qualified Tests.Test.HUnitPlus.ReporterUtils as Utils++type ReporterState = [ReportEvent]+type CombinedReporterState = (ReporterState, ReporterState)+type ReporterOp = Utils.ReporterOp ReporterState+type CombinedReporterOp = Utils.ReporterOp CombinedReporterState+type DefaultReporterOp = Utils.ReporterOp Int++loggingReporter = Utils.loggingReporter++combinedLoggingReporter :: Reporter CombinedReporterState+combinedLoggingReporter = combinedReporter loggingReporter loggingReporter++reportSystemErr = Utils.reportSystemErr combinedLoggingReporter+reportSystemOut = Utils.reportSystemOut combinedLoggingReporter+reportFailure = Utils.reportFailure combinedLoggingReporter+reportError = Utils.reportError combinedLoggingReporter+reportSkip = Utils.reportSkip combinedLoggingReporter+reportProgress = Utils.reportProgress combinedLoggingReporter+reportStartCase = Utils.reportStartCase combinedLoggingReporter+reportEndCase = Utils.reportEndCase combinedLoggingReporter+runReporterTest = Utils.runReporterTest combinedLoggingReporter+reportStartSuite = Utils.reportStartSuite combinedLoggingReporter+reportEndSuite = Utils.reportEndSuite combinedLoggingReporter+reportEnd = Utils.reportEnd combinedLoggingReporter++reporterActions :: [(String, CombinedReporterOp, ReporterState)]+reporterActions = [+ ("systemErr", reportSystemErr "Error Message", [SystemErr "Error Message"]),+ ("systemOut", reportSystemOut "Output Message",+ [SystemOut "Output Message"]),+ ("failure", reportFailure "Failure Message", [Failure "Failure Message"]),+ ("error", reportError "Error Message", [Error "Error Message"]),+ ("progress", reportProgress "Progress Message",+ [Progress "Progress Message"]),+ ("skip", reportSkip, [Skip]),+ ("startCase", reportStartCase, [StartCase]),+ ("endCase", reportEndCase 1.0, [EndCase 1.0]),+ ("startSuite", reportStartSuite, [StartSuite]),+ ("endSuite", reportEndSuite 2.0, [EndSuite 2.0]),+ ("end", reportEnd 3.0, [End 3.0])+ ]++defaultReporterCases :: [(String, State -> Int -> IO Int, Int)]+defaultReporterCases = [+ ("systemErr", (reporterSystemErr defaultReporter) "Error Message", 1),+ ("systemOut", (reporterSystemOut defaultReporter) "Output Message", 2),+ ("failure", (reporterFailure defaultReporter) "Failure Message", 3),+ ("error", (reporterError defaultReporter) "Error Message", 4),+ ("progress", (reporterCaseProgress defaultReporter) "Progress Message", 5),+ ("skip", reporterSkipCase defaultReporter, 6),+ ("startCase", reporterStartCase defaultReporter, 7),+ ("endCase", (reporterEndCase defaultReporter) 1.0, 8),+ ("startSuite", reporterStartSuite defaultReporter, 9),+ ("endSuite", (reporterEndSuite defaultReporter) 2.0, 10),+ ("end", (reporterEnd defaultReporter) 3.0 . stCounts, 11)+ ]++reporterCases :: [[(String, CombinedReporterOp, ReporterState)]]+reporterCases =+ map (: []) reporterActions +++ foldr (\a accum ->+ foldr (\b accum -> [a, b] : accum)+ accum reporterActions)+ [] reporterActions++genCombinedReporterTest :: [(String, CombinedReporterOp, ReporterState)] -> Test+genCombinedReporterTest testactions =+ let+ name = intercalate "_" (map (\(a, _, _) -> a) testactions)+ ops = map (\(_, a, _) -> a) testactions+ log = concat (map (\(_, _, a) -> a) testactions)+ expected = (log, log)++ out = TestInstance { name = "combinedReporter_" ++ name,+ tags = [], options = [],+ setOption = (\_ _ -> Right out),+ run = runReporterTest ops expected show >>=+ return . Finished }+ in+ Test out++genDefaultReporterTest :: (String, State -> Int -> IO Int, Int) -> Test+genDefaultReporterTest (name, reporterAction, reporterState) =+ let+ runTest =+ do+ result <- reporterAction Utils.initState reporterState+ if result == reporterState+ then return (Finished Pass)+ else return (Finished (Fail "defaultReporter altered state!"))++ out = TestInstance { name = "defaultReporter_" ++ name,+ tags = [], options = [], run = runTest,+ setOption = (\_ _ -> Right out) }+ in+ Test out+++tests :: Test+tests = testGroup "Reporting" (map genCombinedReporterTest reporterCases +++ map genDefaultReporterTest defaultReporterCases)
+ test/Tests/Test/HUnitPlus/Text.hs view
@@ -0,0 +1,7 @@+module Tests.Test.HUnitPlus.Text where++import Distribution.TestSuite+import Test.HUnitPlus.Text++tests :: Test+tests = testGroup "Text" []
+ test/Tests/Test/HUnitPlus/XML.hs view
@@ -0,0 +1,348 @@+{-# LANGUAGE OverloadedStrings #-}++module Tests.Test.HUnitPlus.XML(tests) where++import Distribution.TestSuite+import Network.HostName+import System.IO.Unsafe+import Test.HUnitPlus.XML+import Text.XML.Expat.Format+import Text.XML.Expat.Tree++import qualified Data.ByteString.Lazy.Char8 as BC+import qualified Data.Text as Strict+import qualified Tests.Test.HUnitPlus.ReporterUtils as Utils++type ReporterState = [[Node Strict.Text Strict.Text]]+type ReporterOp = Utils.ReporterOp ReporterState++countAsserts = Utils.countAsserts+countTried = Utils.countTried+countSkipped = Utils.countSkipped+countErrors = Utils.countErrors+countFailed = Utils.countFailed+setOpt = Utils.setOpt+setName = Utils.setName+pushPath = Utils.pushPath+popPath = Utils.popPath+reportSystemErr = Utils.reportSystemErr xmlReporter+reportSystemOut = Utils.reportSystemOut xmlReporter+reportFailure = Utils.reportFailure xmlReporter+reportError = Utils.reportError xmlReporter+reportSkip = Utils.reportSkip xmlReporter+reportStartCase = Utils.reportStartCase xmlReporter+reportEndCase = Utils.reportEndCase xmlReporter+runReporterTest = Utils.runReporterTest xmlReporter+reportStartSuite = Utils.reportStartSuite xmlReporter+reportEnd = Utils.reportEnd xmlReporter++reportEndSuite :: Double -> ReporterOp+reportEndSuite time state =+ let+ removeTimestamp ((e @ Element { eAttributes = attrs } : rest) : stack) =+ (e { eAttributes = filter ((/= "timestamp") . fst) attrs } : rest) : stack+ removeTimestamp out = out+ in do+ (state, repstate) <- Utils.reportEndSuite xmlReporter time state+ return (state, removeTimestamp repstate)++reporterTestCases :: [(String, [ReporterOp], Node Strict.Text Strict.Text)]+reporterTestCases =+ [("xmlReporter_systemErr", [reportSystemErr "Error Message Content"],+ Element { eName = "system-err", eChildren = [Text "Error Message Content"],+ eAttributes = [] }),+ ("xmlReporter_systemOut", [reportSystemOut "Message Content"],+ Element { eName = "system-out", eChildren = [Text "Message Content"],+ eAttributes = [] }),+ ("xmlReporter_failure", [reportFailure "Failure Message"],+ Element { eName = "failure", eChildren = [],+ eAttributes = [("message", "Failure Message")] }),+ ("xmlReporter_error", [reportError "Error Message"],+ Element { eName = "error", eChildren = [],+ eAttributes = [("message", "Error Message")] }),+ ("xmlReporter_skip", [setName "Test", pushPath "Path", reportSkip],+ Element { eName = "testcase",+ eChildren = [Element { eName = "skipped",+ eAttributes = [],+ eChildren = [] }],+ eAttributes = [("name", "Test"),+ ("classname", "Path")] }),+ ("xmlReporter_empty_case",+ [setName "Test", pushPath "Path", pushPath "Inner", reportStartCase,+ countAsserts 3, reportEndCase pi],+ Element { eName = "testcase", eChildren = [],+ eAttributes = [("name", "Test"),+ ("classname", "Path.Inner"),+ ("assertions", Strict.pack (show 3)),+ ("time", Strict.pack (show pi))] }),+ ("xmlReporter_output_case",+ [setName "Test", pushPath "Path", pushPath "Inner",+ reportStartCase, countAsserts 3,+ reportSystemErr "Error Message Content", reportSystemOut "Message Content",+ reportEndCase pi],+ Element { eName = "testcase",+ eChildren = [Element { eName = "system-err",+ eChildren = [Text "Error Message Content"],+ eAttributes = [] },+ Element { eName = "system-out",+ eChildren = [Text "Message Content"],+ eAttributes = [] }],+ eAttributes = [("name", "Test"),+ ("classname", "Path.Inner"),+ ("assertions", Strict.pack (show 3)),+ ("time", Strict.pack (show pi))] }),+ ("xmlReporter_failing_case",+ [setName "Test", pushPath "Path", pushPath "Inner",+ reportStartCase, countAsserts 3,+ reportSystemErr "Error Message Content", reportSystemOut "Message Content",+ reportFailure "Failure Message", reportEndCase pi],+ Element { eName = "testcase",+ eChildren = [Element { eName = "system-err",+ eChildren = [Text "Error Message Content"],+ eAttributes = [] },+ Element { eName = "system-out",+ eChildren = [Text "Message Content"],+ eAttributes = [] },+ Element { eName = "failure", eChildren = [],+ eAttributes = [("message",+ "Failure Message")] }],+ eAttributes = [("name", "Test"),+ ("classname", "Path.Inner"),+ ("assertions", Strict.pack (show 3)),+ ("time", Strict.pack (show pi))] }),+ ("xmlReporter_error_case",+ [setName "Test", pushPath "Path", pushPath "Inner",+ reportStartCase, countAsserts 3,+ reportSystemErr "Error Message Content", reportSystemOut "Message Content",+ reportError "Error Message", reportEndCase pi],+ Element { eName = "testcase",+ eChildren = [Element { eName = "system-err",+ eChildren = [Text "Error Message Content"],+ eAttributes = [] },+ Element { eName = "system-out",+ eChildren = [Text "Message Content"],+ eAttributes = [] },+ Element { eName = "error", eChildren = [],+ eAttributes = [("message",+ "Error Message")] }],+ eAttributes = [("name", "Test"),+ ("classname", "Path.Inner"),+ ("assertions", Strict.pack (show 3)),+ ("time", Strict.pack (show pi))] }),+ ("xmlReporter_empty_suite",+ [setName "Test", pushPath "Path", reportStartSuite,+ countTried 4, countSkipped 3, countErrors 2, countFailed 1,+ reportEndSuite pi],+ Element { eName = "testsuite", eChildren = [],+ eAttributes = [("name", "Test"),+ ("hostname", Strict.pack hostname),+ ("time", Strict.pack (show pi)),+ ("tests", Strict.pack (show 7)),+ ("failures", Strict.pack (show 1)),+ ("errors", Strict.pack (show 2)),+ ("skipped", Strict.pack (show 3))] }),+ ("xmlReporter_options_suite",+ [setName "Test", pushPath "Path", reportStartSuite,+ setOpt "option1" "value1", setOpt "option2" "value2",+ countTried 4, countSkipped 3, countErrors 2, countFailed 1,+ reportEndSuite pi],+ Element { eName = "testsuite",+ eChildren =+ [Element { eName = "properties", eAttributes = [],+ eChildren =+ [Element { eName = "property", eChildren = [],+ eAttributes = [("name", "option1"),+ ("value", "value1")] },+ Element { eName = "property", eChildren = [],+ eAttributes = [("name", "option2"),+ ("value", "value2")] }+ ] }],+ eAttributes = [("name", "Test"),+ ("hostname", Strict.pack hostname),+ ("time", Strict.pack (show pi)),+ ("tests", Strict.pack (show 7)),+ ("failures", Strict.pack (show 1)),+ ("errors", Strict.pack (show 2)),+ ("skipped", Strict.pack (show 3))] }),+ ("xmlReporter_content_suite",+ [setName "Test", pushPath "Path", reportStartSuite,+ countTried 4, setName "Pass", reportStartCase, countAsserts 3,+ reportSystemErr "Error Message Content", reportSystemOut "Message Content",+ reportEndCase (pi - 1),+ setName "Skip", pushPath "Inner", reportSkip, countSkipped 1, popPath,+ setName "Fail", countErrors 1, reportStartCase, countAsserts 4,+ reportFailure "Failure Message", reportEndCase (pi / 2),+ setName "Error", countFailed 1, reportStartCase, countAsserts 2,+ reportError "Error Message", reportEndCase pi,+ reportSystemErr "Suite Error Message", reportSystemOut "Suite Message",+ setName "Test", reportEndSuite (pi * pi)],+ Element { eName = "testsuite",+ eAttributes = [("name", "Test"),+ ("hostname", Strict.pack hostname),+ ("time", Strict.pack (show (pi * pi))),+ ("tests", Strict.pack (show 5)),+ ("failures", Strict.pack (show 1)),+ ("errors", Strict.pack (show 1)),+ ("skipped", Strict.pack (show 1))],+ eChildren =+ [Element { eName = "testcase",+ eChildren =+ [Element { eName = "system-err",+ eChildren =+ [Text "Error Message Content"],+ eAttributes = [] },+ Element { eName = "system-out",+ eChildren = [Text "Message Content"],+ eAttributes = [] }],+ eAttributes = [("name", "Pass"),+ ("classname", "Path"),+ ("assertions", Strict.pack (show 3)),+ ("time", Strict.pack (show (pi - 1)))] },+ Element { eName = "testcase",+ eChildren = [Element { eName = "skipped",+ eAttributes = [],+ eChildren = [] }],+ eAttributes = [("name", "Skip"),+ ("classname", "Path.Inner")] },+ Element { eName = "testcase",+ eChildren =+ [Element { eName = "failure", eChildren = [],+ eAttributes = [("message",+ "Failure Message")] }],+ eAttributes = [("name", "Fail"),+ ("classname", "Path"),+ ("assertions", Strict.pack (show 4)),+ ("time", Strict.pack (show (pi / 2)))] },+ Element { eName = "testcase",+ eChildren =+ [Element { eName = "error", eChildren = [],+ eAttributes = [("message",+ "Error Message")] }],+ eAttributes = [("name", "Error"),+ ("classname", "Path"),+ ("assertions", Strict.pack (show 2)),+ ("time", Strict.pack (show pi))] },+ Element { eName = "system-err",+ eChildren = [Text "Suite Error Message"],+ eAttributes = [] },+ Element { eName = "system-out",+ eChildren = [Text "Suite Message"],+ eAttributes = [] }+ ] }),+ ("xmlReporter_empty_suites", [reportEnd pi],+ Element { eName = "testsuites", eChildren = [],+ eAttributes = [("time", Strict.pack (show pi))] }),+ ("xmlReporter_content_suites",+ [setName "Empty", reportStartSuite,+ countTried 5, countSkipped 4, countErrors 2, countFailed 1,+ reportEndSuite pi,+ setName "Test", pushPath "Path", reportStartSuite,+ setOpt "option1" "value1", setOpt "option2" "value2",+ countTried 4, setName "Pass", reportStartCase, countAsserts 3,+ reportSystemErr "Error Message Content", reportSystemOut "Message Content",+ reportEndCase (pi - 1),+ setName "Skip", pushPath "Inner", reportSkip, countSkipped 1, popPath,+ setName "Fail", countErrors 1, reportStartCase, countAsserts 4,+ reportFailure "Failure Message", reportEndCase (pi / 2),+ setName "Error", countFailed 1, reportStartCase, countAsserts 2,+ reportError "Error Message", reportEndCase pi,+ reportSystemErr "Suite Error Message", reportSystemOut "Suite Message",+ setName "Test", reportEndSuite (pi * pi), reportEnd (sqrt pi)],+ Element {+ eName = "testsuites", eAttributes = [("time", Strict.pack (show (sqrt pi)))],+ eChildren =+ [Element {+ eName = "testsuite", eChildren = [],+ eAttributes = [("name", "Empty"),+ ("hostname", Strict.pack hostname),+ ("time", Strict.pack (show pi)),+ ("tests", Strict.pack (show 9)),+ ("failures", Strict.pack (show 1)),+ ("errors", Strict.pack (show 2)),+ ("skipped", Strict.pack (show 4))] },+ Element {+ eName = "testsuite",+ eAttributes = [("name", "Test"),+ ("hostname", Strict.pack hostname),+ ("time", Strict.pack (show (pi * pi))),+ ("tests", Strict.pack (show 14)),+ ("failures", Strict.pack (show 2)),+ ("errors", Strict.pack (show 3)),+ ("skipped", Strict.pack (show 5))],+ eChildren =+ [Element { eName = "properties", eAttributes = [],+ eChildren =+ [Element { eName = "property", eChildren = [],+ eAttributes = [("name", "option1"),+ ("value", "value1")] },+ Element { eName = "property", eChildren = [],+ eAttributes = [("name", "option2"),+ ("value", "value2")] }+ ] },+ Element {+ eName = "testcase",+ eChildren =+ [Element { eName = "system-err",+ eChildren =+ [Text "Error Message Content"],+ eAttributes = [] },+ Element { eName = "system-out",+ eChildren = [Text "Message Content"],+ eAttributes = [] }],+ eAttributes = [("name", "Pass"),+ ("classname", "Path"),+ ("assertions", Strict.pack (show 3)),+ ("time", Strict.pack (show (pi - 1)))] },+ Element { eName = "testcase",+ eChildren = [Element { eName = "skipped",+ eAttributes = [],+ eChildren = [] }],+ eAttributes = [("name", "Skip"),+ ("classname", "Path.Inner")] },+ Element { eName = "testcase",+ eChildren =+ [Element { eName = "failure", eChildren = [],+ eAttributes = [("message",+ "Failure Message")] }],+ eAttributes = [("name", "Fail"),+ ("classname", "Path"),+ ("assertions", Strict.pack (show 4)),+ ("time", Strict.pack (show (pi / 2)))] },+ Element { eName = "testcase",+ eChildren =+ [Element { eName = "error", eChildren = [],+ eAttributes = [("message",+ "Error Message")] }],+ eAttributes = [("name", "Error"),+ ("classname", "Path"),+ ("assertions", Strict.pack (show 2)),+ ("time", Strict.pack (show pi))] },+ Element { eName = "system-err",+ eChildren = [Text "Suite Error Message"],+ eAttributes = [] },+ Element { eName = "system-out",+ eChildren = [Text "Suite Message"],+ eAttributes = [] }+ ] }+ ] })+ ]++{-# NOINLINE hostname #-}+hostname :: String+hostname = unsafePerformIO $ getHostName++genReporterTest :: (String, [ReporterOp], Node Strict.Text Strict.Text) -> Test+genReporterTest (name, op, expected) =+ let+ format :: [[Node Strict.Text Strict.Text]] -> String+ format = concat . map (concat . map (BC.unpack . formatNode . indent 2))++ out = TestInstance { name = name, tags = [], options = [],+ setOption = (\_ _ -> Right out),+ run = runReporterTest op [[expected]] format >>=+ return . Finished }+ in+ Test out++tests :: Test+tests = testGroup "XML" (map genReporterTest reporterTestCases)