@@ -27,14 +27,15 @@ import qualified Language.PureScript as P
2727
2828import Data.Char (isSpace)
2929import Data.Function (on)
30−import Data.List (sort, stripPrefix, intercalate, groupBy, sortBy, partition)
30+import Data.List (sort, stripPrefix, intercalate, groupBy, sortBy, minimumBy)
3131import Data.Maybe (mapMaybe)
3232import Data.Time.Clock (UTCTime())
33+import Data.Tuple (swap)
3334
3435import qualified Data.Map as M
3536
3637import Control.Monad
37−import Control.Arrow ((>>>))
38+import Control.Arrow ((***), (>>>))
3839
3940import Control.Monad.Reader
4041import Control.Monad.Writer.Strict
@@ -57,59 +58,58 @@ main = hspec spec
5758spec :: Spec
5859spec = do
5960
60− (supportExterns, supportForeigns, passingTestCases, failingTestCases) <- runIO $ do
61+ (supportExterns, passingTestCases, warningTestCases, failingTestCases) <- runIO $ do
6162 cwd <- getCurrentDirectory
6263 let passing = cwd </> "examples" </> "passing"
64+ let warning = cwd </> "examples" </> "warning"
6365 let failing = cwd </> "examples" </> "failing"
6466 let supportDir = cwd </> "tests" </> "support" </> "bower_components"
6567 let supportFiles ext = Glob.globDir1 (Glob.compile ("purescript-*/**/*." ++ ext)) supportDir
6668 passingFiles <- getTestFiles passing <$> testGlob passing
69+ warningFiles <- getTestFiles warning <$> testGlob warning
6770 failingFiles <- getTestFiles failing <$> testGlob failing
6871 supportPurs <- supportFiles "purs"
69− supportForeigns <- loadForeigns =<< supportFiles "js"
7072 supportPursFiles <- readInput supportPurs
7173 supportExterns <- runExceptT $ do
7274 modules <- ExceptT . return $ P.parseModulesFromFiles id supportPursFiles
73− externs <- ExceptT . runTest $ P.make (makeActions supportForeigns) (map snd modules)
75+ foreigns <- inferForeignModules modules
76+ externs <- ExceptT . fmap fst . runTest $ P.make (makeActions foreigns) (map snd modules)
7477 return (zip (map snd modules) externs)
7578 case supportExterns of
7679 Left errs -> fail (P.prettyPrintMultipleErrors False errs)
77− Right externs -> return (externs, supportForeigns, passingFiles, failingFiles)
80+ Right externs -> return (externs, passingFiles, warningFiles, failingFiles)
7881
79− context ("Passing examples") $ do
80− forM_ passingTestCases $ \(testPurs, testJS) ->
81− it ("'" <> takeFileName (getTestMain testPurs) <> "' should compile and run without error") $ do
82− testForeigns <- loadForeigns testJS
83− assertCompiles supportExterns testPurs (supportForeigns <> testForeigns)
82+ context "Passing examples" $
83+ forM_ passingTestCases $ \testPurs ->
84+ it ("'" <> takeFileName (getTestMain testPurs) <> "' should compile and run without error") $
85+ assertCompiles supportExterns testPurs
8486
85− context ("Failing examples") $ do
86− forM_ failingTestCases $ \(testPurs, testJS) -> do
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
93+
94+ context "Failing examples" $
95+ forM_ failingTestCases $ \testPurs -> do
8796 let mainPath = getTestMain testPurs
8897 expectedFailures <- runIO $ getShouldFailWith mainPath
89− it ("'" <> takeFileName mainPath <> "' should fail with '" <> intercalate "', '" expectedFailures <> "'") $ do
90− testForeigns <- loadForeigns testJS
91− assertDoesNotCompile supportExterns testPurs (supportForeigns <> testForeigns) expectedFailures
98+ it ("'" <> takeFileName mainPath <> "' should fail with '" <> intercalate "', '" expectedFailures <> "'") $
99+ assertDoesNotCompile supportExterns testPurs expectedFailures
92100
93101 where
94102
95103 -- A glob for all purs and js files within a test directory
96104 testGlob :: FilePath -> IO [FilePath]
97− testGlob dir = join . fst <$> Glob.globDir (map Glob.compile ["**/*.purs", "**/*.js"]) dir
98−
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
104− return foreigns
105+ testGlob = Glob.globDir1 (Glob.compile "**/*.purs")
105106
106107 -- Groups the test files so that a top-level file can have dependencies in a
107108 -- subdirectory of the same name. The inner tuple contains a list of the
108109 -- .purs files and the .js files for the test case.
109− getTestFiles :: FilePath -> [FilePath] -> [([FilePath], [FilePath])]
110+ getTestFiles :: FilePath -> [FilePath] -> [[FilePath]]
110111 getTestFiles baseDir
111− = map (partition ((== ".purs") . takeExtensions))
112− . map (map (baseDir </>))
112+ = map (filter ((== ".purs") . takeExtensions) . map (baseDir </>))
113113 . groupBy ((==) `on` extractPrefix)
114114 . sortBy (compare `on` extractPrefix)
115115 . map (makeRelative baseDir)
@@ -118,7 +118,7 @@ spec = do
118118 -- by the file with the shortest path name, as everything but the main file
119119 -- will be under a subdirectory.
120120 getTestMain :: [FilePath] -> FilePath
121− getTestMain = head . sortBy (compare `on` length)
121+ getTestMain = minimumBy (compare `on` length)
122122
123123 -- Extracts the filename part of a .purs file, or if the file is in a
124124 -- subdirectory, the first part of that directory path.
@@ -133,9 +133,26 @@ spec = do
133133 -- Scans a file for @shouldFailWith directives in the comments, used to
134134 -- determine expected failures
135135 getShouldFailWith :: FilePath -> IO [String]
136− getShouldFailWith = fmap extractFailWiths . readUTF8File
136+ getShouldFailWith = extractPragma "shouldFailWith"
137+
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"
142+
143+ extractPragma :: String -> FilePath -> IO [String]
144+ extractPragma pragma = fmap go . readUTF8File
137145 where
138− extractFailWiths = lines >>> mapMaybe (stripPrefix "-- @shouldFailWith ") >>> map trim
146+ go = lines >>> mapMaybe (stripPrefix ("-- @" ++ pragma ++ " ")) >>> map trim
147+
148+inferForeignModules
149+ :: MonadIO m
150+ => [(FilePath, P.Module)]
151+ -> m (M.Map P.ModuleName FilePath)
152+inferForeignModules = P.inferForeignModules . fromList
153+ where
154+ fromList :: [(FilePath, P.Module)] -> M.Map P.ModuleName (Either P.RebuildPolicy FilePath)
155+ fromList = M.fromList . map ((P.getModuleName *** Right) . swap)
139156
140157trim :: String -> String
141158trim = dropWhile isSpace >>> reverse >>> dropWhile isSpace >>> reverse
@@ -167,20 +184,18 @@ readInput inputFiles = forM inputFiles $ \inputFile -> do
167184 text <- readUTF8File inputFile
168185 return (inputFile, text)
169186
170−type TestM = WriterT [(FilePath, String)] IO
171−
172−runTest :: P.Make a -> IO (Either P.MultipleErrors a)
173−runTest = fmap fst . P.runMake P.defaultOptions
187+runTest :: P.Make a -> IO (Either P.MultipleErrors a, P.MultipleErrors)
188+runTest = P.runMake P.defaultOptions
174189
175190compile
176191 :: [(P.Module, P.ExternsFile)]
177192 -> [FilePath]
178− -> M.Map P.ModuleName FilePath
179193 -> ([P.Module] -> IO ())
180− -> IO (Either P.MultipleErrors [P.ExternsFile])
181−compile supportExterns inputFiles foreigns check = silence $ runTest $ do
194+ -> IO (Either P.MultipleErrors [P.ExternsFile], P.MultipleErrors)
195+compile supportExterns inputFiles check = silence $ runTest $ do
182196 fs <- liftIO $ readInput inputFiles
183197 ms <- P.parseModulesFromFiles id fs
198+ foreigns <- inferForeignModules ms
184199 liftIO (check (map snd ms))
185200 let actions = makeActions foreigns
186201 case ms of
@@ -190,22 +205,32 @@ compile supportExterns inputFiles foreigns check = silence $ runTest $ do
190205assert
191206 :: [(P.Module, P.ExternsFile)]
192207 -> [FilePath]
193− -> M.Map P.ModuleName FilePath
194208 -> ([P.Module] -> IO ())
195− -> (Either P.MultipleErrors [P.ExternsFile] -> IO (Maybe String))
209+ -> (Either P.MultipleErrors P.MultipleErrors -> IO (Maybe String))
196210 -> Expectation
197−assert supportExterns inputFiles foreigns check f = do
198− e <- compile supportExterns inputFiles foreigns check
199− maybeErr <- f e
211+assert supportExterns inputFiles check f = do
212+ (e, w) <- compile supportExterns inputFiles check
213+ maybeErr <- f (const w <$> e)
200214 maybe (return ()) expectationFailure maybeErr
201215
216+checkMain :: [P.Module] -> IO ()
217+checkMain ms =
218+ unless (any ((== P.moduleNameFromString "Main") . P.getModuleName) ms)
219+ (fail "Main module missing")
220+
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
225+ then Nothing
226+ else Just $ "Expected these errors: " ++ show expected ++ ", but got these: " ++ show actual
227+
202228assertCompiles
203229 :: [(P.Module, P.ExternsFile)]
204230 -> [FilePath]
205− -> M.Map P.ModuleName FilePath
206231 -> Expectation
207−assertCompiles supportExterns inputFiles foreigns =
208− assert supportExterns inputFiles foreigns checkMain $ \e ->
232+assertCompiles supportExterns inputFiles =
233+ assert supportExterns inputFiles checkMain $ \e ->
209234 case e of
210235 Left errs -> return . Just . P.prettyPrintMultipleErrors False $ errs
211236 Right _ -> do
@@ -220,19 +245,33 @@ assertCompiles supportExterns inputFiles foreigns =
220245 | otherwise -> return $ Just $ "Test did not finish with 'Done':\n\n" <> out
221246 Just (ExitFailure _, _, err) -> return $ Just err
222247 Nothing -> return $ Just "Couldn't find node.js executable"
248+
249+assertCompilesWithWarnings
250+ :: [(P.Module, P.ExternsFile)]
251+ -> [FilePath]
252+ -> [String]
253+ -> Expectation
254+assertCompilesWithWarnings supportExterns inputFiles shouldWarnWith =
255+ assert supportExterns inputFiles checkMain $ \e ->
256+ case e of
257+ Left errs ->
258+ return . Just . P.prettyPrintMultipleErrors False $ errs
259+ Right warnings ->
260+ return
261+ . fmap (printAllWarnings warnings)
262+ $ checkShouldFailWith shouldWarnWith warnings
263+
223264 where
224− checkMain ms =
225− unless (any ((== P.moduleNameFromString "Main") . P.getModuleName) ms)
226− (fail "Main module missing")
265+ printAllWarnings warnings =
266+ (<> "\n\n" <> P.prettyPrintMultipleErrors False warnings)
227267
228268assertDoesNotCompile
229269 :: [(P.Module, P.ExternsFile)]
230270 -> [FilePath]
231− -> M.Map P.ModuleName FilePath
232271 -> [String]
233272 -> Expectation
234−assertDoesNotCompile supportExterns inputFiles foreigns shouldFailWith =
235− assert supportExterns inputFiles foreigns noPreCheck $ \e ->
273+assertDoesNotCompile supportExterns inputFiles shouldFailWith =
274+ assert supportExterns inputFiles noPreCheck $ \e ->
236275 case e of
237276 Left errs ->
238277 return $ if null shouldFailWith
@@ -245,9 +284,3 @@ assertDoesNotCompile supportExterns inputFiles foreigns shouldFailWith =
245284
246285 where
247286 noPreCheck = const (return ())
248−
249− checkShouldFailWith expected errs =
250− let actual = map P.errorCode $ P.runMultipleErrors errs
251− in if sort expected == sort actual
252− then Nothing
253− else Just $ "Expected these errors: " ++ show expected ++ ", but got these: " ++ show actual