Tests.hs 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346
  1. {-# LANGUAGE DoAndIfThenElse #-}
  2. {-# LANGUAGE LambdaCase #-}
  3. {-# LANGUAGE PatternGuards #-}
  4. module Compiler.Tests where
  5. import Utils
  6. import Test.Tasty
  7. import Test.Tasty.Silver.Advanced (readFileMaybe)
  8. import Test.Tasty.Silver
  9. import Test.Tasty.Silver.Filter
  10. import Data.Bits (finiteBitSize)
  11. import qualified Data.Text as T
  12. import Data.Text.Encoding
  13. import Data.Monoid
  14. import Data.List (isPrefixOf)
  15. import System.Directory
  16. import System.IO.Temp
  17. import System.FilePath
  18. import System.Environment
  19. import System.Exit
  20. import qualified System.Process as P
  21. import System.Process.Text as PT
  22. import Control.Monad (forM)
  23. import Data.Maybe
  24. import Text.Read
  25. import Agda.Utils.List
  26. type GHCArgs = [String]
  27. data ExecResult
  28. = CompileFailed
  29. { result :: ProgramResult }
  30. | CompileSucceeded
  31. { result :: ProgramResult }
  32. | ExecutedProg
  33. { result :: ProgramResult }
  34. deriving (Show, Read, Eq)
  35. data CodeOptimization = NonOptimized | Optimized | MinifiedOptimized
  36. deriving (Show, Read, Eq)
  37. data Strict = Strict | StrictData | Lazy
  38. deriving (Show, Read, Eq)
  39. data Compiler = MAlonzo Strict | JS CodeOptimization
  40. deriving (Show, Read, Eq)
  41. data CompilerOptions
  42. = CompilerOptions
  43. { extraAgdaArgs :: AgdaArgs
  44. } deriving (Show, Read)
  45. data TestOptions
  46. = TestOptions
  47. { forCompilers :: [(Compiler, CompilerOptions)]
  48. , runtimeOptions :: [String]
  49. , executeProg :: Bool
  50. } deriving (Show, Read)
  51. allCompilers :: [Compiler]
  52. allCompilers =
  53. map MAlonzo [Lazy, StrictData, Strict] ++
  54. map JS [NonOptimized, Optimized, MinifiedOptimized]
  55. defaultOptions :: TestOptions
  56. defaultOptions = TestOptions
  57. { forCompilers = [ (c, co) | c <- allCompilers ]
  58. , runtimeOptions = []
  59. , executeProg = True
  60. }
  61. where co = CompilerOptions []
  62. disabledTests :: [RegexFilter]
  63. disabledTests =
  64. [ -----------------------------------------------------------------------------
  65. -- These test are disabled on all backends.
  66. -- See issue 1528
  67. disable "Compiler/.*/simple/Sharing"
  68. -- Fix to 2524 is too unsafe
  69. , disable "Compiler/.*/simple/Issue2524"
  70. -- Issue #2640 (forcing translation for runtime erasure) is still open
  71. , disable "Compiler/.*/simple/Erasure-Issue2640"
  72. -----------------------------------------------------------------------------
  73. -- The test case for #2918 stopped working when inlining of
  74. -- recursive pattern-matching lambdas was disabled.
  75. , disable "Compiler/MAlonzo_.*/simple/Issue2918$"
  76. -----------------------------------------------------------------------------
  77. -- The following test cases fail (at least at the time of writing)
  78. -- for the JS backend.
  79. , disable "Compiler/JS_Optimized/simple/ModuleReexport"
  80. , disable "Compiler/JS_MinifiedOptimized/simple/ModuleReexport"
  81. -----------------------------------------------------------------------------
  82. -- The following test cases use primitives that are not implemented in the
  83. -- JS backend.
  84. , disable "Compiler/JS_.*/simple/Issue4999" -- primNatToChar
  85. -----------------------------------------------------------------------------
  86. -- The following test cases are GHC backend specific and thus disabled on JS.
  87. , disable "Compiler/JS_.*/simple/Issue2821"
  88. , disable "Compiler/JS_.*/simple/Issue2879-.*"
  89. , disable "Compiler/JS_.*/simple/Issue2909-.*"
  90. , disable "Compiler/JS_.*/simple/Issue2914"
  91. , disable "Compiler/JS_.*/simple/Issue2918$"
  92. , disable "Compiler/JS_.*/simple/Issue3732"
  93. , disable "Compiler/JS_.*/simple/VecReverseIrr"
  94. , disable "Compiler/JS_.*/simple/VecReverseErased" -- RangeError: Maximum call stack size exceeded
  95. -----------------------------------------------------------------------------
  96. ]
  97. where disable = RFInclude
  98. -- | Filtering out compiler tests using the Agda standard library.
  99. stdlibTestFilter :: [RegexFilter]
  100. stdlibTestFilter =
  101. [ disable "Compiler/.*/with-stdlib"
  102. ]
  103. where disable = RFInclude
  104. tests :: IO TestTree
  105. tests = do
  106. nodeBin <- findExecutable "node"
  107. ghcVersion <- findGHCVersion
  108. let ghcVersionAtLeast9 = case ghcVersion of
  109. Just (n : _) | n >= 9 -> True
  110. _ -> False
  111. enabledCompilers =
  112. [ MAlonzo s
  113. | s <- [Lazy, StrictData] ++
  114. if ghcVersionAtLeast9 then [Strict] else []
  115. ] ++
  116. [ JS opt
  117. | isJust nodeBin
  118. , opt <- [NonOptimized, Optimized, MinifiedOptimized]
  119. ]
  120. _ <- case nodeBin of
  121. Nothing -> putStrLn "No JS node binary found, skipping JS tests."
  122. Just n -> putStrLn $ "Using JS node binary at " ++ n
  123. ts <- mapM forComp enabledCompilers
  124. return $ testGroup "Compiler" ts
  125. where
  126. forComp comp = testGroup (map spaceToUnderscore $ show comp) . catMaybes
  127. <$> sequence
  128. [ Just <$> simpleTests comp
  129. , Just <$> stdlibTests comp
  130. , specialTests comp]
  131. spaceToUnderscore ' ' = '_'
  132. spaceToUnderscore c = c
  133. simpleTests :: Compiler -> IO TestTree
  134. simpleTests comp = do
  135. let testDir = "test" </> "Compiler" </> "simple"
  136. inps <- getAgdaFilesInDir NonRec testDir
  137. tests' <- forM inps $ \inp -> do
  138. opts <- readOptions inp
  139. return $
  140. agdaRunProgGoldenTest testDir comp
  141. (return $ ["-i" ++ testDir, "-itest/"] ++ compArgs comp) inp opts
  142. return $ testGroup "simple" $ catMaybes tests'
  143. where compArgs :: Compiler -> AgdaArgs
  144. compArgs MAlonzo{} =
  145. ghcArgsAsAgdaArgs ["-itest/", "-fno-excess-precision"]
  146. compArgs JS{} = []
  147. -- The Compiler tests using the standard library are horribly
  148. -- slow at the moment (1min or more per test case).
  149. stdlibTests :: Compiler -> IO TestTree
  150. stdlibTests comp = do
  151. let testDir = "test" </> "Compiler" </> "with-stdlib"
  152. let inps = [testDir </> "AllStdLib.agda"]
  153. -- put all tests in AllStdLib to avoid compiling the standard library
  154. -- multiple times
  155. let extraArgs :: [String]
  156. extraArgs = [ "-i" ++ testDir, "-i" ++ "std-lib" </> "src", "-istd-lib" ]
  157. let -- Note that -M4G can trigger the following error on 32-bit
  158. -- systems: "error in RTS option -M4G: size outside allowed
  159. -- range (4096 - 4294967295)".
  160. maxHeapSize =
  161. if finiteBitSize (undefined :: Int) == 32 then
  162. "-M2G"
  163. else
  164. "-M4G"
  165. rtsOptions :: [String]
  166. -- See Issue #3792.
  167. rtsOptions = [ "+RTS", "-H2G", maxHeapSize, "-RTS" ]
  168. tests' <- forM inps $ \inp -> do
  169. opts <- readOptions inp
  170. return $
  171. agdaRunProgGoldenTest testDir comp (return $ extraArgs ++ rtsOptions) inp opts
  172. return $ testGroup "with-stdlib" $ catMaybes tests'
  173. specialTests :: Compiler -> IO (Maybe TestTree)
  174. specialTests c@MAlonzo{} = do
  175. let t = fromJust $
  176. agdaRunProgGoldenTest1 testDir c (return extraArgs)
  177. (testDir </> "ExportTestAgda.agda") defaultOptions cont
  178. return $ Just $ testGroup "special" [t]
  179. where extraArgs = ["-i" ++ testDir, "-itest/", "--no-main", "--ghc-dont-call-ghc"]
  180. testDir = "test" </> "Compiler" </> "special"
  181. cont compDir out err = do
  182. (ret, sout, _) <- PT.readProcessWithExitCode "runghc"
  183. [ "-itest/"
  184. ,"-i" ++ compDir
  185. , testDir </> "ExportTest.hs"
  186. ]
  187. T.empty
  188. -- ignore stderr, as there may be some GHC warnings in it
  189. return $ ExecutedProg (ProgramResult ret (out <> sout) err)
  190. specialTests JS{} = return Nothing
  191. ghcArgsAsAgdaArgs :: GHCArgs -> AgdaArgs
  192. ghcArgsAsAgdaArgs = map f
  193. where f = ("--ghc-flag=" ++)
  194. agdaRunProgGoldenTest :: FilePath -- ^ directory where to run the tests.
  195. -> Compiler
  196. -> IO AgdaArgs -- ^ extra Agda arguments
  197. -> FilePath -- ^ relative path to agda input file.
  198. -> TestOptions
  199. -> Maybe TestTree
  200. agdaRunProgGoldenTest dir comp extraArgs inp opts =
  201. agdaRunProgGoldenTest1 dir comp extraArgs inp opts $ \compDir out err -> do
  202. if executeProg opts then do
  203. -- read input file, if it exists
  204. inp' <- maybe T.empty decodeUtf8 <$> readFileMaybe inpFile
  205. -- now run the new program
  206. let exec = getExecForComp comp compDir inpFile
  207. case comp of
  208. JS{} -> do
  209. setEnv "NODE_PATH" compDir
  210. (ret, out', err') <- PT.readProcessWithExitCode "node" [exec] inp'
  211. return $ ExecutedProg $ ProgramResult ret (out <> out') (err <> err')
  212. _ -> do
  213. (ret, out', err') <- PT.readProcessWithExitCode exec (runtimeOptions opts) inp'
  214. return $ ExecutedProg $ ProgramResult ret (out <> out') (err <> err')
  215. else
  216. return $ CompileSucceeded (ProgramResult ExitSuccess out err)
  217. where inpFile = dropAgdaExtension inp <.> ".inp"
  218. agdaRunProgGoldenTest1 :: FilePath -- ^ directory where to run the tests.
  219. -> Compiler
  220. -> IO AgdaArgs -- ^ extra Agda arguments
  221. -> FilePath -- ^ relative path to agda input file.
  222. -> TestOptions
  223. -> (FilePath -> T.Text -> T.Text -> IO ExecResult) -- continuation if compile succeeds, gets the compilation dir
  224. -> Maybe TestTree
  225. agdaRunProgGoldenTest1 dir comp extraArgs inp opts cont
  226. | (Just cOpts) <- lookup comp (forCompilers opts) =
  227. Just $ goldenVsAction' testName goldenFile (doRun cOpts) printExecResult
  228. | otherwise = Nothing
  229. where goldenFile = dropAgdaExtension inp <.> ".out"
  230. testName = asTestName dir inp
  231. -- Andreas, 2017-04-14, issue #2317
  232. -- Create temporary files in system temp directory.
  233. -- This has the advantage that upon Ctrl-C no junk is left behind
  234. -- in the Agda directory.
  235. -- doRun cOpts = withTempDirectory dir testName (\compDir -> do
  236. doRun cOpts = withSystemTempDirectory testName (\compDir -> do
  237. -- get extra arguments
  238. extraArgs' <- extraArgs
  239. -- compile file
  240. let cArgs = cleanUpOptions (extraAgdaArgs cOpts)
  241. defArgs = ["--ignore-interfaces" | notElem "--no-ignore-interfaces" (extraAgdaArgs cOpts)] ++
  242. ["--no-libraries"] ++
  243. ["--compile-dir", compDir, "-v0", "-vwarning:1"] ++ extraArgs' ++ cArgs ++ [inp]
  244. let args = argsForComp comp ++ defArgs
  245. res@(ret, out, err) <- readAgdaProcessWithExitCode args T.empty
  246. absDir <- canonicalizePath dir
  247. removePaths [absDir, compDir] <$> case ret of
  248. ExitSuccess -> cont compDir out err
  249. ExitFailure _ -> return $ CompileFailed $ toProgramResult res
  250. )
  251. argsForComp :: Compiler -> [String]
  252. argsForComp (MAlonzo s) = [ "--compile" ] ++ case s of
  253. Lazy -> []
  254. StrictData -> ["--ghc-strict-data"]
  255. Strict -> ["--ghc-strict"]
  256. argsForComp (JS o) = [ "--js", "--js-verify" ] ++ case o of
  257. NonOptimized -> []
  258. Optimized -> [ "--js-optimize" ]
  259. MinifiedOptimized -> [ "--js-optimize", "--js-minify" ]
  260. removePaths ps = \case
  261. CompileFailed r -> CompileFailed (removePaths' r)
  262. CompileSucceeded r -> CompileSucceeded (removePaths' r)
  263. ExecutedProg r -> ExecutedProg (removePaths' r)
  264. where
  265. removePaths' (ProgramResult c out err) = ProgramResult c (rm out) (rm err)
  266. rm = foldr (.) id $
  267. map (\p -> T.concat . T.splitOn (T.pack p)) ps
  268. readOptions :: FilePath -- file name of the agda file
  269. -> IO TestOptions
  270. readOptions inpFile =
  271. maybe defaultOptions (read . T.unpack . decodeUtf8) <$> readFileMaybe optFile
  272. where optFile = dropAgdaOrOtherExtension inpFile <.> ".options"
  273. cleanUpOptions :: AgdaArgs -> AgdaArgs
  274. cleanUpOptions = filter clean
  275. where
  276. clean :: String -> Bool
  277. clean "--no-ignore-interfaces" = False
  278. clean o | isPrefixOf "--ghc-flag=-j" o = True
  279. clean _ = True
  280. -- gets the generated executable path
  281. getExecForComp :: Compiler -> FilePath -> FilePath -> FilePath
  282. getExecForComp JS{} compDir inpFile = compDir </> ("jAgda." ++ (takeFileName $ dropAgdaOrOtherExtension inpFile) ++ ".js")
  283. getExecForComp _ compDir inpFile = compDir </> (takeFileName $ dropAgdaOrOtherExtension inpFile)
  284. printExecResult :: ExecResult -> T.Text
  285. printExecResult (CompileFailed r) = "COMPILE_FAILED\n\n" <> printProgramResult r
  286. printExecResult (CompileSucceeded r) = "COMPILE_SUCCEEDED\n\n" <> printProgramResult r
  287. printExecResult (ExecutedProg r) = "EXECUTED_PROGRAM\n\n" <> printProgramResult r
  288. -- | Tries to figure out the version of the program @ghc@ (if such a
  289. -- program can be found).
  290. findGHCVersion :: IO (Maybe [Integer])
  291. findGHCVersion = do
  292. (code, version, _) <-
  293. P.readProcessWithExitCode "ghc" ["--numeric-version"] ""
  294. case code of
  295. ExitFailure{} -> return Nothing
  296. ExitSuccess -> return $
  297. sequence $
  298. concat $
  299. map (map readMaybe . wordsBy (== '.')) $
  300. take 1 $
  301. lines version