@@ -5,6 +5,7 @@ module Test.Hls.Util
55 , dummyLspFuncs
66 , expectCodeAction
77 , expectDiagnostic
8+ , expectNoMoreDiagnostics
89 , flushStackEnvironment
910 , fromAction
1011 , fromCommand
@@ -13,50 +14,59 @@ module Test.Hls.Util
1314 , hlsCommand
1415 , hlsCommandExamplePlugin
1516 , hlsCommandVomit
17+ , ignoreForGhcVersions
1618 , inspectCodeAction
1719 , inspectCommand
1820 , inspectDiagnostic
21+ , knownBrokenForGhcVersions
1922 , logConfig
2023 , logFilePath
2124 , noLogConfig
2225 , setupBuildToolFiles
2326 , waitForDiagnosticsFrom
2427 , waitForDiagnosticsFromSource
28+ , waitForDiagnosticsFromSourceWithTimeout
2529 , withFileLogging
2630 , withCurrentDirectoryInTmp
2731 )
2832where
2933
3034import Control.Monad
31- import Control.Applicative.Combinators (skipManyTill )
35+ import Control.Monad.IO.Class
36+ import Control.Applicative.Combinators (skipManyTill , (<|>) )
3237import Control.Lens ((^.) )
3338import Data.Default
3439import Data.List (intercalate )
3540import Data.List.Extra (find )
3641import Data.Maybe
3742import qualified Data.Text as T
3843import Language.Haskell.LSP.Core
44+ import Language.Haskell.LSP.Messages (FromServerMessage (NotLogMessage ))
3945import Language.Haskell.LSP.Types
40- import qualified Language.Haskell.LSP.Test as T
46+ import qualified Language.Haskell.LSP.Test as Test
4147import qualified Language.Haskell.LSP.Types.Lens as L
4248import qualified Language.Haskell.LSP.Types.Capabilities as C
4349import System.Directory
4450import System.Environment
51+ import System.Time.Extra (Seconds , sleep )
4552import System.FilePath
4653import qualified System.Log.Logger as L
4754import System.IO.Temp
4855import System.IO.Unsafe
4956import 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 )
5161import Text.Blaze.Renderer.String (renderMarkup )
5262import Text.Blaze.Internal hiding (null )
5363
5464
55- noLogConfig :: T . SessionConfig
56- noLogConfig = T . defaultConfig { T . logMessages = False }
65+ noLogConfig :: Test . SessionConfig
66+ noLogConfig = Test . defaultConfig { Test . logMessages = False }
5767
58- logConfig :: T . SessionConfig
59- logConfig = T . defaultConfig { T . logMessages = True }
68+ logConfig :: Test . SessionConfig
69+ logConfig = Test . defaultConfig { Test . logMessages = True }
6070
6171codeActionSupportCaps :: C. ClientCapabilities
6272codeActionSupportCaps = def { C. _textDocument = Just textDocumentCaps }
@@ -127,6 +137,16 @@ ghcVersion = GHC86
127137ghcVersion = GHC84
128138#endif
129139
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+
130150logFilePath :: String
131151logFilePath = " hls-" ++ show ghcVersion ++ " .log"
132152
@@ -330,17 +350,17 @@ inspectCommand cars s = fromCommand <$> onMatch cars predicate err
330350 predicate _ = False
331351 err = " expected code action matching '" ++ show s ++ " ' but did not find one"
332352
333- waitForDiagnosticsFrom :: TextDocumentIdentifier -> T . Session [Diagnostic ]
353+ waitForDiagnosticsFrom :: TextDocumentIdentifier -> Test . Session [Diagnostic ]
334354waitForDiagnosticsFrom doc = do
335- diagsNot <- skipManyTill T . anyMessage T . message :: T . Session PublishDiagnosticsNotification
355+ diagsNot <- skipManyTill Test . anyMessage Test . message :: Test . Session PublishDiagnosticsNotification
336356 let (List diags) = diagsNot ^. L. params . L. diagnostics
337357 if doc ^. L. uri /= diagsNot ^. L. params . L. uri
338358 then waitForDiagnosticsFrom doc
339359 else return diags
340360
341- waitForDiagnosticsFromSource :: TextDocumentIdentifier -> String -> T . Session [Diagnostic ]
361+ waitForDiagnosticsFromSource :: TextDocumentIdentifier -> String -> Test . Session [Diagnostic ]
342362waitForDiagnosticsFromSource doc src = do
343- diagsNot <- skipManyTill T . anyMessage T . message :: T . Session PublishDiagnosticsNotification
363+ diagsNot <- skipManyTill Test . anyMessage Test . message :: Test . Session PublishDiagnosticsNotification
344364 let (List diags) = diagsNot ^. L. params . L. diagnostics
345365 let res = filter matches diags
346366 if doc ^. L. uri /= diagsNot ^. L. params . L. uri || null res
@@ -349,3 +369,49 @@ waitForDiagnosticsFromSource doc src = do
349369 where
350370 matches :: Diagnostic -> Bool
351371 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