Skip to content

Commit f395c21

Browse files
authored
Merge branch 'master' into Server-Side_Diagnostics
2 parents 2495cb5 + b7636e8 commit f395c21

9 files changed

Lines changed: 1557 additions & 792 deletions

File tree

ghcide-test/exe/CradleTests.hs

Lines changed: 226 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,31 @@
11

2-
{-# LANGUAGE GADTs #-}
2+
{-# LANGUAGE DataKinds #-}
3+
{-# LANGUAGE GADTs #-}
34

45
module CradleTests (tests) where
56

6-
import Config (checkDefs, mkL,
7+
import Config (checkDefs, dummyPlugin,
8+
lspTestCaps, mkIdeTestFs, mkL,
79
runWithExtraFiles,
810
testWithDummyPluginEmpty')
911
import Control.Applicative.Combinators
1012
import Control.Lens ((^.))
1113
import Control.Monad.IO.Class (liftIO)
14+
import qualified Data.Aeson as A
15+
import Data.Proxy (Proxy (..))
1216
import qualified Data.Text as T
1317
import Development.IDE.GHC.Util
14-
import Development.IDE.Plugin.Test (WaitForIdeRuleResult (..))
18+
import Development.IDE.Plugin.Test (TestRequest (..),
19+
WaitForIdeRuleResult (..))
1520
import Development.IDE.Test (expectDiagnostics,
1621
expectDiagnosticsWithTags,
1722
expectNoMoreDiagnostics,
1823
isReferenceReady,
1924
waitForAction)
2025
import Development.IDE.Types.Location
2126
import GHC.TypeLits (symbolVal)
27+
import Ide.Types (Config (..),
28+
SessionLoadingPreferenceConfig (..))
2229
import qualified Language.LSP.Protocol.Lens as L
2330
import Language.LSP.Protocol.Message
2431
import Language.LSP.Protocol.Types hiding
@@ -28,7 +35,9 @@ import Language.LSP.Protocol.Types hiding
2835
mkRange)
2936
import Language.LSP.Test
3037
import System.FilePath
31-
import System.IO.Extra hiding (withTempDir)
38+
import Test.Hls (TestConfig (..), def,
39+
runSessionWithTestConfig,
40+
waitForBuildQueue)
3241
import Test.Hls.FileSystem
3342
import Test.Hls.Util (EnvSpec (..), OS (..),
3443
ignoreInEnv)
@@ -41,6 +50,7 @@ tests = testGroup "cradle"
4150
[testGroup "dependencies" [sessionDepsArePickedUp]
4251
,testGroup "ignore-fatal" [ignoreFatalWarning]
4352
,testGroup "loading" [loadCradleOnlyonce, retryFailedCradle]
53+
,testGroup "regression.batch" batchLoadRegressionTests
4454
,testGroup "multi" (multiTests "multi")
4555
,testGroup "multi-unit" (multiTests "multi-unit")
4656
,testGroup "sub-directory" [simpleSubDirectoryTest]
@@ -174,6 +184,218 @@ simpleMultiTest3 variant =
174184
checkDefs locs (pure [fooL])
175185
expectNoMoreDiagnostics 0.5
176186

187+
runRegressionMultiOpenAThenB :: FilePath -> Session ()
188+
runRegressionMultiOpenAThenB dir = do
189+
let aPath = dir </> "a/A.hs"
190+
bPath = dir </> "b/B.hs"
191+
adoc <- openDoc aPath "haskell"
192+
bdoc <- openDoc bPath "haskell"
193+
_ <- waitForBuildQueue
194+
[aRes, bRes] <- waitForTypeChecksBatched [adoc, bdoc]
195+
liftIO $ assertBool "A should typecheck" (ideResultSuccess aRes)
196+
liftIO $ assertBool "B should typecheck" (ideResultSuccess bRes)
197+
locs <- getDefinitions bdoc (Position 2 7)
198+
let fooL = mkL (adoc ^. L.uri) 2 0 2 3
199+
checkDefs locs (pure [fooL])
200+
expectNoMoreDiagnostics 0.5
201+
202+
runRegressionMultiOpenBThenA :: FilePath -> Session ()
203+
runRegressionMultiOpenBThenA dir = do
204+
let aPath = dir </> "a/A.hs"
205+
bPath = dir </> "b/B.hs"
206+
bdoc <- openDoc bPath "haskell"
207+
adoc <- openDoc aPath "haskell"
208+
_ <- waitForBuildQueue
209+
[bRes, aRes] <- waitForTypeChecksBatched [bdoc, adoc]
210+
liftIO $ assertBool "B should typecheck" (ideResultSuccess bRes)
211+
liftIO $ assertBool "A should typecheck" (ideResultSuccess aRes)
212+
locs <- getDefinitions bdoc (Position 2 7)
213+
let TextDocumentIdentifier auri = adoc
214+
let fooL = mkL auri 2 0 2 3
215+
checkDefs locs (pure [fooL])
216+
expectNoMoreDiagnostics 0.5
217+
218+
runRegressionMultiOpenBThenAThenC :: FilePath -> Session ()
219+
runRegressionMultiOpenBThenAThenC dir = do
220+
let aPath = dir </> "a/A.hs"
221+
bPath = dir </> "b/B.hs"
222+
cPath = dir </> "c/C.hs"
223+
bdoc <- openDoc bPath "haskell"
224+
adoc <- openDoc aPath "haskell"
225+
cdoc <- openDoc cPath "haskell"
226+
_ <- waitForBuildQueue
227+
[bRes, aRes, cRes] <- waitForTypeChecksBatched [bdoc, adoc, cdoc]
228+
liftIO $ assertBool "B should typecheck" (ideResultSuccess bRes)
229+
liftIO $ assertBool "A should typecheck" (ideResultSuccess aRes)
230+
liftIO $ assertBool "C should typecheck" (ideResultSuccess cRes)
231+
locs <- getDefinitions cdoc (Position 2 7)
232+
let TextDocumentIdentifier auri = adoc
233+
let fooL = mkL auri 2 0 2 3
234+
checkDefs locs (pure [fooL])
235+
expectNoMoreDiagnostics 0.5
236+
237+
sendTestRequest :: TestRequest -> Session A.Value
238+
sendTestRequest req = do
239+
let method = SMethod_CustomMethod (Proxy @"test")
240+
reqId <- sendRequest method (A.toJSON req)
241+
TResponseMessage{_result} <- skipManyTill anyMessage $ responseForId method reqId
242+
case _result of
243+
Left err -> liftIO (assertFailure $ "test plugin request failed: " <> show err) >> pure A.Null
244+
Right val -> pure val
245+
246+
waitForTypeChecksBatched :: [TextDocumentIdentifier] -> Session [WaitForIdeRuleResult]
247+
waitForTypeChecksBatched docs = do
248+
let uris = map (\TextDocumentIdentifier{_uri} -> _uri) docs
249+
val <- sendTestRequest (WaitForIdeRules "TypeCheck" uris)
250+
case A.fromJSON val of
251+
A.Success res -> pure res
252+
A.Error parseErr -> liftIO (assertFailure $ "batched typecheck parse failed: " <> parseErr) >> pure []
253+
254+
batchLoadRegressionTests :: [TestTree]
255+
batchLoadRegressionTests =
256+
-- Note [Batch regression scheduling semantics]
257+
-- `didOpen` alone does not enqueue session-loader pending files.
258+
-- Pending entries come from GhcSession demand. For these tests, the `test`
259+
-- plugin uses `WaitForIdeRules` plus a pending-size barrier in session-loader
260+
-- to force all requested files into pending before load begins.
261+
[ testCase "m1-open-a-then-b-batch-pending-and-success" $
262+
runWithExtraFilesMultiComponent "multi" runRegressionMultiOpenAThenB
263+
, testCase "m2-open-b-then-a-batch-pending-and-success" $
264+
runWithExtraFilesMultiComponent "multi" runRegressionMultiOpenBThenA
265+
, testCase "m3-open-b-then-a-then-c-batch-pending-and-success" $
266+
runWithExtraFilesMultiComponent "multi" runRegressionMultiOpenBThenAThenC
267+
, testCase "f1-batch-pending-failure-isolates-broken-file" $
268+
runWithExtraFilesMultiComponent "multi" regressionBatchFailureIsolatesBrokenFile
269+
, testCase "f2-failed-file-keeps-failing-until-cradle-fix" $
270+
runWithExtraFilesMultiComponent "multi" regressionFailedFileKeepsFailingUntilFix
271+
, testCase "r1-failed-file-recovers-after-cradle-fix" $
272+
runWithExtraFilesMultiComponent "multi" regressionFailedFileRecoversAfterFix
273+
, testCase "s1-no-stale-outcomes-across-restart-paths" $
274+
runWithExtraFilesMultiComponent "multi" regressionNoStaleOutcomesOnRestart
275+
]
276+
277+
runWithExtraFilesMultiComponent :: String -> (FilePath -> Session a) -> IO a
278+
runWithExtraFilesMultiComponent dirName action = do
279+
let vfs = mkIdeTestFs [copyDir dirName]
280+
lspConfig :: Config
281+
lspConfig = def { sessionLoading = PreferMultiComponentLoading }
282+
conf :: TestConfig ()
283+
conf = def
284+
{ testPluginDescriptor = dummyPlugin
285+
, testDirLocation = Right vfs
286+
, testConfigCaps = lspTestCaps
287+
, testShiftRoot = True
288+
, testDisableKick = True
289+
, testLspConfig = lspConfig
290+
}
291+
runSessionWithTestConfig conf action
292+
293+
brokenMultiHieYaml :: T.Text
294+
brokenMultiHieYaml = T.unlines
295+
[ "cradle:"
296+
, " cabal:"
297+
, " - path: \"./a\""
298+
, " component: \"lib:a\""
299+
, " - path: \"./b\""
300+
, " component: \"lib:does-not-exist\""
301+
, " - path: \"./c\""
302+
, " component: \"lib:c\""
303+
]
304+
305+
writeBrokenMultiHieYaml :: FilePath -> Session ()
306+
writeBrokenMultiHieYaml dir =
307+
liftIO $ atomicFileWriteStringUTF8 (dir </> "hie.yaml") (T.unpack brokenMultiHieYaml)
308+
309+
notifyHieYamlChanged :: FilePath -> Session ()
310+
notifyHieYamlChanged dir =
311+
sendNotification SMethod_WorkspaceDidChangeWatchedFiles $ DidChangeWatchedFilesParams
312+
[FileEvent (filePathToUri $ dir </> "hie.yaml") FileChangeType_Changed]
313+
314+
assertTypeCheckSuccess :: TextDocumentIdentifier -> String -> Session ()
315+
assertTypeCheckSuccess doc msg = do
316+
WaitForIdeRuleResult {..} <- waitForAction "TypeCheck" doc
317+
liftIO $ assertBool msg ideResultSuccess
318+
319+
assertTypeCheckFailure :: TextDocumentIdentifier -> String -> Session ()
320+
assertTypeCheckFailure doc msg = do
321+
WaitForIdeRuleResult {..} <- waitForAction "TypeCheck" doc
322+
liftIO $ assertBool msg (not ideResultSuccess)
323+
324+
regressionBatchFailureIsolatesBrokenFile :: FilePath -> Session ()
325+
regressionBatchFailureIsolatesBrokenFile dir = do
326+
writeBrokenMultiHieYaml dir
327+
let aPath = dir </> "a/A.hs"
328+
bPath = dir </> "b/B.hs"
329+
adoc <- openDoc aPath "haskell"
330+
bdoc <- openDoc bPath "haskell"
331+
_ <- waitForBuildQueue
332+
[aRes, bRes] <- waitForTypeChecksBatched [adoc, bdoc]
333+
liftIO $ assertBool "A should typecheck when B cradle mapping is broken" (ideResultSuccess aRes)
334+
liftIO $ assertBool "B should fail with a broken cradle mapping" (not $ ideResultSuccess bRes)
335+
336+
regressionFailedFileKeepsFailingUntilFix :: FilePath -> Session ()
337+
regressionFailedFileKeepsFailingUntilFix dir = do
338+
writeBrokenMultiHieYaml dir
339+
let aPath = dir </> "a/A.hs"
340+
bPath = dir </> "b/B.hs"
341+
cPath = dir </> "c/C.hs"
342+
bdoc <- openDoc bPath "haskell"
343+
assertTypeCheckFailure bdoc "B should fail with broken cradle mapping"
344+
345+
bSource <- liftIO $ readFileUtf8 bPath
346+
changeDoc bdoc
347+
[TextDocumentContentChangeEvent . InR . TextDocumentContentChangeWholeDocument $ bSource <> "\n"]
348+
assertTypeCheckFailure bdoc "B should keep failing until the cradle is fixed"
349+
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"
354+
355+
regressionFailedFileRecoversAfterFix :: FilePath -> Session ()
356+
regressionFailedFileRecoversAfterFix dir = do
357+
let hiePath = dir </> "hie.yaml"
358+
bPath = dir </> "b/B.hs"
359+
validHie <- liftIO $ readFileUtf8 hiePath
360+
writeBrokenMultiHieYaml dir
361+
362+
bdoc <- openDoc bPath "haskell"
363+
assertTypeCheckFailure bdoc "B should fail before fixing the cradle"
364+
365+
liftIO $ atomicFileWriteStringUTF8 hiePath (T.unpack validHie)
366+
notifyHieYamlChanged dir
367+
368+
bSource <- liftIO $ readFileUtf8 bPath
369+
changeDoc bdoc
370+
[TextDocumentContentChangeEvent . InR . TextDocumentContentChangeWholeDocument $ bSource <> "\n"]
371+
assertTypeCheckSuccess bdoc "B should recover after restoring the cradle"
372+
373+
regressionNoStaleOutcomesOnRestart :: FilePath -> Session ()
374+
regressionNoStaleOutcomesOnRestart dir = do
375+
let hiePath = dir </> "hie.yaml"
376+
aPath = dir </> "a/A.hs"
377+
bPath = dir </> "b/B.hs"
378+
cPath = dir </> "c/C.hs"
379+
validHie <- liftIO $ readFileUtf8 hiePath
380+
writeBrokenMultiHieYaml dir
381+
382+
bdoc <- openDoc bPath "haskell"
383+
assertTypeCheckFailure bdoc "B should fail before cradle fix"
384+
385+
adoc <- openDoc aPath "haskell"
386+
assertTypeCheckSuccess adoc "A should remain healthy while B is broken"
387+
388+
liftIO $ atomicFileWriteStringUTF8 hiePath (T.unpack validHie)
389+
notifyHieYamlChanged dir
390+
391+
cdoc <- openDoc cPath "haskell"
392+
assertTypeCheckSuccess cdoc "C should typecheck after cradle restart"
393+
394+
bSource <- liftIO $ readFileUtf8 bPath
395+
changeDoc bdoc
396+
[TextDocumentContentChangeEvent . InR . TextDocumentContentChangeWholeDocument $ bSource <> "\n"]
397+
assertTypeCheckSuccess bdoc "B should not keep stale failure after cradle restart"
398+
177399
-- Like simpleMultiTest but open the files in component 'a' in a separate session
178400
simpleMultiDefTest :: FilePath -> TestTree
179401
simpleMultiDefTest variant = ignoreForWindows $ testCase testName $

ghcide/ghcide.cabal

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,7 +182,9 @@ library
182182
Development.IDE.Plugin.Test
183183
Development.IDE.Plugin.TypeLenses
184184
Development.IDE.Session
185+
Development.IDE.Session.Dependency
185186
Development.IDE.Session.Diagnostics
187+
Development.IDE.Session.Ghc
186188
Development.IDE.Session.Implicit
187189
Development.IDE.Spans.AtPoint
188190
Development.IDE.Spans.Common
@@ -206,6 +208,7 @@ library
206208
Development.IDE.GHC.CPP
207209
Development.IDE.GHC.Warnings
208210
Development.IDE.Types.Action
211+
Development.IDE.Session.OrderedSet
209212

210213
if flag(pedantic)
211214
ghc-options:

0 commit comments

Comments
 (0)