packages feed

hls-test-utils 1.3.0.0 → 1.4.0.0

raw patch · 3 files changed

+93/−84 lines, 3 filesdep ~ghcidedep ~hls-plugin-apidep ~lspPVP ok

version bump matches the API change (PVP)

Dependency ranges changed: ghcide, hls-plugin-api, lsp, lsp-types

API changes (from Hackage documentation)

- Test.Hls: normalizedFilePath :: NormalizedUri -> FilePath -> NormalizedFilePath
+ Test.Hls: ErrorCodeCustom :: Int32 -> ErrorCode
+ Test.Hls: RequestFailed :: ErrorCode
+ Test.Hls: ServerCancelled :: ErrorCode
+ Test.Hls: SttNamespace :: SemanticTokenTypes
+ Test.Hls: emptyNormalizedFilePath :: NormalizedFilePath
+ Test.Hls: isSubrangeOf :: Range -> Range -> Bool
+ Test.Hls: positionInRange :: Position -> Range -> Bool
+ Test.Hls.Util: GHC94 :: GhcVersion
+ Test.Hls.Util: onlyRunForGhcVersions :: [GhcVersion] -> String -> TestTree -> TestTree
+ Test.Hls.Util: onlyWorkForGhcVersions :: (GhcVersion -> Bool) -> String -> TestTree -> TestTree
+ Test.Hls.Util: withCanonicalTempDir :: (FilePath -> IO a) -> IO a
- Test.Hls: [CustomEq] :: forall (f :: From) (t1 :: MethodType) (m1 :: Method f t1) (t2 :: MethodType) (m2 :: Method f t2). (m1 ~ ('CustomMethod :: Method f t1), m2 ~ ('CustomMethod :: Method f t2)) => {runCustomEq :: t1 ~ t2 => m1 :~~: m2} -> CustomEq m1 m2
+ Test.Hls: [CustomEq] :: forall (f :: From) (t1 :: MethodType) (m1 :: Method f t1) (t2 :: MethodType) (m2 :: Method f t2). (m1 ~ ('CustomMethod :: Method f t1), m2 ~ ('CustomMethod :: Method f t2)) => (t1 ~ t2 => m1 :~~: m2) -> CustomEq m1 m2
- Test.Hls: [IsClientEither] :: forall (t :: MethodType). ClientNotOrReq ('CustomMethod :: Method 'FromClient t)
+ Test.Hls: [IsClientEither] :: forall {t :: MethodType}. ClientNotOrReq ('CustomMethod :: Method 'FromClient t)
- Test.Hls: [IsServerEither] :: forall (t :: MethodType). ServerNotOrReq ('CustomMethod :: Method 'FromServer t)
+ Test.Hls: [IsServerEither] :: forall {t :: MethodType}. ServerNotOrReq ('CustomMethod :: Method 'FromServer t)
- Test.Hls: [SCancelRequest] :: forall (f :: From). SMethod ('CancelRequest :: Method f 'Notification)
+ Test.Hls: [SCancelRequest] :: forall {f :: From}. SMethod ('CancelRequest :: Method f 'Notification)
- Test.Hls: [SCustomMethod] :: forall (f :: From) (t :: MethodType). Text -> SMethod ('CustomMethod :: Method f t)
+ Test.Hls: [SCustomMethod] :: forall {f :: From} {t :: MethodType}. Text -> SMethod ('CustomMethod :: Method f t)
- Test.Hls: [SomeLspId] :: forall (f :: From) (m :: Method f 'Request). !LspId m -> SomeLspId
+ Test.Hls: [SomeLspId] :: forall {f :: From} (m :: Method f 'Request). !LspId m -> SomeLspId
- Test.Hls: [SomeMethod] :: forall (f :: From) (t :: MethodType) (m :: Method f t). SMethod m -> SomeMethod
+ Test.Hls: [SomeMethod] :: forall {f :: From} {t :: MethodType} (m :: Method f t). SMethod m -> SomeMethod
- Test.Hls: [WorkspaceSemanticTokensRefresh] :: Method 'FromClient 'Request
+ Test.Hls: [WorkspaceSemanticTokensRefresh] :: Method 'FromServer 'Request
- Test.Hls: clientMethodJSON :: forall (t :: MethodType) (m :: Method 'FromClient t) x. SClientMethod m -> (ToJSON (ClientMessage m) => x) -> x
+ Test.Hls: clientMethodJSON :: forall {t :: MethodType} (m :: Method 'FromClient t) x. SClientMethod m -> (ToJSON (ClientMessage m) => x) -> x
- Test.Hls: mEqClient :: forall (t1 :: MethodType) (t2 :: MethodType) (m1 :: Method 'FromClient t1) (m2 :: Method 'FromClient t2). SClientMethod m1 -> SClientMethod m2 -> Maybe (Either (CustomEq m1 m2) (m1 :~~: m2))
+ Test.Hls: mEqClient :: forall {t1 :: MethodType} {t2 :: MethodType} (m1 :: Method 'FromClient t1) (m2 :: Method 'FromClient t2). SClientMethod m1 -> SClientMethod m2 -> Maybe (Either (CustomEq m1 m2) (m1 :~~: m2))
- Test.Hls: mEqServer :: forall (t1 :: MethodType) (t2 :: MethodType) (m1 :: Method 'FromServer t1) (m2 :: Method 'FromServer t2). SServerMethod m1 -> SServerMethod m2 -> Maybe (Either (CustomEq m1 m2) (m1 :~~: m2))
+ Test.Hls: mEqServer :: forall {t1 :: MethodType} {t2 :: MethodType} (m1 :: Method 'FromServer t1) (m2 :: Method 'FromServer t2). SServerMethod m1 -> SServerMethod m2 -> Maybe (Either (CustomEq m1 m2) (m1 :~~: m2))
- Test.Hls: regHelper :: forall (t :: MethodType) (m :: Method 'FromClient t) x. SMethod m -> ((Show (RegistrationOptions m), ToJSON (RegistrationOptions m), FromJSON (RegistrationOptions m)) => x) -> x
+ Test.Hls: regHelper :: forall {t :: MethodType} (m :: Method 'FromClient t) x. SMethod m -> ((Show (RegistrationOptions m), ToJSON (RegistrationOptions m), FromJSON (RegistrationOptions m)) => x) -> x
- Test.Hls: serverMethodJSON :: forall (t :: MethodType) (m :: Method 'FromServer t) x. SServerMethod m -> (ToJSON (ServerMessage m) => x) -> x
+ Test.Hls: serverMethodJSON :: forall {t :: MethodType} (m :: Method 'FromServer t) x. SServerMethod m -> (ToJSON (ServerMessage m) => x) -> x
- Test.Hls: splitClientMethod :: forall (t :: MethodType) (m :: Method 'FromClient t). SClientMethod m -> ClientNotOrReq m
+ Test.Hls: splitClientMethod :: forall {t :: MethodType} (m :: Method 'FromClient t). SClientMethod m -> ClientNotOrReq m
- Test.Hls: splitServerMethod :: forall (t :: MethodType) (m :: Method 'FromServer t). SServerMethod m -> ServerNotOrReq m
+ Test.Hls: splitServerMethod :: forall {t :: MethodType} (m :: Method 'FromServer t). SServerMethod m -> ServerNotOrReq m

Files

hls-test-utils.cabal view
@@ -1,6 +1,6 @@ cabal-version: 2.4 name:          hls-test-utils-version:       1.3.0.0+version:       1.4.0.0 synopsis:      Utilities used in the tests of Haskell Language Server description:   Please see the README on GitHub at <https://github.com/haskell/haskell-language-server#readme>@@ -41,13 +41,13 @@     , directory     , extra     , filepath-    , ghcide                  ^>=1.6 || ^>=1.7+    , ghcide                  ^>=1.8     , hls-graph-    , hls-plugin-api          ^>=1.3 || ^>=1.4+    , hls-plugin-api          ^>=1.5     , lens-    , lsp                     ^>=1.4+    , lsp                     ^>=1.6.0.0     , lsp-test                ^>=0.14-    , lsp-types               ^>=1.4.0.1+    , lsp-types               ^>=1.6.0.0     , tasty     , tasty-expected-failure     , tasty-golden
src/Test/Hls.hs view
@@ -47,8 +47,8 @@ import qualified Data.Aeson                      as A import           Data.ByteString.Lazy            (ByteString) import           Data.Default                    (def)-import           Data.Maybe                      (fromMaybe) import qualified Data.Map                        as M+import           Data.Maybe                      (fromMaybe) import qualified Data.Text                       as T import qualified Data.Text.Lazy                  as TL import qualified Data.Text.Lazy.Encoding         as TL@@ -69,7 +69,8 @@ import           Development.IDE.Types.Options import           GHC.IO.Handle import           GHC.Stack                       (emptyCallStack)-import           Ide.Plugin.Config               (Config, formattingProvider, PluginConfig, plugins)+import           Ide.Plugin.Config               (Config, PluginConfig,+                                                  formattingProvider, plugins) import           Ide.PluginUtils                 (idePluginsToPluginDesc,                                                   pluginDescToIdePlugins) import           Ide.Types@@ -188,57 +189,56 @@   Session a ->   IO a runSessionWithServer' plugins conf sconf caps root s = withLock lock $ keepCurrentDirectory $ do-  (inR, inW) <- createPipe-  (outR, outW) <- createPipe+    (inR, inW) <- createPipe+    (outR, outW) <- createPipe -  docWithPriorityRecorder <- makeDefaultStderrRecorder Nothing Debug+    docWithPriorityRecorder <- makeDefaultStderrRecorder Nothing Debug -  logStdErr <- fromMaybe "0" <$> lookupEnv "LSP_TEST_LOG_STDERR"+    logStdErr <- fromMaybe "0" <$> lookupEnv "LSP_TEST_LOG_STDERR" -  let-    docWithFilteredPriorityRecorder@Recorder{ logger_ } =-      if logStdErr == "0" then mempty-      else cfilter (\WithPriority{ priority } -> priority >= Debug) docWithPriorityRecorder+    let+        docWithFilteredPriorityRecorder@Recorder{ logger_ } =+            if logStdErr == "0" then mempty+            else cfilter (\WithPriority{ priority } -> priority >= Debug) docWithPriorityRecorder -    -- exists until old logging style is phased out-    logger = Logger $ \p m -> logger_ (WithPriority p emptyCallStack (pretty m))+        -- exists until old logging style is phased out+        logger = Logger $ \p m -> logger_ (WithPriority p emptyCallStack (pretty m)) -    recorder = cmapWithPrio pretty docWithFilteredPriorityRecorder+        recorder = cmapWithPrio pretty docWithFilteredPriorityRecorder -    arguments@Arguments{ argsHlsPlugins, argsIdeOptions, argsLogger } = defaultArguments (cmapWithPrio LogIDEMain recorder) logger+        arguments@Arguments{ argsHlsPlugins, argsIdeOptions, argsLogger } = defaultArguments (cmapWithPrio LogIDEMain recorder) logger -    hlsPlugins =-      idePluginsToPluginDesc argsHlsPlugins-      ++ [Test.blockCommandDescriptor "block-command", Test.plugin]-      ++ plugins-    ideOptions = \config ghcSession ->-      let defIdeOptions = argsIdeOptions config ghcSession-      in defIdeOptions-           { optTesting = IdeTesting True-           , optCheckProject = pure False-           }+        hlsPlugins =+            plugins+            ++ [Test.blockCommandDescriptor "block-command", Test.plugin]+            ++ idePluginsToPluginDesc argsHlsPlugins+        ideOptions config ghcSession =+            let defIdeOptions = argsIdeOptions config ghcSession+            in defIdeOptions+                    { optTesting = IdeTesting True+                    , optCheckProject = pure False+                    } -  server <--    async $-      Ghcide.defaultMain-        (cmapWithPrio LogIDEMain recorder)-        arguments-          { argsHandleIn = pure inR-          , argsHandleOut = pure outW-          , argsDefaultHlsConfig = conf-          , argsLogger = argsLogger-          , argsIdeOptions = ideOptions-          , argsHlsPlugins = pluginDescToIdePlugins hlsPlugins }+    server <- async $+        Ghcide.defaultMain (cmapWithPrio LogIDEMain recorder)+            arguments+                { argsHandleIn = pure inR+                , argsHandleOut = pure outW+                , argsDefaultHlsConfig = conf+                , argsLogger = argsLogger+                , argsIdeOptions = ideOptions+                , argsHlsPlugins = pluginDescToIdePlugins hlsPlugins+                } -  x <- runSessionWithHandles inW outR sconf caps root s-  hClose inW-  timeout 3 (wait server) >>= \case-    Just () -> pure ()-    Nothing -> do-      putStrLn "Server does not exit in 3s, canceling the async task..."-      (t, _) <- duration $ cancel server-      putStrLn $ "Finishing canceling (took " <> showDuration t <> "s)"-  pure x+    x <- runSessionWithHandles inW outR sconf caps root s+    hClose inW+    timeout 3 (wait server) >>= \case+        Just () -> pure ()+        Nothing -> do+            putStrLn "Server does not exit in 3s, canceling the async task..."+            (t, _) <- duration $ cancel server+            putStrLn $ "Finishing canceling (took " <> showDuration t <> "s)"+    pure x  -- | Wait for the next progress end step waitForProgressDone :: Session ()
src/Test/Hls/Util.hs view
@@ -3,7 +3,6 @@ {-# LANGUAGE FlexibleContexts      #-} {-# LANGUAGE GADTs                 #-} {-# LANGUAGE MultiParamTypeClasses #-}-{-# LANGUAGE NamedFieldPuns        #-} {-# LANGUAGE OverloadedStrings     #-} {-# LANGUAGE TypeOperators         #-} module Test.Hls.Util@@ -25,12 +24,14 @@     , noLiteralCaps     , ignoreForGhcVersions     , ignoreInEnv+    , onlyRunForGhcVersions     , inspectCodeAction     , inspectCommand     , inspectDiagnostic     , knownBrokenOnWindows     , knownBrokenForGhcVersions     , knownBrokenInEnv+    , onlyWorkForGhcVersions     , setupBuildToolFiles     , SymbolLocation     , waitForDiagnosticsFrom@@ -38,12 +39,13 @@     , waitForDiagnosticsFromSourceWithTimeout     , withCurrentDirectoryInTmp     , withCurrentDirectoryInTmp'+    , withCanonicalTempDir   ) where  import           Control.Applicative.Combinators (skipManyTill, (<|>)) import           Control.Exception               (catch, throwIO)-import           Control.Lens                    ((^.))+import           Control.Lens                    ((&), (?~), (^.)) import           Control.Monad import           Control.Monad.IO.Class import qualified Data.Aeson                      as A@@ -52,16 +54,18 @@ import           Data.List.Extra                 (find) import qualified Data.Set                        as Set import qualified Data.Text                       as T-import           Development.IDE                 (GhcVersion(..), ghcVersion)+import           Development.IDE                 (GhcVersion (..), ghcVersion) import qualified Language.LSP.Test               as Test import           Language.LSP.Types              hiding (Reason (..)) import qualified Language.LSP.Types.Capabilities as C+import           Language.LSP.Types.Lens         (textDocument) import qualified Language.LSP.Types.Lens         as L import           System.Directory import           System.Environment import           System.FilePath-import           System.IO.Temp import           System.Info.Extra               (isMac, isWindows)+import qualified System.IO.Extra+import           System.IO.Temp import           System.Time.Extra               (Seconds, sleep) import           Test.Tasty                      (TestTree) import           Test.Tasty.ExpectedFailure      (expectFailBecause,@@ -70,13 +74,13 @@                                                   (@?=))  noLiteralCaps :: C.ClientCapabilities-noLiteralCaps = def { C._textDocument = Just textDocumentCaps }+noLiteralCaps = def & textDocument ?~ textDocumentCaps   where     textDocumentCaps = def { C._codeAction = Just codeActionCaps }     codeActionCaps = CodeActionClientCapabilities (Just True) Nothing Nothing Nothing Nothing Nothing Nothing  codeActionSupportCaps :: C.ClientCapabilities-codeActionSupportCaps = def { C._textDocument = Just textDocumentCaps }+codeActionSupportCaps = def & textDocument ?~ textDocumentCaps   where     textDocumentCaps = def { C._codeAction = Just codeActionCaps }     codeActionCaps = CodeActionClientCapabilities (Just True) (Just literalSupport) (Just True) Nothing Nothing Nothing Nothing@@ -148,6 +152,20 @@ ignoreForGhcVersions :: [GhcVersion] -> String -> TestTree -> TestTree ignoreForGhcVersions vers = ignoreInEnv (map GhcVer vers) +-- | Mark as broken if GHC does not match only work versions.+onlyWorkForGhcVersions :: (GhcVersion -> Bool) -> String -> TestTree -> TestTree+onlyWorkForGhcVersions p reason =+    if p ghcVersion+        then id+        else expectFailBecause reason++-- | Ignore the test if GHC does not match only work versions.+onlyRunForGhcVersions :: [GhcVersion] -> String -> TestTree -> TestTree+onlyRunForGhcVersions vers =+    if ghcVersion `elem` vers+    then const id+    else ignoreTestBecause+ -- ---------------------------------------------------------------------  hieYamlCradleDirectContents :: String@@ -237,7 +255,7 @@ onMatch as predicate err = maybe (fail err) return (find predicate as)  noMatch :: [a] -> (a -> Bool) -> String -> IO ()-noMatch [] _ _ = pure ()+noMatch [] _ _           = pure () noMatch as predicate err = bool (pure ()) (fail err) (any predicate as)  inspectDiagnostic :: [Diagnostic] -> [T.Text] -> IO Diagnostic@@ -279,16 +297,7 @@        else return diags  waitForDiagnosticsFromSource :: TextDocumentIdentifier -> String -> Test.Session [Diagnostic]-waitForDiagnosticsFromSource doc src = do-    diagsNot <- skipManyTill Test.anyMessage (Test.message STextDocumentPublishDiagnostics)-    let (List diags) = diagsNot ^. L.params . L.diagnostics-    let res = filter matches diags-    if doc ^. L.uri /= diagsNot ^. L.params . L.uri || null res-       then waitForDiagnosticsFromSource doc src-       else return res-  where-    matches :: Diagnostic -> Bool-    matches d = d ^. L.source == Just (T.pack src)+waitForDiagnosticsFromSource = waitForDiagnosticsFromSourceWithTimeout 5  -- | wait for @timeout@ seconds and report an assertion failure -- if any diagnostic messages arrive in that period@@ -304,38 +313,31 @@ -- If timeout is 0 it will wait until the session timeout waitForDiagnosticsFromSourceWithTimeout :: Seconds -> TextDocumentIdentifier -> String -> Test.Session [Diagnostic] waitForDiagnosticsFromSourceWithTimeout timeout document source = do-    when (timeout > 0) $ do+    when (timeout > 0) $         -- Give any further diagnostic messages time to arrive.         liftIO $ sleep timeout         -- Send a dummy message to provoke a response from the server.         -- This guarantees that we have at least one message to         -- process, so message won't block or timeout.-        void $ Test.sendNotification (SCustomMethod "non-existent-method") A.Null-    handleMessages+    testId <- Test.sendRequest (SCustomMethod "test") A.Null+    handleMessages testId   where     matches :: Diagnostic -> Bool     matches d = d ^. L.source == Just (T.pack source) -    handleMessages = handleDiagnostic <|> handleCustomMethodResponse <|> ignoreOthers-    handleDiagnostic = do+    handleMessages testId = handleDiagnostic testId <|> handleCustomMethodResponse testId <|> ignoreOthers testId+    handleDiagnostic testId = do         diagsNot <- Test.message STextDocumentPublishDiagnostics         let fileUri = diagsNot ^. L.params . L.uri             (List diags) = diagsNot ^. L.params . L.diagnostics             res = filter matches diags         if fileUri == document ^. L.uri && not (null res)-            then return diags else handleMessages-    handleCustomMethodResponse =-        -- the CustomClientMethod triggers a RspCustomServer-        -- handle that and then exit-        void (Test.satisfyMaybe responseForNonExistentMethod) >> return []--    responseForNonExistentMethod :: FromServerMessage -> Maybe FromServerMessage-    responseForNonExistentMethod notif-        | FromServerMess SWindowLogMessage logMsg <- notif,-          "non-existent-method" `T.isInfixOf` (logMsg ^. L.params . L.message)  = Just notif-        | otherwise = Nothing+            then return res else handleMessages testId+    handleCustomMethodResponse testId = do+        _ <- Test.responseForId (SCustomMethod "test") testId+        pure [] -    ignoreOthers = void Test.anyMessage >> handleMessages+    ignoreOthers testId = void Test.anyMessage >> handleMessages testId  failIfSessionTimeout :: IO a -> IO a failIfSessionTimeout action = action `catch` errorHandler@@ -362,9 +364,16 @@  -- --------------------------------------------------------------------- getCompletionByLabel :: MonadIO m => T.Text -> [CompletionItem] -> m CompletionItem-getCompletionByLabel desiredLabel compls = +getCompletionByLabel desiredLabel compls =     case find (\c -> c ^. L.label == desiredLabel) compls of         Just c -> pure c         Nothing -> liftIO . assertFailure $             "Completion with label " <> show desiredLabel             <> " not found in " <> show (fmap (^. L.label) compls)++-- ---------------------------------------------------------------------+-- Run with a canonicalized temp dir+withCanonicalTempDir :: (FilePath -> IO a) -> IO a+withCanonicalTempDir f = System.IO.Extra.withTempDir $ \dir -> do+  dir' <- canonicalizePath dir+  f dir'