diff options
Diffstat (limited to 'test/runTests.hs')
-rw-r--r-- | test/runTests.hs | 198 |
1 files changed, 198 insertions, 0 deletions
diff --git a/test/runTests.hs b/test/runTests.hs new file mode 100644 index 00000000..a25e6158 --- /dev/null +++ b/test/runTests.hs | |||
@@ -0,0 +1,198 @@ | |||
1 | {-# LANGUAGE OverloadedStrings, PackageImports, LambdaCase #-} | ||
2 | {-# LANGUAGE FlexibleContexts #-} | ||
3 | module Main where | ||
4 | |||
5 | import Data.List | ||
6 | import Control.Applicative | ||
7 | import Control.Arrow | ||
8 | import Control.Concurrent | ||
9 | import Control.Concurrent.Async | ||
10 | import Control.Monad | ||
11 | import Control.Monad.Reader | ||
12 | |||
13 | import System.Environment | ||
14 | import System.Exit | ||
15 | import System.Directory | ||
16 | import System.FilePath | ||
17 | import System.IO | ||
18 | import Control.Exception hiding (catch) | ||
19 | import Control.Monad.Trans.Control | ||
20 | import Control.Monad.Catch | ||
21 | import Control.DeepSeq | ||
22 | import qualified Data.Set as Set | ||
23 | |||
24 | import LambdaCube.Compiler.Pretty hiding ((</>)) | ||
25 | import LambdaCube.Compiler.CGExp | ||
26 | import LambdaCube.Compiler.Driver | ||
27 | import LambdaCube.Compiler.CoreToIR | ||
28 | import IR (Backend(..)) | ||
29 | import Text.Parsec.Pos | ||
30 | |||
31 | instance NFData SourcePos where | ||
32 | rnf _ = () | ||
33 | |||
34 | acceptPath = "./testdata/accept" | ||
35 | rejectPath = "./testdata/reject" | ||
36 | timeout = 15 {- in seconds -} | ||
37 | |||
38 | data Res = Accepted | New | Rejected | Failed | ErrorCatched | ||
39 | deriving (Eq, Ord, Show) | ||
40 | |||
41 | erroneous = (>= Rejected) | ||
42 | |||
43 | instance NFData Res where | ||
44 | rnf a = a `seq` () | ||
45 | |||
46 | optionArgs = ["-v", "-r"] | ||
47 | |||
48 | main :: IO () | ||
49 | main = do | ||
50 | hSetBuffering stdout NoBuffering | ||
51 | hSetBuffering stdin NoBuffering | ||
52 | args <- getArgs | ||
53 | |||
54 | let samplesToAccept = filter (not . flip elem optionArgs) args | ||
55 | verbose = elem "-v" args | ||
56 | reject = elem "-r" args | ||
57 | (testToAccept,testToReject) <- case samplesToAccept of | ||
58 | [] -> do | ||
59 | toAccept <- map dropExtension . filter (\n -> ".lc" == takeExtension n) <$> getDirectoryContents acceptPath | ||
60 | toReject <- map dropExtension . filter (\n -> ".lc" == takeExtension n) <$> getDirectoryContents rejectPath | ||
61 | return (toAccept, toReject) | ||
62 | _ -> do | ||
63 | let intersect = Set.toList . Set.intersection (Set.fromList samplesToAccept) . Set.fromList | ||
64 | toAccept <- intersect . map dropExtension . filter (\n -> ".lc" == takeExtension n) <$> getDirectoryContents acceptPath | ||
65 | toReject <- intersect . map dropExtension . filter (\n -> ".lc" == takeExtension n) <$> getDirectoryContents rejectPath | ||
66 | return (toAccept, toReject) | ||
67 | |||
68 | n <- runMM' $ do | ||
69 | liftIO $ putStrLn $ "------------------------------------ Checking valid pipelines" | ||
70 | n1 <- acceptTests reject testToAccept | ||
71 | |||
72 | liftIO $ putStrLn $ "------------------------------------ Catching errors (must get an error)" | ||
73 | n2 <- rejectTests reject testToReject | ||
74 | |||
75 | return $ n1 ++ n2 | ||
76 | |||
77 | let sh b ty = [(if erroneous ty then "!" else "") ++ show (length ss) ++ " " ++ pad 10 (b ++ ": ") ++ intercalate ", " ss | not $ null ss] | ||
78 | where | ||
79 | ss = sort [s | (ty', s) <- n, ty' == ty] | ||
80 | let results = [t | (t,_) <- n] | ||
81 | |||
82 | putStrLn $ "------------------------------------ Summary\n" ++ | ||
83 | if null n | ||
84 | then "All OK" | ||
85 | else unlines $ | ||
86 | sh "crashed test" ErrorCatched | ||
87 | ++ sh "failed test" Failed | ||
88 | ++ sh "rejected result" Rejected | ||
89 | ++ sh "new result" New | ||
90 | ++ sh "accepted result" Accepted | ||
91 | when (any erroneous results) exitFailure | ||
92 | |||
93 | acceptTests reject = testFrame reject [acceptPath, rejectPath] $ \case | ||
94 | Left e -> Left e | ||
95 | Right (Left e, i) -> Right ("typechecked", unlines $ e: "tooltips:": [showRange (b, e) ++ " " ++ m | (b, e, m) <- i]) | ||
96 | Right (Right e, i) | ||
97 | | True <- i `deepseq` False -> error "impossible" | ||
98 | | tyOf e == TCon0 "Output" | ||
99 | -> Right ("compiled main", show . compilePipeline True OpenGL33 $ e) | ||
100 | | tyOf e == TCon0 "Bool" -> case e of | ||
101 | x@(A0 "True") -> Right ("main ~~> True", ppShow x) | ||
102 | x -> Left $ "main should be True but it is \n" ++ ppShow x | ||
103 | | otherwise -> Right ("reduced main " ++ ppShow (tyOf e), ppShow e) | ||
104 | -- | otherwise -> Right ("System-F main ", ppShow . toCore mempty $ e) | ||
105 | |||
106 | rejectTests reject = testFrame reject [rejectPath, acceptPath] $ \case | ||
107 | Left e -> Right ("error message", e) | ||
108 | Right _ -> Left "failed to catch error" | ||
109 | |||
110 | runMM' = fmap (either (error "impossible") id . fst) . runMM (ioFetch []) | ||
111 | |||
112 | testFrame :: Bool -> [FilePath] -> (Either String (Either String Exp, Infos) -> Either String (String, String)) -> [String] -> MMT IO [(Res, String)] | ||
113 | testFrame reject dirs f tests | ||
114 | = local (const $ ioFetch dirs') . testFrame_ compare (head dirs') (\n -> do | ||
115 | result <- catchMM $ getDef (ExpN n) (ExpN "main") Nothing | ||
116 | return $ f result) $ tests | ||
117 | where | ||
118 | compare = if reject then alwaysReject else compareResult | ||
119 | dirs_ = [takeDirectory f | f <- tests, takeFileName f /= f] | ||
120 | dirs' = if null dirs_ then dirs else dirs_ | ||
121 | |||
122 | |||
123 | timeOut :: Int -> a -> MM a -> MM a | ||
124 | timeOut n d m = MMT $ | ||
125 | control (\runInIO -> | ||
126 | race' (runInIO (runMMT m)) | ||
127 | (threadDelay (n * 1000000) >> (runInIO $ return d))) | ||
128 | where | ||
129 | race' a b = either id id <$> race a b | ||
130 | |||
131 | testFrame_ compareResult path action tests = fmap concat $ forM (zip [1..] (tests :: [String])) $ \(i, n) -> do | ||
132 | let er e = do | ||
133 | liftIO $ putStrLn $ "\n!Crashed " ++ n ++ "\n" ++ tab e | ||
134 | return [(ErrorCatched, n)] | ||
135 | catchErr er $ do | ||
136 | result <- timeOut timeout (Left "Timed Out") (action n) | ||
137 | liftIO $ case result of | ||
138 | Left e -> do | ||
139 | putStrLn $ "\n!Failed " ++ n ++ "\n" ++ tab e | ||
140 | return [(Failed, n)] | ||
141 | Right (op, x) -> do | ||
142 | length x `seq` compareResult n (pad 15 op) (path </> (n ++ ".out")) x | ||
143 | where | ||
144 | tab = unlines . map (" " ++) . lines | ||
145 | |||
146 | -- Reject unrigestered or chaned results automatically | ||
147 | alwaysReject n msg ef e = doesFileExist ef >>= \b -> case b of | ||
148 | False -> putStrLn ("Unregistered - " ++ msg) >> return [(Rejected, n)] | ||
149 | True -> do | ||
150 | e' <- readFile ef | ||
151 | case map fst $ filter snd $ zip [0..] $ zipWith (/=) e e' of | ||
152 | [] -> return [] | ||
153 | rs -> do | ||
154 | putStrLn $ msg ++ " has changed." | ||
155 | putStrLn "------------------------------------------- Old" | ||
156 | putStrLn $ showRanges ef rs e' | ||
157 | putStrLn "------------------------------------------- New" | ||
158 | putStrLn $ showRanges ef rs e | ||
159 | putStrLn "-------------------------------------------" | ||
160 | return [(Rejected, n)] | ||
161 | |||
162 | compareResult n msg ef e = doesFileExist ef >>= \b -> case b of | ||
163 | False -> writeFile ef e >> putStrLn ("OK - " ++ msg ++ " is written") >> return [(New, n)] | ||
164 | True -> do | ||
165 | e' <- readFile ef | ||
166 | case map fst $ filter snd $ zip [0..] $ zipWith (/=) e e' ++ replicate (abs $ length e - length e') True of | ||
167 | [] -> return [] | ||
168 | rs -> do | ||
169 | putStrLn $ msg ++ " has changed." | ||
170 | putStrLn "------------------------------------------- Old" | ||
171 | putStrLn $ showRanges ef rs e' | ||
172 | putStrLn "------------------------------------------- New" | ||
173 | putStrLn $ showRanges ef rs e | ||
174 | putStrLn "-------------------------------------------" | ||
175 | putStr $ "Accept new " ++ msg ++ " (y/n)? " | ||
176 | c <- length e' `seq` getChar | ||
177 | if c `elem` ("yY\n" :: String) | ||
178 | then writeFile ef e >> putStrLn " - accepted." >> return [(Accepted, n)] | ||
179 | else putStrLn " - not Accepted." >> return [(Rejected, n)] | ||
180 | |||
181 | pad n s = s ++ replicate (n - length s) ' ' | ||
182 | |||
183 | limit :: String -> Int -> String -> String | ||
184 | limit msg n s = take n s ++ if null (drop n s) then "" else msg | ||
185 | |||
186 | showRanges :: String -> [Int] -> String -> String | ||
187 | showRanges fname is e = (if head rs == 0 then "" else "...\n") | ||
188 | ++ limit ("\n... (see " ++ fname ++ " for more differences)") 140000 (intercalate "\n...\n" $ f (zipWith (-) rs (0:rs)) e) | ||
189 | where | ||
190 | f :: [Int] -> String -> [String] | ||
191 | f (i:is) e = g is $ drop i e | ||
192 | f [] "" = [] | ||
193 | f [] _ = ["\n..."] | ||
194 | g (i:is) e = take i e: f is (drop i e) | ||
195 | rs = (head is - x) : concat [[a + x, b - x] | (a, b) <- zip is (tail is), a + y < b] ++ [last is + x] | ||
196 | x = 100000 | ||
197 | y = 3*x | ||
198 | |||