@@ -37,28 +37,36 @@ import Language.LSP.Test
3737import System.FilePath
3838import Test.Hls (TestConfig (.. ), def ,
3939 runSessionWithTestConfig ,
40- waitForBuildQueue )
40+ waitForBuildQueue , setHlsConfig )
4141import Test.Hls.FileSystem
4242import Test.Hls.Util (EnvSpec (.. ), OS (.. ),
4343 ignoreInEnv )
4444import Test.Tasty
4545import Test.Tasty.HUnit
46+ import Control.Monad (when )
4647
4748
4849tests :: TestTree
4950tests = testGroup " cradle"
50- [testGroup " dependencies" [ sessionDepsArePickedUp]
51- ,testGroup " ignore-fatal" [ ignoreFatalWarning]
52- ,testGroup " loading" [loadCradleOnlyonce, retryFailedCradle]
51+ [testGroup " dependencies" $ bothLoadings1 sessionDepsArePickedUp
52+ ,testGroup " ignore-fatal" $ bothLoadings1 ignoreFatalWarning
53+ ,testGroup " loading" $ bothLoadings $ \ initM -> [loadCradleOnlyonce initM , retryFailedCradle initM ]
5354 ,testGroup " regression.batch" batchLoadRegressionTests
5455 ,testGroup " multi" (multiTests " multi" )
5556 ,testGroup " multi-unit" (multiTests " multi-unit" )
56- ,testGroup " sub-directory" [ simpleSubDirectoryTest]
57- ,testGroup " multi-unit-rexport" [ multiRexportTest]
57+ ,testGroup " sub-directory" $ bothLoadings1 simpleSubDirectoryTest
58+ ,testGroup " multi-unit-rexport" $ bothLoadings1 multiRexportTest
5859 ]
5960
60- loadCradleOnlyonce :: TestTree
61- loadCradleOnlyonce = testGroup " load cradle only once"
61+ -- | Test both with the default `componentsLoading` and WholeProject.
62+ bothLoadings :: (Session () -> [TestTree ]) -> [TestTree ]
63+ bothLoadings m = [testGroup " default" (m (return () ))
64+ , testGroup " whole-project" (m setWholeProjectLoading)]
65+ bothLoadings1 :: (Session () -> TestTree ) -> [TestTree ]
66+ bothLoadings1 m = bothLoadings $ \ initM -> [m initM]
67+
68+ loadCradleOnlyonce :: Session () -> TestTree
69+ loadCradleOnlyonce initM = testGroup " load cradle only once"
6270 [ testWithDummyPluginEmpty' " implicit" implicit
6371 , testWithDummyPluginEmpty' " direct" direct
6472 ]
@@ -69,6 +77,7 @@ loadCradleOnlyonce = testGroup "load cradle only once"
6977 test dir
7078 implicit dir = test dir
7179 test _dir = do
80+ initM
7281 doc <- createDoc " B.hs" " haskell" " module B where\n import Data.Foo"
7382 msgs <- someTill (skipManyTill anyMessage cradleLoadedMessage) (skipManyTill anyMessage (message SMethod_TextDocumentPublishDiagnostics ))
7483 liftIO $ length msgs @?= 1
@@ -79,8 +88,9 @@ loadCradleOnlyonce = testGroup "load cradle only once"
7988 msgs <- manyTill (skipManyTill anyMessage cradleLoadedMessage) (skipManyTill anyMessage (message SMethod_TextDocumentPublishDiagnostics ))
8089 liftIO $ length msgs @?= 0
8190
82- retryFailedCradle :: TestTree
83- retryFailedCradle = testWithDummyPluginEmpty' " retry failed" $ \ dir -> do
91+ retryFailedCradle :: Session () -> TestTree
92+ retryFailedCradle initM = testWithDummyPluginEmpty' " retry failed" $ \ dir -> do
93+ initM
8494 -- The false cradle always fails
8595 let hieContents = " cradle: {bios: {shell: \" false\" }}"
8696 hiePath = dir </> " hie.yaml"
@@ -108,16 +118,18 @@ cradleLoadedMessage = satisfy $ \case
108118cradleLoadedMethod :: String
109119cradleLoadedMethod = " ghcide/cradle/loaded"
110120
111- ignoreFatalWarning :: TestTree
112- ignoreFatalWarning = testCase " ignore-fatal-warning" $ runWithExtraFiles " ignore-fatal" $ \ dir -> do
121+ ignoreFatalWarning :: Session () -> TestTree
122+ ignoreFatalWarning initM = testCase " ignore-fatal-warning" $ runWithExtraFiles " ignore-fatal" $ \ dir -> do
123+ initM
113124 let srcPath = dir </> " IgnoreFatal.hs"
114125 src <- liftIO $ readFileUtf8 srcPath
115126 _ <- createDoc srcPath " haskell" src
116127 expectNoMoreDiagnostics 5
117128
118- simpleSubDirectoryTest :: TestTree
119- simpleSubDirectoryTest =
129+ simpleSubDirectoryTest :: Session () -> TestTree
130+ simpleSubDirectoryTest initM =
120131 testCase " simple-subdirectory" $ runWithExtraFiles " cabal-exe" $ \ dir -> do
132+ initM
121133 let mainPath = dir </> " a/src/Main.hs"
122134 mainSource <- liftIO $ readFileUtf8 mainPath
123135 _mdoc <- createDoc mainPath " haskell" mainSource
@@ -127,18 +139,31 @@ simpleSubDirectoryTest =
127139 expectNoMoreDiagnostics 0.5
128140
129141multiTests :: FilePath -> [TestTree ]
130- multiTests dir =
131- [ simpleMultiTest dir
132- , simpleMultiTest2 dir
133- , simpleMultiTest3 dir
134- , simpleMultiDefTest dir
135- ]
142+ multiTests odir =
143+ [ testGroup " default" $ tests (return () )
144+ , testGroup " whole-project" $ tests setWholeProjectLoading
145+ ]
146+ where
147+ tests initM =
148+ [ ignoreForWindows testName $ testCase testName $ runWithExtraFiles odir $ \ dir -> initM >> test dir
149+ | (name,test) <-
150+ [ (" test" ,simpleMultiTest)
151+ , (" test2" ,simpleMultiTest2)
152+ , (" test3" ,simpleMultiTest3)
153+ , (" def-test" ,simpleMultiDefTest)
154+ ]
155+ ,
156+ let testName = multiTestName odir name
157+ ]
158+ ignoreForWindows testName
159+ | testName == " simple-multi-def-test" = ignoreInEnv [HostOS Windows ] " Test is flaky on Windows, see #4270"
160+ | otherwise = id
136161
137162multiTestName :: FilePath -> String -> String
138163multiTestName dir name = " simple-" ++ dir ++ " -" ++ name
139164
140- simpleMultiTest :: FilePath -> TestTree
141- simpleMultiTest variant = testCase (multiTestName variant " test " ) $ runWithExtraFiles variant $ \ dir -> do
165+ simpleMultiTest :: FilePath -> Session ()
166+ simpleMultiTest = \ dir -> do
142167 let aPath = dir </> " a/A.hs"
143168 bPath = dir </> " b/B.hs"
144169 adoc <- openDoc aPath " haskell"
@@ -153,8 +178,8 @@ simpleMultiTest variant = testCase (multiTestName variant "test") $ runWithExtra
153178 expectNoMoreDiagnostics 0.5
154179
155180-- Like simpleMultiTest but open the files in the other order
156- simpleMultiTest2 :: FilePath -> TestTree
157- simpleMultiTest2 variant = testCase (multiTestName variant " test2 " ) $ runWithExtraFiles variant $ \ dir -> do
181+ simpleMultiTest2 :: FilePath -> Session ()
182+ simpleMultiTest2 = \ dir -> do
158183 let aPath = dir </> " a/A.hs"
159184 bPath = dir </> " b/B.hs"
160185 bdoc <- openDoc bPath " haskell"
@@ -167,9 +192,8 @@ simpleMultiTest2 variant = testCase (multiTestName variant "test2") $ runWithExt
167192 expectNoMoreDiagnostics 0.5
168193
169194-- Now with 3 components
170- simpleMultiTest3 :: FilePath -> TestTree
171- simpleMultiTest3 variant =
172- testCase (multiTestName variant " test3" ) $ runWithExtraFiles variant $ \ dir -> do
195+ simpleMultiTest3 :: FilePath -> Session ()
196+ simpleMultiTest3 = \ dir -> do
173197 let aPath = dir </> " a/A.hs"
174198 bPath = dir </> " b/B.hs"
175199 cPath = dir </> " c/C.hs"
@@ -273,12 +297,31 @@ batchLoadRegressionTests =
273297 , testCase " s1-no-stale-outcomes-across-restart-paths" $
274298 runWithExtraFilesMultiComponent " multi" regressionNoStaleOutcomesOnRestart
275299 ]
300+ ++ [ testGroup " whole-project"
301+ [ testCase " m1-open-a-then-b-batch-pending-and-success" $
302+ runWithExtraFilesMultiComponent' PreferMultiWholeProjectLoading " multi" runRegressionMultiOpenAThenB
303+ , testCase " m2-open-b-then-a-batch-pending-and-success" $
304+ runWithExtraFilesMultiComponent' PreferMultiWholeProjectLoading " multi" runRegressionMultiOpenBThenA
305+ , testCase " m3-open-b-then-a-then-c-batch-pending-and-success" $
306+ runWithExtraFilesMultiComponent' PreferMultiWholeProjectLoading " multi" runRegressionMultiOpenBThenAThenC
307+ , testCase " f1-batch-pending-failure-does-not-isolate-broken-component" $
308+ runWithExtraFilesMultiComponent' PreferMultiWholeProjectLoading " multi" regressionBatchFailureDoesNotIsolateBrokenComponent
309+ , testCase " f2-failed-file-keeps-failing-until-cradle-fix" $
310+ runWithExtraFilesMultiComponent' PreferMultiWholeProjectLoading " multi" $ regressionFailedFileKeepsFailingUntilFix' False
311+ , testCase " r1-failed-file-recovers-after-cradle-fix" $
312+ runWithExtraFilesMultiComponent' PreferMultiWholeProjectLoading " multi" regressionFailedFileRecoversAfterFix
313+ , testCase " s1-no-stale-outcomes-across-restart-paths" $
314+ runWithExtraFilesMultiComponent' PreferMultiWholeProjectLoading " multi" $ regressionNoStaleOutcomesOnRestart' False
315+ ]]
276316
277317runWithExtraFilesMultiComponent :: String -> (FilePath -> Session a ) -> IO a
278- runWithExtraFilesMultiComponent dirName action = do
318+ runWithExtraFilesMultiComponent = runWithExtraFilesMultiComponent' PreferMultiComponentLoading
319+
320+ runWithExtraFilesMultiComponent' :: SessionLoadingPreferenceConfig -> String -> (FilePath -> Session a ) -> IO a
321+ runWithExtraFilesMultiComponent' sesLoading dirName action = do
279322 let vfs = mkIdeTestFs [copyDir dirName]
280323 lspConfig :: Config
281- lspConfig = def { sessionLoading = PreferMultiComponentLoading }
324+ lspConfig = def { sessionLoading = sesLoading }
282325 conf :: TestConfig ()
283326 conf = def
284327 { testPluginDescriptor = dummyPlugin
@@ -321,6 +364,11 @@ assertTypeCheckFailure doc msg = do
321364 WaitForIdeRuleResult {.. } <- waitForAction " TypeCheck" doc
322365 liftIO $ assertBool msg (not ideResultSuccess)
323366
367+ setWholeProjectLoading :: Session ()
368+ setWholeProjectLoading = do
369+ setIgnoringConfigurationRequests False
370+ setHlsConfig def{sessionLoading = PreferMultiWholeProjectLoading }
371+
324372regressionBatchFailureIsolatesBrokenFile :: FilePath -> Session ()
325373regressionBatchFailureIsolatesBrokenFile dir = do
326374 writeBrokenMultiHieYaml dir
@@ -333,8 +381,24 @@ regressionBatchFailureIsolatesBrokenFile dir = do
333381 liftIO $ assertBool " A should typecheck when B cradle mapping is broken" (ideResultSuccess aRes)
334382 liftIO $ assertBool " B should fail with a broken cradle mapping" (not $ ideResultSuccess bRes)
335383
384+ -- | With whole-project loading a failed component blocks the whole session.
385+ regressionBatchFailureDoesNotIsolateBrokenComponent :: FilePath -> Session ()
386+ regressionBatchFailureDoesNotIsolateBrokenComponent dir = do
387+ writeBrokenMultiHieYaml dir
388+ let aPath = dir </> " a/A.hs"
389+ bPath = dir </> " b/B.hs"
390+ adoc <- openDoc aPath " haskell"
391+ bdoc <- openDoc bPath " haskell"
392+ _ <- waitForBuildQueue
393+ [aRes, bRes] <- waitForTypeChecksBatched [adoc, bdoc]
394+ liftIO $ assertBool " A should not typecheck when B cradle mapping is broken" (not $ ideResultSuccess aRes)
395+ liftIO $ assertBool " B should fail with a broken cradle mapping" (not $ ideResultSuccess bRes)
396+
336397regressionFailedFileKeepsFailingUntilFix :: FilePath -> Session ()
337- regressionFailedFileKeepsFailingUntilFix dir = do
398+ regressionFailedFileKeepsFailingUntilFix = regressionFailedFileKeepsFailingUntilFix' True
399+
400+ regressionFailedFileKeepsFailingUntilFix' :: Bool -> FilePath -> Session ()
401+ regressionFailedFileKeepsFailingUntilFix' isolated dir = do
338402 writeBrokenMultiHieYaml dir
339403 let aPath = dir </> " a/A.hs"
340404 bPath = dir </> " b/B.hs"
@@ -347,10 +411,11 @@ regressionFailedFileKeepsFailingUntilFix dir = do
347411 [TextDocumentContentChangeEvent . InR . TextDocumentContentChangeWholeDocument $ bSource <> " \n " ]
348412 assertTypeCheckFailure bdoc " B should keep failing until the cradle is fixed"
349413
350- adoc <- openDoc aPath " haskell"
351- cdoc <- openDoc cPath " haskell"
352- assertTypeCheckSuccess adoc " A should still typecheck while B remains broken"
353- assertTypeCheckSuccess cdoc " C should still typecheck while B remains broken"
414+ when isolated $ do
415+ adoc <- openDoc aPath " haskell"
416+ cdoc <- openDoc cPath " haskell"
417+ assertTypeCheckSuccess adoc " A should still typecheck while B remains broken"
418+ assertTypeCheckSuccess cdoc " C should still typecheck while B remains broken"
354419
355420regressionFailedFileRecoversAfterFix :: FilePath -> Session ()
356421regressionFailedFileRecoversAfterFix dir = do
@@ -371,7 +436,10 @@ regressionFailedFileRecoversAfterFix dir = do
371436 assertTypeCheckSuccess bdoc " B should recover after restoring the cradle"
372437
373438regressionNoStaleOutcomesOnRestart :: FilePath -> Session ()
374- regressionNoStaleOutcomesOnRestart dir = do
439+ regressionNoStaleOutcomesOnRestart = regressionNoStaleOutcomesOnRestart' True
440+
441+ regressionNoStaleOutcomesOnRestart' :: Bool -> FilePath -> Session ()
442+ regressionNoStaleOutcomesOnRestart' isolated dir = do
375443 let hiePath = dir </> " hie.yaml"
376444 aPath = dir </> " a/A.hs"
377445 bPath = dir </> " b/B.hs"
@@ -382,8 +450,9 @@ regressionNoStaleOutcomesOnRestart dir = do
382450 bdoc <- openDoc bPath " haskell"
383451 assertTypeCheckFailure bdoc " B should fail before cradle fix"
384452
385- adoc <- openDoc aPath " haskell"
386- assertTypeCheckSuccess adoc " A should remain healthy while B is broken"
453+ when isolated $ do
454+ adoc <- openDoc aPath " haskell"
455+ assertTypeCheckSuccess adoc " A should remain healthy while B is broken"
387456
388457 liftIO $ atomicFileWriteStringUTF8 hiePath (T. unpack validHie)
389458 notifyHieYamlChanged dir
@@ -397,9 +466,8 @@ regressionNoStaleOutcomesOnRestart dir = do
397466 assertTypeCheckSuccess bdoc " B should not keep stale failure after cradle restart"
398467
399468-- Like simpleMultiTest but open the files in component 'a' in a separate session
400- simpleMultiDefTest :: FilePath -> TestTree
401- simpleMultiDefTest variant = ignoreForWindows $ testCase testName $
402- runWithExtraFiles variant $ \ dir -> do
469+ simpleMultiDefTest :: FilePath -> Session ()
470+ simpleMultiDefTest = \ dir -> do
403471 let aPath = dir </> " a/A.hs"
404472 bPath = dir </> " b/B.hs"
405473 adoc <- openDoc aPath " haskell"
@@ -411,15 +479,11 @@ simpleMultiDefTest variant = ignoreForWindows $ testCase testName $
411479 let fooL = mkL (adoc ^. L. uri) 2 0 2 3
412480 checkDefs locs (pure [fooL])
413481 expectNoMoreDiagnostics 0.5
414- where
415- testName = multiTestName variant " def-test"
416- ignoreForWindows
417- | testName == " simple-multi-def-test" = ignoreInEnv [HostOS Windows ] " Test is flaky on Windows, see #4270"
418- | otherwise = id
419482
420- multiRexportTest :: TestTree
421- multiRexportTest =
483+ multiRexportTest :: Session () -> TestTree
484+ multiRexportTest initM =
422485 testCase " multi-unit-reexport-test" $ runWithExtraFiles " multi-unit-reexport" $ \ dir -> do
486+ initM
423487 let cPath = dir </> " c/C.hs"
424488 cdoc <- openDoc cPath " haskell"
425489 WaitForIdeRuleResult {} <- waitForAction " TypeCheck" cdoc
@@ -429,10 +493,11 @@ multiRexportTest =
429493 checkDefs locs (pure [fooL])
430494 expectNoMoreDiagnostics 0.5
431495
432- sessionDepsArePickedUp :: TestTree
433- sessionDepsArePickedUp = testWithDummyPluginEmpty'
496+ sessionDepsArePickedUp :: Session () -> TestTree
497+ sessionDepsArePickedUp initM = testWithDummyPluginEmpty'
434498 " session-deps-are-picked-up"
435499 $ \ dir -> do
500+ initM
436501 liftIO $
437502 atomicFileWriteStringUTF8
438503 (dir </> " hie.yaml" )
0 commit comments