@@ -27,14 +27,15 @@ import qualified Language.PureScript as P
28import Data.Char (isSpace)
28import Data.Char (isSpace)
29import Data.Function (on)
29import Data.Function (on)
30−import Data.List (sort, stripPrefix, intercalate, groupBy, sortBy, partition)
30+import Data.List (sort, stripPrefix, intercalate, groupBy, sortBy, minimumBy)
31import Data.Maybe (mapMaybe)
31import Data.Maybe (mapMaybe)
32import Data.Time.Clock (UTCTime())
32import Data.Time.Clock (UTCTime())
33+import Data.Tuple (swap)
34import qualified Data.Map as M
35import qualified Data.Map as M
36import Control.Monad
37import Control.Monad
37−import Control.Arrow ((>>>))
38+import Control.Arrow ((***), (>>>))
39import Control.Monad.Reader
40import Control.Monad.Reader
40import Control.Monad.Writer.Strict
41import Control.Monad.Writer.Strict
@@ -57,59 +58,58 @@ main = hspec spec
57spec :: Spec
58spec :: Spec
60− (supportExterns, supportForeigns, passingTestCases, failingTestCases) <- runIO $ do
61+ (supportExterns, passingTestCases, warningTestCases, failingTestCases) <- runIO $ do
61 cwd <- getCurrentDirectory
62 cwd <- getCurrentDirectory
62 let passing = cwd </> "examples" </> "passing"
63 let passing = cwd </> "examples" </> "passing"
64+ let warning = cwd </> "examples" </> "warning"
63 let failing = cwd </> "examples" </> "failing"
65 let failing = cwd </> "examples" </> "failing"
64 let supportDir = cwd </> "tests" </> "support" </> "bower_components"
66 let supportDir = cwd </> "tests" </> "support" </> "bower_components"
65 let supportFiles ext = Glob.globDir1 (Glob.compile ("purescript-*/**/*." ++ ext)) supportDir
67 let supportFiles ext = Glob.globDir1 (Glob.compile ("purescript-*/**/*." ++ ext)) supportDir
66 passingFiles <- getTestFiles passing <$> testGlob passing
68 passingFiles <- getTestFiles passing <$> testGlob passing
69+ warningFiles <- getTestFiles warning <$> testGlob warning
67 failingFiles <- getTestFiles failing <$> testGlob failing
70 failingFiles <- getTestFiles failing <$> testGlob failing
68 supportPurs <- supportFiles "purs"
71 supportPurs <- supportFiles "purs"
69− supportForeigns <- loadForeigns =<< supportFiles "js"
70 supportPursFiles <- readInput supportPurs
72 supportPursFiles <- readInput supportPurs
71 supportExterns <- runExceptT $ do
73 supportExterns <- runExceptT $ do
72 modules <- ExceptT . return $ P.parseModulesFromFiles id supportPursFiles
74 modules <- ExceptT . return $ P.parseModulesFromFiles id supportPursFiles
75+ foreigns <- inferForeignModules modules
73− externs <- ExceptT . runTest $ P.make (makeActions supportForeigns) (map snd modules)
76+ externs <- ExceptT . fmap fst . runTest $ P.make (makeActions foreigns) (map snd modules)
74 return (zip (map snd modules) externs)
77 return (zip (map snd modules) externs)
75 case supportExterns of
78 case supportExterns of
76 Left errs -> fail (P.prettyPrintMultipleErrors False errs)
79 Left errs -> fail (P.prettyPrintMultipleErrors False errs)
77− Right externs -> return (externs, supportForeigns, passingFiles, failingFiles)
80+ Right externs -> return (externs, passingFiles, warningFiles, failingFiles)
79− context ("Passing examples") $ do
82+ context "Passing examples" $
80− forM_ passingTestCases $ \(testPurs, testJS) ->
83+ forM_ passingTestCases $ \testPurs ->
81− it ("'" <> takeFileName (getTestMain testPurs) <> "' should compile and run without error") $ do
84+ it ("'" <> takeFileName (getTestMain testPurs) <> "' should compile and run without error") $
82− testForeigns <- loadForeigns testJS
83− assertCompiles supportExterns testPurs (supportForeigns <> testForeigns)
85+ assertCompiles supportExterns testPurs
87+ context "Warning examples" $
88+ forM_ warningTestCases $ \testPurs -> do
89+ let mainPath = getTestMain testPurs
90+ expectedWarnings <- runIO $ getShouldWarnWith mainPath
91+ it ("'" <> takeFileName mainPath <> "' should compile with warning(s) '" <> intercalate "', '" expectedWarnings <> "'") $
92+ assertCompilesWithWarnings supportExterns testPurs expectedWarnings
85− context ("Failing examples") $ do
94+ context "Failing examples" $
86− forM_ failingTestCases $ \(testPurs, testJS) -> do
95+ forM_ failingTestCases $ \testPurs -> do
87 let mainPath = getTestMain testPurs
96 let mainPath = getTestMain testPurs
88 expectedFailures <- runIO $ getShouldFailWith mainPath
97 expectedFailures <- runIO $ getShouldFailWith mainPath
89− it ("'" <> takeFileName mainPath <> "' should fail with '" <> intercalate "', '" expectedFailures <> "'") $ do
98+ it ("'" <> takeFileName mainPath <> "' should fail with '" <> intercalate "', '" expectedFailures <> "'") $
90− testForeigns <- loadForeigns testJS
91− assertDoesNotCompile supportExterns testPurs (supportForeigns <> testForeigns) expectedFailures
99+ assertDoesNotCompile supportExterns testPurs expectedFailures
95 -- A glob for all purs and js files within a test directory
103 -- A glob for all purs and js files within a test directory
96 testGlob :: FilePath -> IO [FilePath]
104 testGlob :: FilePath -> IO [FilePath]
97− testGlob dir = join . fst <$> Glob.globDir (map Glob.compile ["**/*.purs", "**/*.js"]) dir
105+ testGlob = Glob.globDir1 (Glob.compile "**/*.purs")
99− -- Loads foreign modules from source files
100− loadForeigns :: [FilePath] -> IO (M.Map P.ModuleName FilePath)
101− loadForeigns paths = do
102− files <- forM paths (\f -> (f,) <$> readUTF8File f)
103− Right (foreigns, _) <- runExceptT $ runWriterT $ P.parseForeignModulesFromFiles files
106 -- Groups the test files so that a top-level file can have dependencies in a
107 -- Groups the test files so that a top-level file can have dependencies in a
107 -- subdirectory of the same name. The inner tuple contains a list of the
108 -- subdirectory of the same name. The inner tuple contains a list of the
108 -- .purs files and the .js files for the test case.
109 -- .purs files and the .js files for the test case.
109− getTestFiles :: FilePath -> [FilePath] -> [([FilePath], [FilePath])]
110+ getTestFiles :: FilePath -> [FilePath] -> [[FilePath]]
110 getTestFiles baseDir
111 getTestFiles baseDir
111− = map (partition ((== ".purs") . takeExtensions))
112+ = map (filter ((== ".purs") . takeExtensions) . map (baseDir </>))
112− . map (map (baseDir </>))
113 . groupBy ((==) `on` extractPrefix)
113 . groupBy ((==) `on` extractPrefix)
114 . sortBy (compare `on` extractPrefix)
114 . sortBy (compare `on` extractPrefix)
115 . map (makeRelative baseDir)
115 . map (makeRelative baseDir)
@@ -118,7 +118,7 @@ spec = do
118 -- by the file with the shortest path name, as everything but the main file
118 -- by the file with the shortest path name, as everything but the main file
119 -- will be under a subdirectory.
119 -- will be under a subdirectory.
120 getTestMain :: [FilePath] -> FilePath
120 getTestMain :: [FilePath] -> FilePath
121− getTestMain = head . sortBy (compare `on` length)
121+ getTestMain = minimumBy (compare `on` length)
123 -- Extracts the filename part of a .purs file, or if the file is in a
123 -- Extracts the filename part of a .purs file, or if the file is in a
124 -- subdirectory, the first part of that directory path.
124 -- subdirectory, the first part of that directory path.
@@ -133,9 +133,26 @@ spec = do
133 -- Scans a file for @shouldFailWith directives in the comments, used to
133 -- Scans a file for @shouldFailWith directives in the comments, used to
134 -- determine expected failures
134 -- determine expected failures
135 getShouldFailWith :: FilePath -> IO [String]
135 getShouldFailWith :: FilePath -> IO [String]
136− getShouldFailWith = fmap extractFailWiths . readUTF8File
136+ getShouldFailWith = extractPragma "shouldFailWith"
138+ -- Scans a file for @shouldWarnWith directives in the comments, used to
139+ -- determine expected warnings
140+ getShouldWarnWith :: FilePath -> IO [String]
141+ getShouldWarnWith = extractPragma "shouldWarnWith"
143+ extractPragma :: String -> FilePath -> IO [String]
144+ extractPragma pragma = fmap go . readUTF8File
138− extractFailWiths = lines >>> mapMaybe (stripPrefix "-- @shouldFailWith ") >>> map trim
146+ go = lines >>> mapMaybe (stripPrefix ("-- @" ++ pragma ++ " ")) >>> map trim
150+ => [(FilePath, P.Module)]
151+ -> m (M.Map P.ModuleName FilePath)
152+inferForeignModules = P.inferForeignModules . fromList
154+ fromList :: [(FilePath, P.Module)] -> M.Map P.ModuleName (Either P.RebuildPolicy FilePath)
155+ fromList = M.fromList . map ((P.getModuleName *** Right) . swap)
140trim :: String -> String
157trim :: String -> String
141trim = dropWhile isSpace >>> reverse >>> dropWhile isSpace >>> reverse
158trim = dropWhile isSpace >>> reverse >>> dropWhile isSpace >>> reverse
@@ -167,20 +184,18 @@ readInput inputFiles = forM inputFiles $ \inputFile -> do
167 text <- readUTF8File inputFile
184 text <- readUTF8File inputFile
168 return (inputFile, text)
185 return (inputFile, text)
170−type TestM = WriterT [(FilePath, String)] IO
172−runTest :: P.Make a -> IO (Either P.MultipleErrors a)
187+runTest :: P.Make a -> IO (Either P.MultipleErrors a, P.MultipleErrors)
173−runTest = fmap fst . P.runMake P.defaultOptions
188+runTest = P.runMake P.defaultOptions
176 :: [(P.Module, P.ExternsFile)]
191 :: [(P.Module, P.ExternsFile)]
177 -> [FilePath]
192 -> [FilePath]
178− -> M.Map P.ModuleName FilePath
179 -> ([P.Module] -> IO ())
193 -> ([P.Module] -> IO ())
180− -> IO (Either P.MultipleErrors [P.ExternsFile])
194+ -> IO (Either P.MultipleErrors [P.ExternsFile], P.MultipleErrors)
181−compile supportExterns inputFiles foreigns check = silence $ runTest $ do
195+compile supportExterns inputFiles check = silence $ runTest $ do
182 fs <- liftIO $ readInput inputFiles
196 fs <- liftIO $ readInput inputFiles
183 ms <- P.parseModulesFromFiles id fs
197 ms <- P.parseModulesFromFiles id fs
198+ foreigns <- inferForeignModules ms
184 liftIO (check (map snd ms))
199 liftIO (check (map snd ms))
185 let actions = makeActions foreigns
200 let actions = makeActions foreigns
186 case ms of
201 case ms of
@@ -190,22 +205,32 @@ compile supportExterns inputFiles foreigns check = silence $ runTest $ do
191 :: [(P.Module, P.ExternsFile)]
206 :: [(P.Module, P.ExternsFile)]
192 -> [FilePath]
207 -> [FilePath]
193− -> M.Map P.ModuleName FilePath
194 -> ([P.Module] -> IO ())
208 -> ([P.Module] -> IO ())
195− -> (Either P.MultipleErrors [P.ExternsFile] -> IO (Maybe String))
209+ -> (Either P.MultipleErrors P.MultipleErrors -> IO (Maybe String))
196 -> Expectation
210 -> Expectation
197−assert supportExterns inputFiles foreigns check f = do
211+assert supportExterns inputFiles check f = do
198− e <- compile supportExterns inputFiles foreigns check
212+ (e, w) <- compile supportExterns inputFiles check
199− maybeErr <- f e
213+ maybeErr <- f (const w <$> e)
200 maybe (return ()) expectationFailure maybeErr
214 maybe (return ()) expectationFailure maybeErr
216+checkMain :: [P.Module] -> IO ()
218+ unless (any ((== P.moduleNameFromString "Main") . P.getModuleName) ms)
219+ (fail "Main module missing")
221+checkShouldFailWith :: [String] -> P.MultipleErrors -> Maybe String
222+checkShouldFailWith expected errs =
223+ let actual = map P.errorCode $ P.runMultipleErrors errs
224+ in if sort expected == sort actual
226+ else Just $ "Expected these errors: " ++ show expected ++ ", but got these: " ++ show actual
202assertCompiles
228assertCompiles
203 :: [(P.Module, P.ExternsFile)]
229 :: [(P.Module, P.ExternsFile)]
204 -> [FilePath]
230 -> [FilePath]
205− -> M.Map P.ModuleName FilePath
206 -> Expectation
231 -> Expectation
207−assertCompiles supportExterns inputFiles foreigns =
232+assertCompiles supportExterns inputFiles =
208− assert supportExterns inputFiles foreigns checkMain $ \e ->
233+ assert supportExterns inputFiles checkMain $ \e ->
209 case e of
234 case e of
210 Left errs -> return . Just . P.prettyPrintMultipleErrors False $ errs
235 Left errs -> return . Just . P.prettyPrintMultipleErrors False $ errs
211 Right _ -> do
236 Right _ -> do
@@ -220,19 +245,33 @@ assertCompiles supportExterns inputFiles foreigns =
220 | otherwise -> return $ Just $ "Test did not finish with 'Done':\n\n" <> out
245 | otherwise -> return $ Just $ "Test did not finish with 'Done':\n\n" <> out
221 Just (ExitFailure _, _, err) -> return $ Just err
246 Just (ExitFailure _, _, err) -> return $ Just err
222 Nothing -> return $ Just "Couldn't find node.js executable"
247 Nothing -> return $ Just "Couldn't find node.js executable"
249+assertCompilesWithWarnings
250+ :: [(P.Module, P.ExternsFile)]
254+assertCompilesWithWarnings supportExterns inputFiles shouldWarnWith =
255+ assert supportExterns inputFiles checkMain $ \e ->
258+ return . Just . P.prettyPrintMultipleErrors False $ errs
261+ . fmap (printAllWarnings warnings)
262+ $ checkShouldFailWith shouldWarnWith warnings
224− checkMain ms =
265+ printAllWarnings warnings =
225− unless (any ((== P.moduleNameFromString "Main") . P.getModuleName) ms)
266+ (<> "\n\n" <> P.prettyPrintMultipleErrors False warnings)
226− (fail "Main module missing")
228assertDoesNotCompile
268assertDoesNotCompile
229 :: [(P.Module, P.ExternsFile)]
269 :: [(P.Module, P.ExternsFile)]
230 -> [FilePath]
270 -> [FilePath]
231− -> M.Map P.ModuleName FilePath
232 -> [String]
271 -> [String]
233 -> Expectation
272 -> Expectation
234−assertDoesNotCompile supportExterns inputFiles foreigns shouldFailWith =
273+assertDoesNotCompile supportExterns inputFiles shouldFailWith =
235− assert supportExterns inputFiles foreigns noPreCheck $ \e ->
274+ assert supportExterns inputFiles noPreCheck $ \e ->
236 case e of
275 case e of
237 Left errs ->
276 Left errs ->
238 return $ if null shouldFailWith
277 return $ if null shouldFailWith
@@ -245,9 +284,3 @@ assertDoesNotCompile supportExterns inputFiles foreigns shouldFailWith =
247 noPreCheck = const (return ())
286 noPreCheck = const (return ())
249− checkShouldFailWith expected errs =
250− let actual = map P.errorCode $ P.runMultipleErrors errs
251− in if sort expected == sort actual
253− else Just $ "Expected these errors: " ++ show expected ++ ", but got these: " ++ show actual