@@ -5,6 +5,7 @@ module Test.Hls.Util
5
5
, dummyLspFuncs
6
6
, expectCodeAction
7
7
, expectDiagnostic
8
+ , expectNoMoreDiagnostics
8
9
, flushStackEnvironment
9
10
, fromAction
10
11
, fromCommand
@@ -13,50 +14,59 @@ module Test.Hls.Util
13
14
, hlsCommand
14
15
, hlsCommandExamplePlugin
15
16
, hlsCommandVomit
17
+ , ignoreForGhcVersions
16
18
, inspectCodeAction
17
19
, inspectCommand
18
20
, inspectDiagnostic
21
+ , knownBrokenForGhcVersions
19
22
, logConfig
20
23
, logFilePath
21
24
, noLogConfig
22
25
, setupBuildToolFiles
23
26
, waitForDiagnosticsFrom
24
27
, waitForDiagnosticsFromSource
28
+ , waitForDiagnosticsFromSourceWithTimeout
25
29
, withFileLogging
26
30
, withCurrentDirectoryInTmp
27
31
)
28
32
where
29
33
30
34
import Control.Monad
31
- import Control.Applicative.Combinators (skipManyTill )
35
+ import Control.Monad.IO.Class
36
+ import Control.Applicative.Combinators (skipManyTill , (<|>) )
32
37
import Control.Lens ((^.) )
33
38
import Data.Default
34
39
import Data.List (intercalate )
35
40
import Data.List.Extra (find )
36
41
import Data.Maybe
37
42
import qualified Data.Text as T
38
43
import Language.Haskell.LSP.Core
44
+ import Language.Haskell.LSP.Messages (FromServerMessage (NotLogMessage ))
39
45
import Language.Haskell.LSP.Types
40
- import qualified Language.Haskell.LSP.Test as T
46
+ import qualified Language.Haskell.LSP.Test as Test
41
47
import qualified Language.Haskell.LSP.Types.Lens as L
42
48
import qualified Language.Haskell.LSP.Types.Capabilities as C
43
49
import System.Directory
44
50
import System.Environment
51
+ import System.Time.Extra (Seconds , sleep )
45
52
import System.FilePath
46
53
import qualified System.Log.Logger as L
47
54
import System.IO.Temp
48
55
import System.IO.Unsafe
49
56
import Test.Hspec.Runner
50
- import Test.Hspec.Core.Formatters
57
+ import Test.Hspec.Core.Formatters hiding (Seconds )
58
+ import Test.Tasty (TestTree )
59
+ import Test.Tasty.ExpectedFailure (ignoreTestBecause , expectFailBecause )
60
+ import Test.Tasty.HUnit (assertFailure )
51
61
import Text.Blaze.Renderer.String (renderMarkup )
52
62
import Text.Blaze.Internal hiding (null )
53
63
54
64
55
- noLogConfig :: T . SessionConfig
56
- noLogConfig = T . defaultConfig { T . logMessages = False }
65
+ noLogConfig :: Test . SessionConfig
66
+ noLogConfig = Test . defaultConfig { Test . logMessages = False }
57
67
58
- logConfig :: T . SessionConfig
59
- logConfig = T . defaultConfig { T . logMessages = True }
68
+ logConfig :: Test . SessionConfig
69
+ logConfig = Test . defaultConfig { Test . logMessages = True }
60
70
61
71
codeActionSupportCaps :: C. ClientCapabilities
62
72
codeActionSupportCaps = def { C. _textDocument = Just textDocumentCaps }
@@ -127,6 +137,16 @@ ghcVersion = GHC86
127
137
ghcVersion = GHC84
128
138
#endif
129
139
140
+ knownBrokenForGhcVersions :: [GhcVersion ] -> String -> TestTree -> TestTree
141
+ knownBrokenForGhcVersions vers reason
142
+ | ghcVersion `elem` vers = expectFailBecause reason
143
+ | otherwise = id
144
+
145
+ ignoreForGhcVersions :: [GhcVersion ] -> String -> TestTree -> TestTree
146
+ ignoreForGhcVersions vers reason
147
+ | ghcVersion `elem` vers = ignoreTestBecause reason
148
+ | otherwise = id
149
+
130
150
logFilePath :: String
131
151
logFilePath = " hls-" ++ show ghcVersion ++ " .log"
132
152
@@ -330,17 +350,17 @@ inspectCommand cars s = fromCommand <$> onMatch cars predicate err
330
350
predicate _ = False
331
351
err = " expected code action matching '" ++ show s ++ " ' but did not find one"
332
352
333
- waitForDiagnosticsFrom :: TextDocumentIdentifier -> T . Session [Diagnostic ]
353
+ waitForDiagnosticsFrom :: TextDocumentIdentifier -> Test . Session [Diagnostic ]
334
354
waitForDiagnosticsFrom doc = do
335
- diagsNot <- skipManyTill T . anyMessage T . message :: T . Session PublishDiagnosticsNotification
355
+ diagsNot <- skipManyTill Test . anyMessage Test . message :: Test . Session PublishDiagnosticsNotification
336
356
let (List diags) = diagsNot ^. L. params . L. diagnostics
337
357
if doc ^. L. uri /= diagsNot ^. L. params . L. uri
338
358
then waitForDiagnosticsFrom doc
339
359
else return diags
340
360
341
- waitForDiagnosticsFromSource :: TextDocumentIdentifier -> String -> T . Session [Diagnostic ]
361
+ waitForDiagnosticsFromSource :: TextDocumentIdentifier -> String -> Test . Session [Diagnostic ]
342
362
waitForDiagnosticsFromSource doc src = do
343
- diagsNot <- skipManyTill T . anyMessage T . message :: T . Session PublishDiagnosticsNotification
363
+ diagsNot <- skipManyTill Test . anyMessage Test . message :: Test . Session PublishDiagnosticsNotification
344
364
let (List diags) = diagsNot ^. L. params . L. diagnostics
345
365
let res = filter matches diags
346
366
if doc ^. L. uri /= diagsNot ^. L. params . L. uri || null res
@@ -349,3 +369,49 @@ waitForDiagnosticsFromSource doc src = do
349
369
where
350
370
matches :: Diagnostic -> Bool
351
371
matches d = d ^. L. source == Just (T. pack src)
372
+
373
+ -- | wait for @timeout@ seconds and report an assertion failure
374
+ -- if any diagnostic messages arrive in that period
375
+ expectNoMoreDiagnostics :: Seconds -> TextDocumentIdentifier -> String -> Test. Session ()
376
+ expectNoMoreDiagnostics timeout doc src = do
377
+ diags <- waitForDiagnosticsFromSourceWithTimeout timeout doc src
378
+ unless (null diags) $
379
+ liftIO $ assertFailure $
380
+ " Got unexpected diagnostics for " <> show (doc ^. L. uri) <>
381
+ " got " <> show diags
382
+
383
+ -- | wait for @timeout@ seconds and return diagnostics for the given @document and @source.
384
+ -- If timeout is 0 it will wait until the session timeout
385
+ waitForDiagnosticsFromSourceWithTimeout :: Seconds -> TextDocumentIdentifier -> String -> Test. Session [Diagnostic ]
386
+ waitForDiagnosticsFromSourceWithTimeout timeout document source = do
387
+ when (timeout > 0 ) $ do
388
+ -- Give any further diagnostic messages time to arrive.
389
+ liftIO $ sleep timeout
390
+ -- Send a dummy message to provoke a response from the server.
391
+ -- This guarantees that we have at least one message to
392
+ -- process, so message won't block or timeout.
393
+ void $ Test. sendRequest (CustomClientMethod " non-existent-method" ) ()
394
+ handleMessages
395
+ where
396
+ matches :: Diagnostic -> Bool
397
+ matches d = d ^. L. source == Just (T. pack source)
398
+
399
+ handleMessages = handleDiagnostic <|> handleCustomMethodResponse <|> ignoreOthers
400
+ handleDiagnostic = do
401
+ diagsNot <- Test. message :: Test. Session PublishDiagnosticsNotification
402
+ let fileUri = diagsNot ^. L. params . L. uri
403
+ (List diags) = diagsNot ^. L. params . L. diagnostics
404
+ res = filter matches diags
405
+ if fileUri == document ^. L. uri && not (null res)
406
+ then return diags else handleMessages
407
+ handleCustomMethodResponse =
408
+ -- the CustomClientMethod triggers a RspCustomServer
409
+ -- handle that and then exit
410
+ void (Test. satisfyMaybe responseForNonExistentMethod) >> return []
411
+
412
+ responseForNonExistentMethod notif
413
+ | NotLogMessage logMsg <- notif,
414
+ " non-existent-method" `T.isInfixOf` (logMsg ^. L. params . L. message) = Just notif
415
+ | otherwise = Nothing
416
+
417
+ ignoreOthers = void Test. anyMessage >> handleMessages
0 commit comments