Revamped the TestHarness framework to allow multiple substitutions in a test prologue

This commit is contained in:
Neil Brown 2009-01-20 23:43:07 +00:00
parent c8a8370636
commit d806931fbf

View File

@ -21,11 +21,21 @@ with this program. If not, see <http://www.gnu.org/licenses/>.
This TestHarness module contains helper functions that deal with a file of the following format.
There is an initial prologue, assumed to be source code. Any lines beginning with "%%" are deemed
to be destinations for substitution. The prologue is terminated by the first test.
to be destinations for substitution. You can label different substitutions by
following the double-percent with other characters (but not PASS or FAIL), e.g. %%2.
The prologue is terminated by the first test.
Each test has a starting line that begins with a single % followed by either PASS or FAIL,
optionally followed by an * (indicating this test is a good benchmark for timing)
followed by a test name. The lines that run to the start of the next test are the source substitution.
followed by a test name. The lines that run to the start of the next test, or
to the next line beginning with a percent are the source substitution. If you have
two or more substitution destinations (e.g. %% and %%2), you substitute them with
%PASS
First substitution
%2
Second substitution
i.e. using the label after your percent.
The file is terminated by a single percent on its own line.
@ -34,13 +44,11 @@ The file is terminated by a single percent on its own line.
module TestHarness (automaticTest) where
import Control.Monad.Error
import Control.Monad.State
import Control.Monad.Writer
import Data.List
import Data.Maybe
import System.IO
import Test.HUnit hiding (performTest)
import Text.Regex
import CompState
import ParseOccam
@ -50,6 +58,14 @@ import PassList
import PreprocessOccam
import Utils
data TestLine = Line String | Sub String {- without percents -}
data TestBody = TestBody Bool String [(String {- substName -}, [String] {- content -})]
data AutoTest = AutoTest
{ prologueLines :: [TestLine]
, bodies :: [TestBody]
}
automaticTest :: CompFrontend -> FilePath -> IO Test
automaticTest fr fileName = readFile fileName >>* performTest fr fileName
@ -79,8 +95,19 @@ testRain source = do (result,_) <- runPassM (defaultState FrontendRain) compila
>>= runPasses (getPassList $ defaultState FrontendRain)
-- | Substitutes each substitution into the prologue
substitute :: String -> [(Bool, Bool, String, String)] -> [(Bool, Bool, String, String)]
substitute prologue = map (\(a,b,c,subst) -> (a,b,c,subRegex (mkRegex "%%") prologue subst))
substitute :: AutoTest -> Either String [(Bool, String, String)]
substitute t = sequence [ do ls <- execWriterT $ subst (prologueLines t, ss)
return (p, n, unlines ls)
| TestBody p n ss <- bodies t]
where
subst :: ([TestLine], [(String, [String])]) -> WriterT [String] (Either String) ()
subst ([], []) = return ()
subst ([], subs) = throwError $ "Left over substitutions: " ++ show subs
subst (Line l : ls, subs) = tell [l] >> subst (ls, subs)
subst (Sub s : ls, subs)
= case lookup s subs of
Just subLines -> tell subLines >> subst (ls, filter ((/= s) . fst) subs)
Nothing -> throwError $ "Could not find substitution \"" ++ s ++ "\""
-- | Given a file's contents, tests it
@ -88,11 +115,14 @@ performTest :: CompFrontend -> String -> String -> Test
performTest fr fileName fileContents
= case parseTestFile fileContents of
Left err -> TestCase $ assertFailure $ "Error processing file \"" ++ fileName ++ "\": " ++ err
Right (prologue,tests) -> TestLabel fileName $ TestList $ map performTest' (substitute prologue tests)
Right test -> TestLabel fileName $ TestList $
either
(singleton . TestCase . assertFailure . (fileName ++))
(map performTest')
(substitute test)
where
performTest' :: (Bool, Bool, String, String) -> Test
performTest' (expPass, _, testName, testText)
performTest' :: (Bool, String, String) -> Test
performTest' (expPass, testName, testText)
= TestCase $
do result <- (if fr == FrontendOccam then testOccam else testRain) testText
case result of
@ -100,23 +130,44 @@ performTest fr fileName fileContents
Nothing -> if expPass then return () else assertFailure (testName ++ " expected to fail but passed")
-- | Splits a file's contents into the prologue, and subsequent testcases
parseTestFile :: String -> Either String (String, [(Bool, Bool, String, String)])
parseTestFile wholeText = seqPair (return $ unlines prologue, splitCases testcases)
parseTestFile :: String -> Either String AutoTest
parseTestFile wholeText = liftM2 AutoTest parsePrologue (splitCases testcases)
where
allLines = lines wholeText
(prologue, testcases) = span (\l -> ("%%" `isPrefixOf` l) || (not $ "%" `isPrefixOf` l)) allLines
parsePrologue :: Either String [TestLine]
parsePrologue = if nub subs == subs then return parsed else
throwError "Multiple substitutions with the same name"
where
subs = [s | Sub s <- parsed]
parsed = [ if "%%" `isPrefixOf` l
then Sub $ drop 2 l
else Line l
| l <- prologue ]
splitCases :: [String] -> Either String [(Bool, Bool, String, String)]
splitCases :: [String] -> Either String [TestBody]
splitCases [] = throwError "Unexpected EOF"
splitCases (headLine:otherLines)
| "%PASS" `isPrefixOf` headLine = joinM (True, hasStar, testTitle, unlines testCaseLines) (splitCases furtherLines)
| "%FAIL" `isPrefixOf` headLine = joinM (False, hasStar, testTitle, unlines testCaseLines) (splitCases furtherLines)
| "%" == headLine = return []
| "%PASS" `isPrefixOf` headLine
= (TestBody True testTitle $ foldl testCaseSubs [("",[])] testCaseLines)
`joinM` (splitCases furtherLines)
| "%FAIL" `isPrefixOf` headLine
= (TestBody False testTitle $ foldl testCaseSubs [("",[])] testCaseLines)
`joinM` (splitCases furtherLines)
| "%" == headLine = return []
| otherwise = throwError $ "Unexpected format in testcase-header line: \"" ++ headLine ++ "\""
where
(hasStar, testTitle) = if headLine !! 5 == '*' then (True, drop 6 headLine) else (False, drop 5 headLine)
testTitle = drop 5 headLine
(testCaseLines, furtherLines) = span (not . isPrefixOf "%") otherLines
(testCaseLines, furtherLines) = span (\x -> not $ "%PASS" `isPrefixOf`
x || "%FAIL" `isPrefixOf` x || "%" == x) otherLines
testCaseSubs :: [(String, [String])] -> String -> [(String, [String])]
testCaseSubs acc ('%':id) = (id, []) : acc -- start new subst
testCaseSubs ((aName, aSubst):as) l = (aName,aSubst++[l]) : as -- join to current
joinM :: Monad m => a -> m [a] -> m [a]
joinM x mxs = mxs >>* (x :)