@@ -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 (.. ))
@@ -39,47 +38,58 @@ import System.FilePath
3938import Test.Hls (TestConfig (.. ), def ,
4039 runSessionWithTestConfig ,
4140 setHlsConfig ,
42- waitForBuildQueue )
41+ waitForBuildQueue , expectFailBecause )
4342import Test.Hls.FileSystem
4443import Test.Hls.Util (EnvSpec (.. ), OS (.. ),
4544 ignoreInEnv )
4645import Test.Tasty
4746import Test.Tasty.HUnit
4847
48+ defComponentLoadingConf :: SessionLoadingPreferenceConfig
49+ defComponentLoadingConf = componentsLoading def
50+
51+ wholeProjectConf :: SessionLoadingPreferenceConfig
52+ wholeProjectConf = PreferMultiWholeProjectLoading
4953
5054tests :: TestTree
5155tests = 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
56+ [ testGroup " whole project"
57+ [ testGroup " dependencies" [sessionDepsArePickedUp wholeProjectConf]
58+ , testGroup " ignore-fatal" [ignoreFatalWarning wholeProjectConf]
59+ , testGroup " loading" [loadCradleOnlyOnce wholeProjectConf, retryFailedCradle wholeProjectConf]
60+ , testGroup " regression.batch" (batchLoadRegressionTests wholeProjectConf)
61+ , testGroup " cross-cradle" [crossCradleBatchIsolationTest wholeProjectConf]
62+ , testGroup " multi" (multiTests wholeProjectConf " multi" )
63+ , testGroup " multi-unit" (multiTests wholeProjectConf " multi-unit" )
64+ , testGroup " sub-directory" [simpleSubDirectoryTest wholeProjectConf]
65+ , testGroup " multi-unit-rexport" [multiRexportTest wholeProjectConf]
6166 ]
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
67+ , testGroup " default"
68+ [ testGroup " dependencies" [sessionDepsArePickedUp defComponentLoadingConf]
69+ , testGroup " ignore-fatal" [ignoreFatalWarning defComponentLoadingConf]
70+ , testGroup " loading" [loadCradleOnlyOnce defComponentLoadingConf, retryFailedCradle defComponentLoadingConf]
71+ , testGroup " regression.batch" (batchLoadRegressionTests defComponentLoadingConf)
72+ , testGroup " cross-cradle" [crossCradleBatchIsolationTest defComponentLoadingConf]
73+ , testGroup " multi" (multiTests defComponentLoadingConf " multi" )
74+ , testGroup " multi-unit" (multiTests defComponentLoadingConf " multi-unit" )
75+ , testGroup " sub-directory" [simpleSubDirectoryTest defComponentLoadingConf]
76+ , testGroup " multi-unit-rexport" [multiRexportTest defComponentLoadingConf]
7477 ]
78+ ]
79+
80+ loadCradleOnlyOnce :: SessionLoadingPreferenceConfig -> TestTree
81+ loadCradleOnlyOnce conf = testGroup " load cradle only once"
82+ [ testWithDummyPluginEmpty' " implicit" implicit
83+ , testWithDummyPluginEmpty' " direct" direct
84+ ]
7585 where
7686 direct dir = do
7787 liftIO $ atomicFileWriteStringUTF8 (dir </> " hie.yaml" )
7888 " cradle: {direct: {arguments: []}}"
7989 test dir
8090 implicit dir = test dir
8191 test _dir = do
82- initM
92+ setComponentsLoadingPreference conf
8393 doc <- createDoc " B.hs" " haskell" " module B where\n import Data.Foo"
8494 msgs <- someTill (skipManyTill anyMessage cradleLoadedMessage) (skipManyTill anyMessage (message SMethod_TextDocumentPublishDiagnostics ))
8595 liftIO $ length msgs @?= 1
@@ -90,9 +100,9 @@ loadCradleOnlyonce initM = testGroup "load cradle only once"
90100 msgs <- manyTill (skipManyTill anyMessage cradleLoadedMessage) (skipManyTill anyMessage (message SMethod_TextDocumentPublishDiagnostics ))
91101 liftIO $ length msgs @?= 0
92102
93- retryFailedCradle :: Session () -> TestTree
94- retryFailedCradle initM = testWithDummyPluginEmpty' " retry failed" $ \ dir -> do
95- initM
103+ retryFailedCradle :: SessionLoadingPreferenceConfig -> TestTree
104+ retryFailedCradle conf = testWithDummyPluginEmpty' " retry failed" $ \ dir -> do
105+ setComponentsLoadingPreference conf
96106 -- The false cradle always fails
97107 let hieContents = " cradle: {bios: {shell: \" false\" }}"
98108 hiePath = dir </> " hie.yaml"
@@ -120,18 +130,18 @@ cradleLoadedMessage = satisfy $ \case
120130cradleLoadedMethod :: String
121131cradleLoadedMethod = " ghcide/cradle/loaded"
122132
123- ignoreFatalWarning :: Session () -> TestTree
124- ignoreFatalWarning initM = testCase " ignore-fatal-warning" $ runWithExtraFiles " ignore-fatal" $ \ dir -> do
125- initM
133+ ignoreFatalWarning :: SessionLoadingPreferenceConfig -> TestTree
134+ ignoreFatalWarning conf = testCase " ignore-fatal-warning" $ runWithExtraFiles " ignore-fatal" $ \ dir -> do
135+ setComponentsLoadingPreference conf
126136 let srcPath = dir </> " IgnoreFatal.hs"
127137 src <- liftIO $ readFileUtf8 srcPath
128138 _ <- createDoc srcPath " haskell" src
129139 expectNoMoreDiagnostics 5
130140
131- simpleSubDirectoryTest :: Session () -> TestTree
132- simpleSubDirectoryTest initM =
141+ simpleSubDirectoryTest :: SessionLoadingPreferenceConfig -> TestTree
142+ simpleSubDirectoryTest conf =
133143 testCase " simple-subdirectory" $ runWithExtraFiles " cabal-exe" $ \ dir -> do
134- initM
144+ setComponentsLoadingPreference conf
135145 let mainPath = dir </> " a/src/Main.hs"
136146 mainSource <- liftIO $ readFileUtf8 mainPath
137147 _mdoc <- createDoc mainPath " haskell" mainSource
@@ -140,23 +150,22 @@ simpleSubDirectoryTest initM =
140150 ]
141151 expectNoMoreDiagnostics 0.5
142152
143- multiTests :: FilePath -> [TestTree ]
144- multiTests odir =
145- [ testGroup " default" $ tests (return () )
146- , testGroup " whole-project" $ tests setWholeProjectLoading
147- ]
153+ multiTests :: SessionLoadingPreferenceConfig -> FilePath -> [TestTree ]
154+ multiTests conf odir =
155+ [ runOneTest testName test
156+ | (name,test) <-
157+ [ (" test" ,simpleMultiTest)
158+ , (" test2" ,simpleMultiTest2)
159+ , (" test3" ,simpleMultiTest3)
160+ , (" def-test" ,simpleMultiDefTest)
161+ ]
162+ ,
163+ let testName = multiTestName odir name
164+ ]
148165 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- ]
166+ runOneTest testName act = ignoreForWindows testName $ testCase testName $ runWithExtraFiles odir $ \ dir -> do
167+ setComponentsLoadingPreference conf
168+ act dir
160169 ignoreForWindows testName
161170 | testName == " simple-multi-def-test" = ignoreInEnv [HostOS Windows ] " Test is flaky on Windows, see #4270"
162171 | otherwise = id
@@ -315,52 +324,46 @@ waitForTypeChecksBatched docs = do
315324 A. Success res -> pure res
316325 A. Error parseErr -> liftIO (assertFailure $ " batched typecheck parse failed: " <> parseErr) >> pure []
317326
318- batchLoadRegressionTests :: [TestTree ]
319- batchLoadRegressionTests =
327+ batchLoadRegressionTests :: SessionLoadingPreferenceConfig -> [TestTree ]
328+ batchLoadRegressionTests conf =
320329 -- Note [Batch regression scheduling semantics]
321330 -- `didOpen` alone does not enqueue session-loader pending files.
322331 -- Pending entries come from GhcSession demand. For these tests, the `test`
323332 -- plugin uses `WaitForIdeRules` plus a pending-size barrier in session-loader
324333 -- to force all requested files into pending before load begins.
325334 [ testCase " m1-open-a-then-b-batch-pending-and-success" $
326- runWithExtraFilesMultiComponent " multi" runRegressionMultiOpenAThenB
335+ runWithExtraFilesMultiComponent conf " multi" runRegressionMultiOpenAThenB
327336 , testCase " m2-open-b-then-a-batch-pending-and-success" $
328- runWithExtraFilesMultiComponent " multi" runRegressionMultiOpenBThenA
337+ runWithExtraFilesMultiComponent conf " multi" runRegressionMultiOpenBThenA
329338 , testCase " m3-open-b-then-a-then-c-batch-pending-and-success" $
330- runWithExtraFilesMultiComponent " multi" runRegressionMultiOpenBThenAThenC
339+ runWithExtraFilesMultiComponent conf " multi" runRegressionMultiOpenBThenAThenC
331340 , 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
341+ runWithExtraFilesMultiComponent conf " multi" runRegressionInitialOpenSingleBatchLoad
342+ , expectBrokenWithWholeProjectLoading conf $
343+ testCase " f1-batch-pending-failure-isolates-broken-file" $
344+ runWithExtraFilesMultiComponent conf " multi" regressionBatchFailureIsolatesBrokenFile
345+ , expectBrokenWithWholeProjectLoading conf $
346+ testCase " f2-failed-file-keeps-failing-until-cradle-fix" $
347+ runWithExtraFilesMultiComponent conf " multi" regressionFailedFileKeepsFailingUntilFix
348+ , testCase " f3-batch-pending-failure-does-not-isolate-broken-component" $
349+ runWithExtraFilesMultiComponent conf " multi" regressionBatchFailureDoesNotIsolateBrokenComponent
337350 , 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
351+ runWithExtraFilesMultiComponent conf " multi" regressionFailedFileRecoversAfterFix
352+ , expectBrokenWithWholeProjectLoading conf
353+ $ testCase " s1-no-stale-outcomes-across-restart-paths" $
354+ runWithExtraFilesMultiComponent conf " multi" regressionNoStaleOutcomesOnRestart
355+ , testCase " s2-no-stale-outcomes-across-restart-paths" $
356+ runWithExtraFilesMultiComponent conf " multi" regressionNoStaleOutcomesOnRestartNotHealthyInBetween
341357 ]
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- ]]
358358
359- runWithExtraFilesMultiComponent :: String -> (FilePath -> Session a ) -> IO a
360- runWithExtraFilesMultiComponent = runWithExtraFilesMultiComponent' PreferMultiComponentLoading
359+ expectBrokenWithWholeProjectLoading :: SessionLoadingPreferenceConfig -> TestTree -> TestTree
360+ expectBrokenWithWholeProjectLoading conf =
361+ if conf == wholeProjectConf
362+ then expectFailBecause " We can't load the whole project if the hie.yaml file is invalid"
363+ else id
361364
362- runWithExtraFilesMultiComponent' :: SessionLoadingPreferenceConfig -> String -> (FilePath -> Session a ) -> IO a
363- runWithExtraFilesMultiComponent' sesLoading dirName action = do
365+ runWithExtraFilesMultiComponent :: SessionLoadingPreferenceConfig -> String -> (FilePath -> Session a ) -> IO a
366+ runWithExtraFilesMultiComponent sesLoading dirName action = do
364367 let vfs = mkIdeTestFs [copyDir dirName]
365368 lspConfig :: Config
366369 lspConfig = def { componentsLoading = sesLoading }
@@ -406,10 +409,10 @@ assertTypeCheckFailure doc msg = do
406409 WaitForIdeRuleResult {.. } <- waitForAction " TypeCheck" doc
407410 liftIO $ assertBool msg (not ideResultSuccess)
408411
409- setWholeProjectLoading :: Session ()
410- setWholeProjectLoading = do
412+ setComponentsLoadingPreference :: SessionLoadingPreferenceConfig -> Session ()
413+ setComponentsLoadingPreference pref = do
411414 setIgnoringConfigurationRequests False
412- setHlsConfig def{componentsLoading = PreferMultiWholeProjectLoading }
415+ setHlsConfig def{componentsLoading = pref }
413416
414417regressionBatchFailureIsolatesBrokenFile :: FilePath -> Session ()
415418regressionBatchFailureIsolatesBrokenFile dir = do
@@ -437,10 +440,7 @@ regressionBatchFailureDoesNotIsolateBrokenComponent dir = do
437440 liftIO $ assertBool " B should fail with a broken cradle mapping" (not $ ideResultSuccess bRes)
438441
439442regressionFailedFileKeepsFailingUntilFix :: FilePath -> Session ()
440- regressionFailedFileKeepsFailingUntilFix = regressionFailedFileKeepsFailingUntilFix' True
441-
442- regressionFailedFileKeepsFailingUntilFix' :: Bool -> FilePath -> Session ()
443- regressionFailedFileKeepsFailingUntilFix' isolated dir = do
443+ regressionFailedFileKeepsFailingUntilFix dir = do
444444 writeBrokenMultiHieYaml dir
445445 let aPath = dir </> " a/A.hs"
446446 bPath = dir </> " b/B.hs"
@@ -453,11 +453,10 @@ regressionFailedFileKeepsFailingUntilFix' isolated dir = do
453453 [TextDocumentContentChangeEvent . InR . TextDocumentContentChangeWholeDocument $ bSource <> " \n " ]
454454 assertTypeCheckFailure bdoc " B should keep failing until the cradle is fixed"
455455
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"
456+ adoc <- openDoc aPath " haskell"
457+ cdoc <- openDoc cPath " haskell"
458+ assertTypeCheckSuccess adoc " A should still typecheck while B remains broken"
459+ assertTypeCheckSuccess cdoc " C should still typecheck while B remains broken"
461460
462461regressionFailedFileRecoversAfterFix :: FilePath -> Session ()
463462regressionFailedFileRecoversAfterFix dir = do
@@ -478,10 +477,7 @@ regressionFailedFileRecoversAfterFix dir = do
478477 assertTypeCheckSuccess bdoc " B should recover after restoring the cradle"
479478
480479regressionNoStaleOutcomesOnRestart :: FilePath -> Session ()
481- regressionNoStaleOutcomesOnRestart = regressionNoStaleOutcomesOnRestart' True
482-
483- regressionNoStaleOutcomesOnRestart' :: Bool -> FilePath -> Session ()
484- regressionNoStaleOutcomesOnRestart' isolated dir = do
480+ regressionNoStaleOutcomesOnRestart dir = do
485481 let hiePath = dir </> " hie.yaml"
486482 aPath = dir </> " a/A.hs"
487483 bPath = dir </> " b/B.hs"
@@ -492,9 +488,35 @@ regressionNoStaleOutcomesOnRestart' isolated dir = do
492488 bdoc <- openDoc bPath " haskell"
493489 assertTypeCheckFailure bdoc " B should fail before cradle fix"
494490
495- when isolated $ do
496- adoc <- openDoc aPath " haskell"
497- assertTypeCheckSuccess adoc " A should remain healthy while B is broken"
491+ adoc <- openDoc aPath " haskell"
492+ assertTypeCheckSuccess adoc " A should remain healthy while B is broken"
493+
494+ liftIO $ atomicFileWriteStringUTF8 hiePath (T. unpack validHie)
495+ notifyHieYamlChanged dir
496+
497+ cdoc <- openDoc cPath " haskell"
498+ assertTypeCheckSuccess cdoc " C should typecheck after cradle restart"
499+
500+ bSource <- liftIO $ readFileUtf8 bPath
501+ changeDoc bdoc
502+ [TextDocumentContentChangeEvent . InR . TextDocumentContentChangeWholeDocument $ bSource <> " \n " ]
503+ assertTypeCheckSuccess bdoc " B should not keep stale failure after cradle restart"
504+
505+ -- | Like 'regressionNoStaleOutcomesOnRestart', but we don't check that
506+ -- unrelated components can still be loaded.
507+ --
508+ -- When we load the whole project, we can't load intermediate components, since the hie.yaml =
509+ -- is broken.
510+ regressionNoStaleOutcomesOnRestartNotHealthyInBetween :: FilePath -> Session ()
511+ regressionNoStaleOutcomesOnRestartNotHealthyInBetween dir = do
512+ let hiePath = dir </> " hie.yaml"
513+ bPath = dir </> " b/B.hs"
514+ cPath = dir </> " c/C.hs"
515+ validHie <- liftIO $ readFileUtf8 hiePath
516+ writeBrokenMultiHieYaml dir
517+
518+ bdoc <- openDoc bPath " haskell"
519+ assertTypeCheckFailure bdoc " B should fail before cradle fix"
498520
499521 liftIO $ atomicFileWriteStringUTF8 hiePath (T. unpack validHie)
500522 notifyHieYamlChanged dir
@@ -512,10 +534,10 @@ regressionNoStaleOutcomesOnRestart' isolated dir = do
512534-- cradle; once it is loaded, opening @a/A.hs@ (owned by the root cabal cradle)
513535-- used to batch the standalone file into @cabal repl@, which cannot map it to
514536-- any component and fails wholesale, poisoning the load of A.
515- crossCradleBatchIsolationTest :: TestTree
516- crossCradleBatchIsolationTest =
537+ crossCradleBatchIsolationTest :: SessionLoadingPreferenceConfig -> TestTree
538+ crossCradleBatchIsolationTest conf =
517539 testCase " direct-cradle-file-does-not-poison-cabal-load" $
518- runWithExtraFilesMultiComponent " cross-cradle" $ \ dir -> do
540+ runWithExtraFilesMultiComponent conf " cross-cradle" $ \ dir -> do
519541 let standalonePath = dir </> " standalone/Standalone.hs"
520542 aPath = dir </> " a/A.hs"
521543 sdoc <- openDoc standalonePath " haskell"
@@ -539,10 +561,10 @@ simpleMultiDefTest = \dir -> do
539561 checkDefs locs (pure [fooL])
540562 expectNoMoreDiagnostics 0.5
541563
542- multiRexportTest :: Session () -> TestTree
543- multiRexportTest initM =
564+ multiRexportTest :: SessionLoadingPreferenceConfig -> TestTree
565+ multiRexportTest conf =
544566 testCase " multi-unit-reexport-test" $ runWithExtraFiles " multi-unit-reexport" $ \ dir -> do
545- initM
567+ setComponentsLoadingPreference conf
546568 let cPath = dir </> " c/C.hs"
547569 cdoc <- openDoc cPath " haskell"
548570 WaitForIdeRuleResult {} <- waitForAction " TypeCheck" cdoc
@@ -552,11 +574,11 @@ multiRexportTest initM =
552574 checkDefs locs (pure [fooL])
553575 expectNoMoreDiagnostics 0.5
554576
555- sessionDepsArePickedUp :: Session () -> TestTree
556- sessionDepsArePickedUp initM = testWithDummyPluginEmpty'
577+ sessionDepsArePickedUp :: SessionLoadingPreferenceConfig -> TestTree
578+ sessionDepsArePickedUp conf = testWithDummyPluginEmpty'
557579 " session-deps-are-picked-up"
558580 $ \ dir -> do
559- initM
581+ setComponentsLoadingPreference conf
560582 liftIO $
561583 atomicFileWriteStringUTF8
562584 (dir </> " hie.yaml" )
0 commit comments