@@ -10,7 +10,6 @@ import Config (checkDefs, dummyPlugin,
1010 testWithDummyPluginEmpty' )
1111import Control.Applicative.Combinators
1212import Control.Lens ((^.) )
13- import Control.Monad (when )
1413import Control.Monad.IO.Class (liftIO )
1514import qualified Data.Aeson as A
1615import Data.Proxy (Proxy (.. ))
@@ -37,6 +36,8 @@ import Language.LSP.Protocol.Types hiding
3736import Language.LSP.Test
3837import System.FilePath
3938import Test.Hls (TestConfig (.. ), def ,
39+ expectFailBecause ,
40+ ignoreTestBecause ,
4041 runSessionWithTestConfig ,
4142 setHlsConfig ,
4243 waitForBuildQueue )
@@ -46,40 +47,51 @@ import Test.Hls.Util (EnvSpec (..), OS (..),
4647import Test.Tasty
4748import Test.Tasty.HUnit
4849
50+ defComponentLoadingConf :: SessionLoadingPreferenceConfig
51+ defComponentLoadingConf = componentsLoading def
52+
53+ wholeProjectConf :: SessionLoadingPreferenceConfig
54+ wholeProjectConf = PreferMultiWholeProjectLoading
4955
5056tests :: TestTree
5157tests = testGroup " cradle"
52- [testGroup " dependencies" $ bothLoadings1 sessionDepsArePickedUp
53- ,testGroup " ignore-fatal" $ bothLoadings1 ignoreFatalWarning
54- ,testGroup " loading" $ bothLoadings $ \ initM -> [loadCradleOnlyonce initM, retryFailedCradle initM]
55- ,testGroup " regression.batch" batchLoadRegressionTests
56- ,testGroup " cross-cradle" [crossCradleBatchIsolationTest]
57- ,testGroup " multi" (multiTests " multi" )
58- ,testGroup " multi-unit" (multiTests " multi-unit" )
59- ,testGroup " sub-directory" $ bothLoadings1 simpleSubDirectoryTest
60- ,testGroup " multi-unit-rexport" $ bothLoadings1 multiRexportTest
58+ [ testGroup " whole project"
59+ [ testGroup " dependencies" [sessionDepsArePickedUp wholeProjectConf]
60+ , testGroup " ignore-fatal" [ignoreFatalWarning wholeProjectConf]
61+ , testGroup " loading" [loadCradleOnlyOnce wholeProjectConf, retryFailedCradle wholeProjectConf]
62+ , testGroup " regression.batch" (batchLoadRegressionTests wholeProjectConf)
63+ , testGroup " cross-cradle" [crossCradleBatchIsolationTest wholeProjectConf]
64+ , testGroup " multi" (multiTests wholeProjectConf " multi" )
65+ , testGroup " multi-unit" (multiTests wholeProjectConf " multi-unit" )
66+ , testGroup " sub-directory" [simpleSubDirectoryTest wholeProjectConf]
67+ , testGroup " multi-unit-rexport" [multiRexportTest wholeProjectConf]
6168 ]
62-
63- -- | Test both with the default `componentsLoading` and WholeProject.
64- bothLoadings :: (Session () -> [TestTree ]) -> [TestTree ]
65- bothLoadings m = [testGroup " default" (m (return () ))
66- , testGroup " whole-project" (m setWholeProjectLoading)]
67- bothLoadings1 :: (Session () -> TestTree ) -> [TestTree ]
68- bothLoadings1 m = bothLoadings $ \ initM -> [m initM]
69-
70- loadCradleOnlyonce :: Session () -> TestTree
71- loadCradleOnlyonce initM = testGroup " load cradle only once"
72- [ testWithDummyPluginEmpty' " implicit" implicit
73- , testWithDummyPluginEmpty' " direct" direct
69+ , testGroup " default"
70+ [ testGroup " dependencies" [sessionDepsArePickedUp defComponentLoadingConf]
71+ , testGroup " ignore-fatal" [ignoreFatalWarning defComponentLoadingConf]
72+ , testGroup " loading" [loadCradleOnlyOnce defComponentLoadingConf, retryFailedCradle defComponentLoadingConf]
73+ , testGroup " regression.batch" (batchLoadRegressionTests defComponentLoadingConf)
74+ , testGroup " cross-cradle" [crossCradleBatchIsolationTest defComponentLoadingConf]
75+ , testGroup " multi" (multiTests defComponentLoadingConf " multi" )
76+ , testGroup " multi-unit" (multiTests defComponentLoadingConf " multi-unit" )
77+ , testGroup " sub-directory" [simpleSubDirectoryTest defComponentLoadingConf]
78+ , testGroup " multi-unit-rexport" [multiRexportTest defComponentLoadingConf]
7479 ]
80+ ]
81+
82+ loadCradleOnlyOnce :: SessionLoadingPreferenceConfig -> TestTree
83+ loadCradleOnlyOnce conf = testGroup " load cradle only once"
84+ [ testWithDummyPluginEmpty' " implicit" implicit
85+ , testWithDummyPluginEmpty' " direct" direct
86+ ]
7587 where
7688 direct dir = do
7789 liftIO $ atomicFileWriteStringUTF8 (dir </> " hie.yaml" )
7890 " cradle: {direct: {arguments: []}}"
7991 test dir
8092 implicit dir = test dir
8193 test _dir = do
82- initM
94+ setComponentsLoadingPreference conf
8395 doc <- createDoc " B.hs" " haskell" " module B where\n import Data.Foo"
8496 msgs <- someTill (skipManyTill anyMessage cradleLoadedMessage) (skipManyTill anyMessage (message SMethod_TextDocumentPublishDiagnostics ))
8597 liftIO $ length msgs @?= 1
@@ -90,9 +102,9 @@ loadCradleOnlyonce initM = testGroup "load cradle only once"
90102 msgs <- manyTill (skipManyTill anyMessage cradleLoadedMessage) (skipManyTill anyMessage (message SMethod_TextDocumentPublishDiagnostics ))
91103 liftIO $ length msgs @?= 0
92104
93- retryFailedCradle :: Session () -> TestTree
94- retryFailedCradle initM = testWithDummyPluginEmpty' " retry failed" $ \ dir -> do
95- initM
105+ retryFailedCradle :: SessionLoadingPreferenceConfig -> TestTree
106+ retryFailedCradle conf = testWithDummyPluginEmpty' " retry failed" $ \ dir -> do
107+ setComponentsLoadingPreference conf
96108 -- The false cradle always fails
97109 let hieContents = " cradle: {bios: {shell: \" false\" }}"
98110 hiePath = dir </> " hie.yaml"
@@ -120,18 +132,18 @@ cradleLoadedMessage = satisfy $ \case
120132cradleLoadedMethod :: String
121133cradleLoadedMethod = " ghcide/cradle/loaded"
122134
123- ignoreFatalWarning :: Session () -> TestTree
124- ignoreFatalWarning initM = testCase " ignore-fatal-warning" $ runWithExtraFiles " ignore-fatal" $ \ dir -> do
125- initM
135+ ignoreFatalWarning :: SessionLoadingPreferenceConfig -> TestTree
136+ ignoreFatalWarning conf = testCase " ignore-fatal-warning" $ runWithExtraFiles " ignore-fatal" $ \ dir -> do
137+ setComponentsLoadingPreference conf
126138 let srcPath = dir </> " IgnoreFatal.hs"
127139 src <- liftIO $ readFileUtf8 srcPath
128140 _ <- createDoc srcPath " haskell" src
129141 expectNoMoreDiagnostics 5
130142
131- simpleSubDirectoryTest :: Session () -> TestTree
132- simpleSubDirectoryTest initM =
143+ simpleSubDirectoryTest :: SessionLoadingPreferenceConfig -> TestTree
144+ simpleSubDirectoryTest conf =
133145 testCase " simple-subdirectory" $ runWithExtraFiles " cabal-exe" $ \ dir -> do
134- initM
146+ setComponentsLoadingPreference conf
135147 let mainPath = dir </> " a/src/Main.hs"
136148 mainSource <- liftIO $ readFileUtf8 mainPath
137149 _mdoc <- createDoc mainPath " haskell" mainSource
@@ -140,23 +152,22 @@ simpleSubDirectoryTest initM =
140152 ]
141153 expectNoMoreDiagnostics 0.5
142154
143- multiTests :: FilePath -> [TestTree ]
144- multiTests odir =
145- [ testGroup " default" $ tests (return () )
146- , testGroup " whole-project" $ tests setWholeProjectLoading
147- ]
155+ multiTests :: SessionLoadingPreferenceConfig -> FilePath -> [TestTree ]
156+ multiTests conf odir =
157+ [ runOneTest testName test
158+ | (name,test) <-
159+ [ (" test" ,simpleMultiTest)
160+ , (" test2" ,simpleMultiTest2)
161+ , (" test3" ,simpleMultiTest3)
162+ , (" def-test" ,simpleMultiDefTest)
163+ ]
164+ ,
165+ let testName = multiTestName odir name
166+ ]
148167 where
149- tests initM =
150- [ ignoreForWindows testName $ testCase testName $ runWithExtraFiles odir $ \ dir -> initM >> test dir
151- | (name,test) <-
152- [ (" test" ,simpleMultiTest)
153- , (" test2" ,simpleMultiTest2)
154- , (" test3" ,simpleMultiTest3)
155- , (" def-test" ,simpleMultiDefTest)
156- ]
157- ,
158- let testName = multiTestName odir name
159- ]
168+ runOneTest testName act = ignoreForWindows testName $ testCase testName $ runWithExtraFiles odir $ \ dir -> do
169+ setComponentsLoadingPreference conf
170+ act dir
160171 ignoreForWindows testName
161172 | testName == " simple-multi-def-test" = ignoreInEnv [HostOS Windows ] " Test is flaky on Windows, see #4270"
162173 | otherwise = id
@@ -315,52 +326,53 @@ waitForTypeChecksBatched docs = do
315326 A. Success res -> pure res
316327 A. Error parseErr -> liftIO (assertFailure $ " batched typecheck parse failed: " <> parseErr) >> pure []
317328
318- batchLoadRegressionTests :: [TestTree ]
319- batchLoadRegressionTests =
329+ batchLoadRegressionTests :: SessionLoadingPreferenceConfig -> [TestTree ]
330+ batchLoadRegressionTests conf =
320331 -- Note [Batch regression scheduling semantics]
321332 -- `didOpen` alone does not enqueue session-loader pending files.
322333 -- Pending entries come from GhcSession demand. For these tests, the `test`
323334 -- plugin uses `WaitForIdeRules` plus a pending-size barrier in session-loader
324335 -- to force all requested files into pending before load begins.
325336 [ testCase " m1-open-a-then-b-batch-pending-and-success" $
326- runWithExtraFilesMultiComponent " multi" runRegressionMultiOpenAThenB
337+ runWithExtraFilesMultiComponent conf " multi" runRegressionMultiOpenAThenB
327338 , testCase " m2-open-b-then-a-batch-pending-and-success" $
328- runWithExtraFilesMultiComponent " multi" runRegressionMultiOpenBThenA
339+ runWithExtraFilesMultiComponent conf " multi" runRegressionMultiOpenBThenA
329340 , testCase " m3-open-b-then-a-then-c-batch-pending-and-success" $
330- runWithExtraFilesMultiComponent " multi" runRegressionMultiOpenBThenAThenC
341+ runWithExtraFilesMultiComponent conf " multi" runRegressionMultiOpenBThenAThenC
331342 , testCase " m4-initial-multi-file-open-loads-cradle-once" $
332- runWithExtraFilesMultiComponent " multi" runRegressionInitialOpenSingleBatchLoad
333- , testCase " f1-batch-pending-failure-isolates-broken-file" $
334- runWithExtraFilesMultiComponent " multi" regressionBatchFailureIsolatesBrokenFile
335- , testCase " f2-failed-file-keeps-failing-until-cradle-fix" $
336- runWithExtraFilesMultiComponent " multi" regressionFailedFileKeepsFailingUntilFix
343+ runWithExtraFilesMultiComponent conf " multi" runRegressionInitialOpenSingleBatchLoad
344+ , expectBrokenWithWholeProjectLoading conf $
345+ testCase " f1-batch-pending-failure-isolates-broken-file" $
346+ runWithExtraFilesMultiComponent conf " multi" regressionBatchFailureIsolatesBrokenFile
347+ , expectBrokenWithWholeProjectLoading conf $
348+ testCase " f2-failed-file-keeps-failing-until-cradle-fix" $
349+ runWithExtraFilesMultiComponent conf " multi" regressionFailedFileKeepsFailingUntilFix
350+ , onlyWholeProjectLoading conf $
351+ testCase " f3-batch-pending-failure-does-not-isolate-broken-component" $
352+ runWithExtraFilesMultiComponent conf " multi" regressionBatchFailureDoesNotIsolateBrokenComponent
337353 , testCase " r1-failed-file-recovers-after-cradle-fix" $
338- runWithExtraFilesMultiComponent " multi" regressionFailedFileRecoversAfterFix
339- , testCase " s1-no-stale-outcomes-across-restart-paths" $
340- runWithExtraFilesMultiComponent " multi" regressionNoStaleOutcomesOnRestart
354+ runWithExtraFilesMultiComponent conf " multi" regressionFailedFileRecoversAfterFix
355+ , expectBrokenWithWholeProjectLoading conf $
356+ testCase " s1-no-stale-outcomes-across-restart-paths" $
357+ runWithExtraFilesMultiComponent conf " multi" regressionNoStaleOutcomesOnRestart
358+ , testCase " s2-no-stale-outcomes-across-restart-paths" $
359+ runWithExtraFilesMultiComponent conf " multi" regressionNoStaleOutcomesOnRestartNotHealthyInBetween
341360 ]
342- ++ [ testGroup " whole-project"
343- [ testCase " m1-open-a-then-b-batch-pending-and-success" $
344- runWithExtraFilesMultiComponent' PreferMultiWholeProjectLoading " multi" runRegressionMultiOpenAThenB
345- , testCase " m2-open-b-then-a-batch-pending-and-success" $
346- runWithExtraFilesMultiComponent' PreferMultiWholeProjectLoading " multi" runRegressionMultiOpenBThenA
347- , testCase " m3-open-b-then-a-then-c-batch-pending-and-success" $
348- runWithExtraFilesMultiComponent' PreferMultiWholeProjectLoading " multi" runRegressionMultiOpenBThenAThenC
349- , testCase " f1-batch-pending-failure-does-not-isolate-broken-component" $
350- runWithExtraFilesMultiComponent' PreferMultiWholeProjectLoading " multi" regressionBatchFailureDoesNotIsolateBrokenComponent
351- , testCase " f2-failed-file-keeps-failing-until-cradle-fix" $
352- runWithExtraFilesMultiComponent' PreferMultiWholeProjectLoading " multi" $ regressionFailedFileKeepsFailingUntilFix' False
353- , testCase " r1-failed-file-recovers-after-cradle-fix" $
354- runWithExtraFilesMultiComponent' PreferMultiWholeProjectLoading " multi" regressionFailedFileRecoversAfterFix
355- , testCase " s1-no-stale-outcomes-across-restart-paths" $
356- runWithExtraFilesMultiComponent' PreferMultiWholeProjectLoading " multi" $ regressionNoStaleOutcomesOnRestart' False
357- ]]
358361
359- runWithExtraFilesMultiComponent :: String -> (FilePath -> Session a ) -> IO a
360- runWithExtraFilesMultiComponent = runWithExtraFilesMultiComponent' PreferMultiComponentLoading
362+ expectBrokenWithWholeProjectLoading :: SessionLoadingPreferenceConfig -> TestTree -> TestTree
363+ expectBrokenWithWholeProjectLoading conf =
364+ if conf == wholeProjectConf
365+ then expectFailBecause " We can't load the whole project if the hie.yaml file is invalid"
366+ else id
361367
362- runWithExtraFilesMultiComponent' :: SessionLoadingPreferenceConfig -> String -> (FilePath -> Session a ) -> IO a
363- runWithExtraFilesMultiComponent' sesLoading dirName action = do
368+ onlyWholeProjectLoading :: SessionLoadingPreferenceConfig -> TestTree -> TestTree
369+ onlyWholeProjectLoading conf =
370+ if conf == wholeProjectConf
371+ then id
372+ else ignoreTestBecause " This test only works with PreferMultiWholeProjectLoading"
373+
374+ runWithExtraFilesMultiComponent :: SessionLoadingPreferenceConfig -> String -> (FilePath -> Session a ) -> IO a
375+ runWithExtraFilesMultiComponent sesLoading dirName action = do
364376 let vfs = mkIdeTestFs [copyDir dirName]
365377 lspConfig :: Config
366378 lspConfig = def { componentsLoading = sesLoading }
@@ -406,10 +418,10 @@ assertTypeCheckFailure doc msg = do
406418 WaitForIdeRuleResult {.. } <- waitForAction " TypeCheck" doc
407419 liftIO $ assertBool msg (not ideResultSuccess)
408420
409- setWholeProjectLoading :: Session ()
410- setWholeProjectLoading = do
421+ setComponentsLoadingPreference :: SessionLoadingPreferenceConfig -> Session ()
422+ setComponentsLoadingPreference pref = do
411423 setIgnoringConfigurationRequests False
412- setHlsConfig def{componentsLoading = PreferMultiWholeProjectLoading }
424+ setHlsConfig def{componentsLoading = pref }
413425
414426regressionBatchFailureIsolatesBrokenFile :: FilePath -> Session ()
415427regressionBatchFailureIsolatesBrokenFile dir = do
@@ -437,10 +449,7 @@ regressionBatchFailureDoesNotIsolateBrokenComponent dir = do
437449 liftIO $ assertBool " B should fail with a broken cradle mapping" (not $ ideResultSuccess bRes)
438450
439451regressionFailedFileKeepsFailingUntilFix :: FilePath -> Session ()
440- regressionFailedFileKeepsFailingUntilFix = regressionFailedFileKeepsFailingUntilFix' True
441-
442- regressionFailedFileKeepsFailingUntilFix' :: Bool -> FilePath -> Session ()
443- regressionFailedFileKeepsFailingUntilFix' isolated dir = do
452+ regressionFailedFileKeepsFailingUntilFix dir = do
444453 writeBrokenMultiHieYaml dir
445454 let aPath = dir </> " a/A.hs"
446455 bPath = dir </> " b/B.hs"
@@ -453,11 +462,10 @@ regressionFailedFileKeepsFailingUntilFix' isolated dir = do
453462 [TextDocumentContentChangeEvent . InR . TextDocumentContentChangeWholeDocument $ bSource <> " \n " ]
454463 assertTypeCheckFailure bdoc " B should keep failing until the cradle is fixed"
455464
456- when isolated $ do
457- adoc <- openDoc aPath " haskell"
458- cdoc <- openDoc cPath " haskell"
459- assertTypeCheckSuccess adoc " A should still typecheck while B remains broken"
460- assertTypeCheckSuccess cdoc " C should still typecheck while B remains broken"
465+ adoc <- openDoc aPath " haskell"
466+ cdoc <- openDoc cPath " haskell"
467+ assertTypeCheckSuccess adoc " A should still typecheck while B remains broken"
468+ assertTypeCheckSuccess cdoc " C should still typecheck while B remains broken"
461469
462470regressionFailedFileRecoversAfterFix :: FilePath -> Session ()
463471regressionFailedFileRecoversAfterFix dir = do
@@ -478,10 +486,7 @@ regressionFailedFileRecoversAfterFix dir = do
478486 assertTypeCheckSuccess bdoc " B should recover after restoring the cradle"
479487
480488regressionNoStaleOutcomesOnRestart :: FilePath -> Session ()
481- regressionNoStaleOutcomesOnRestart = regressionNoStaleOutcomesOnRestart' True
482-
483- regressionNoStaleOutcomesOnRestart' :: Bool -> FilePath -> Session ()
484- regressionNoStaleOutcomesOnRestart' isolated dir = do
489+ regressionNoStaleOutcomesOnRestart dir = do
485490 let hiePath = dir </> " hie.yaml"
486491 aPath = dir </> " a/A.hs"
487492 bPath = dir </> " b/B.hs"
@@ -492,9 +497,35 @@ regressionNoStaleOutcomesOnRestart' isolated dir = do
492497 bdoc <- openDoc bPath " haskell"
493498 assertTypeCheckFailure bdoc " B should fail before cradle fix"
494499
495- when isolated $ do
496- adoc <- openDoc aPath " haskell"
497- assertTypeCheckSuccess adoc " A should remain healthy while B is broken"
500+ adoc <- openDoc aPath " haskell"
501+ assertTypeCheckSuccess adoc " A should remain healthy while B is broken"
502+
503+ liftIO $ atomicFileWriteStringUTF8 hiePath (T. unpack validHie)
504+ notifyHieYamlChanged dir
505+
506+ cdoc <- openDoc cPath " haskell"
507+ assertTypeCheckSuccess cdoc " C should typecheck after cradle restart"
508+
509+ bSource <- liftIO $ readFileUtf8 bPath
510+ changeDoc bdoc
511+ [TextDocumentContentChangeEvent . InR . TextDocumentContentChangeWholeDocument $ bSource <> " \n " ]
512+ assertTypeCheckSuccess bdoc " B should not keep stale failure after cradle restart"
513+
514+ -- | Like 'regressionNoStaleOutcomesOnRestart', but we don't check that
515+ -- unrelated components can still be loaded.
516+ --
517+ -- When we load the whole project, we can't load intermediate components, since the hie.yaml =
518+ -- is broken.
519+ regressionNoStaleOutcomesOnRestartNotHealthyInBetween :: FilePath -> Session ()
520+ regressionNoStaleOutcomesOnRestartNotHealthyInBetween dir = do
521+ let hiePath = dir </> " hie.yaml"
522+ bPath = dir </> " b/B.hs"
523+ cPath = dir </> " c/C.hs"
524+ validHie <- liftIO $ readFileUtf8 hiePath
525+ writeBrokenMultiHieYaml dir
526+
527+ bdoc <- openDoc bPath " haskell"
528+ assertTypeCheckFailure bdoc " B should fail before cradle fix"
498529
499530 liftIO $ atomicFileWriteStringUTF8 hiePath (T. unpack validHie)
500531 notifyHieYamlChanged dir
@@ -512,10 +543,10 @@ regressionNoStaleOutcomesOnRestart' isolated dir = do
512543-- cradle; once it is loaded, opening @a/A.hs@ (owned by the root cabal cradle)
513544-- used to batch the standalone file into @cabal repl@, which cannot map it to
514545-- any component and fails wholesale, poisoning the load of A.
515- crossCradleBatchIsolationTest :: TestTree
516- crossCradleBatchIsolationTest =
546+ crossCradleBatchIsolationTest :: SessionLoadingPreferenceConfig -> TestTree
547+ crossCradleBatchIsolationTest conf =
517548 testCase " direct-cradle-file-does-not-poison-cabal-load" $
518- runWithExtraFilesMultiComponent " cross-cradle" $ \ dir -> do
549+ runWithExtraFilesMultiComponent conf " cross-cradle" $ \ dir -> do
519550 let standalonePath = dir </> " standalone/Standalone.hs"
520551 aPath = dir </> " a/A.hs"
521552 sdoc <- openDoc standalonePath " haskell"
@@ -539,10 +570,10 @@ simpleMultiDefTest = \dir -> do
539570 checkDefs locs (pure [fooL])
540571 expectNoMoreDiagnostics 0.5
541572
542- multiRexportTest :: Session () -> TestTree
543- multiRexportTest initM =
573+ multiRexportTest :: SessionLoadingPreferenceConfig -> TestTree
574+ multiRexportTest conf =
544575 testCase " multi-unit-reexport-test" $ runWithExtraFiles " multi-unit-reexport" $ \ dir -> do
545- initM
576+ setComponentsLoadingPreference conf
546577 let cPath = dir </> " c/C.hs"
547578 cdoc <- openDoc cPath " haskell"
548579 WaitForIdeRuleResult {} <- waitForAction " TypeCheck" cdoc
@@ -552,11 +583,11 @@ multiRexportTest initM =
552583 checkDefs locs (pure [fooL])
553584 expectNoMoreDiagnostics 0.5
554585
555- sessionDepsArePickedUp :: Session () -> TestTree
556- sessionDepsArePickedUp initM = testWithDummyPluginEmpty'
586+ sessionDepsArePickedUp :: SessionLoadingPreferenceConfig -> TestTree
587+ sessionDepsArePickedUp conf = testWithDummyPluginEmpty'
557588 " session-deps-are-picked-up"
558589 $ \ dir -> do
559- initM
590+ setComponentsLoadingPreference conf
560591 liftIO $
561592 atomicFileWriteStringUTF8
562593 (dir </> " hie.yaml" )
0 commit comments