hls-test-utils 2.9.0.1 → 2.10.0.0
raw patch · 5 files changed
+106/−20 lines, 5 filesdep ~ghcidedep ~hls-plugin-apiPVP ok
version bump matches the API change (PVP)
Dependency ranges changed: ghcide, hls-plugin-api
API changes (from Hackage documentation)
- Test.Hls.Util: GHC92 :: GhcVersion
+ Development.IDE.Test: type ExpectedDiagnostic = (DiagnosticSeverity, Cursor, Text, Maybe Text)
+ Development.IDE.Test: type ExpectedDiagnosticWithTag = (DiagnosticSeverity, Cursor, Text, Maybe Text, Maybe DiagnosticTag)
+ Development.IDE.Test.Diagnostic: type ExpectedDiagnostic = (DiagnosticSeverity, Cursor, Text, Maybe Text)
+ Development.IDE.Test.Diagnostic: type ExpectedDiagnosticWithTag = (DiagnosticSeverity, Cursor, Text, Maybe Text, Maybe DiagnosticTag)
+ Test.Hls: Current :: BrokenBehavior
+ Test.Hls: Ideal :: BrokenBehavior
+ Test.Hls: [BrokenCurrent] :: a -> ExpectBroken 'Current a
+ Test.Hls: [BrokenIdeal] :: a -> ExpectBroken 'Ideal a
+ Test.Hls: captureKickDiagnostics :: Session () -> Session () -> Session [Diagnostic]
+ Test.Hls: data BrokenBehavior
+ Test.Hls: data ExpectBroken (k :: BrokenBehavior) a
+ Test.Hls: kick :: KnownSymbol k => Proxy k -> Session [FilePath]
+ Test.Hls: unCurrent :: ExpectBroken 'Current a -> a
+ Test.Hls.Util: GHC912 :: GhcVersion
+ Test.Hls.Util: codeActionNoInlayHintsCaps :: ClientCapabilities
+ Test.Hls.Util: inspectDiagnosticAny :: [Diagnostic] -> [Text] -> IO Diagnostic
- Development.IDE.Test: checkDiagnosticsForDoc :: HasCallStack => TextDocumentIdentifier -> [(DiagnosticSeverity, Cursor, Text)] -> [Diagnostic] -> Session ()
+ Development.IDE.Test: checkDiagnosticsForDoc :: HasCallStack => TextDocumentIdentifier -> [ExpectedDiagnostic] -> [Diagnostic] -> Session ()
- Development.IDE.Test: expectCurrentDiagnostics :: HasCallStack => TextDocumentIdentifier -> [(DiagnosticSeverity, Cursor, Text)] -> Session ()
+ Development.IDE.Test: expectCurrentDiagnostics :: HasCallStack => TextDocumentIdentifier -> [ExpectedDiagnostic] -> Session ()
- Development.IDE.Test: expectDiagnostics :: HasCallStack => [(FilePath, [(DiagnosticSeverity, Cursor, Text)])] -> Session ()
+ Development.IDE.Test: expectDiagnostics :: HasCallStack => [(FilePath, [ExpectedDiagnostic])] -> Session ()
- Development.IDE.Test: expectDiagnosticsWithTags :: HasCallStack => [(String, [(DiagnosticSeverity, Cursor, Text, Maybe DiagnosticTag)])] -> Session ()
+ Development.IDE.Test: expectDiagnosticsWithTags :: HasCallStack => [(String, [ExpectedDiagnosticWithTag])] -> Session ()
- Development.IDE.Test: requireDiagnostic :: (Foldable f, Show (f Diagnostic), HasCallStack) => f Diagnostic -> (DiagnosticSeverity, Cursor, Text, Maybe DiagnosticTag) -> Maybe ErrorMsg
+ Development.IDE.Test: requireDiagnostic :: (Foldable f, Show (f Diagnostic), HasCallStack) => f Diagnostic -> ExpectedDiagnosticWithTag -> Maybe ErrorMsg
- Development.IDE.Test.Diagnostic: requireDiagnostic :: (Foldable f, Show (f Diagnostic), HasCallStack) => f Diagnostic -> (DiagnosticSeverity, Cursor, Text, Maybe DiagnosticTag) -> Maybe ErrorMsg
+ Development.IDE.Test.Diagnostic: requireDiagnostic :: (Foldable f, Show (f Diagnostic), HasCallStack) => f Diagnostic -> ExpectedDiagnosticWithTag -> Maybe ErrorMsg
- Test.Hls: goldenWithTestConfig :: Pretty b => TestConfig b -> TestName -> FilePath -> FilePath -> FilePath -> FilePath -> (TextDocumentIdentifier -> Session ()) -> TestTree
+ Test.Hls: goldenWithTestConfig :: Pretty b => TestConfig b -> TestName -> VirtualFileTree -> FilePath -> FilePath -> FilePath -> (TextDocumentIdentifier -> Session ()) -> TestTree
Files
- hls-test-utils.cabal +3/−3
- src/Development/IDE/Test.hs +15/−10
- src/Development/IDE/Test/Diagnostic.hs +42/−2
- src/Test/Hls.hs +34/−5
- src/Test/Hls/Util.hs +12/−0
hls-test-utils.cabal view
@@ -1,6 +1,6 @@ cabal-version: 2.4 name: hls-test-utils-version: 2.9.0.1+version: 2.10.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>@@ -43,8 +43,8 @@ , directory , extra , filepath- , ghcide == 2.9.0.1- , hls-plugin-api == 2.9.0.1+ , ghcide == 2.10.0.0+ , hls-plugin-api == 2.10.0.0 , lens , lsp , lsp-test ^>=0.17
src/Development/IDE/Test.hs view
@@ -14,6 +14,8 @@ , diagnostic , expectDiagnostics , expectDiagnosticsWithTags+ , ExpectedDiagnostic+ , ExpectedDiagnosticWithTag , expectNoMoreDiagnostics , expectMessages , expectCurrentDiagnostics@@ -63,10 +65,13 @@ import System.Time.Extra import Test.Tasty.HUnit +expectedDiagnosticWithNothing :: ExpectedDiagnostic -> ExpectedDiagnosticWithTag+expectedDiagnosticWithNothing (ds, c, t, code) = (ds, c, t, code, Nothing)+ requireDiagnosticM :: (Foldable f, Show (f Diagnostic), HasCallStack) => f Diagnostic- -> (DiagnosticSeverity, Cursor, T.Text, Maybe DiagnosticTag)+ -> ExpectedDiagnosticWithTag -> Assertion requireDiagnosticM actuals expected = case requireDiagnostic actuals expected of Nothing -> pure ()@@ -114,25 +119,25 @@ -- -- Rather than trying to assert the absence of diagnostics, introduce an -- expected diagnostic (e.g. a redundant import) and assert the singleton diagnostic.-expectDiagnostics :: HasCallStack => [(FilePath, [(DiagnosticSeverity, Cursor, T.Text)])] -> Session ()+expectDiagnostics :: HasCallStack => [(FilePath, [ExpectedDiagnostic])] -> Session () expectDiagnostics = expectDiagnosticsWithTags- . map (second (map (\(ds, c, t) -> (ds, c, t, Nothing))))+ . map (second (map expectedDiagnosticWithNothing)) unwrapDiagnostic :: TServerMessage Method_TextDocumentPublishDiagnostics -> (Uri, [Diagnostic]) unwrapDiagnostic diagsNot = (diagsNot^. L.params . L.uri, diagsNot^. L.params . L.diagnostics) -expectDiagnosticsWithTags :: HasCallStack => [(String, [(DiagnosticSeverity, Cursor, T.Text, Maybe DiagnosticTag)])] -> Session ()+expectDiagnosticsWithTags :: HasCallStack => [(String, [ExpectedDiagnosticWithTag])] -> Session () expectDiagnosticsWithTags expected = do- let f = getDocUri >=> liftIO . canonicalizeUri >=> pure . toNormalizedUri+ let toSessionPath = getDocUri >=> liftIO . canonicalizeUri >=> pure . toNormalizedUri next = unwrapDiagnostic <$> skipManyTill anyMessage diagnostic- expected' <- Map.fromListWith (<>) <$> traverseOf (traverse . _1) f expected+ expected' <- Map.fromListWith (<>) <$> traverseOf (traverse . _1) toSessionPath expected expectDiagnosticsWithTags' next expected' expectDiagnosticsWithTags' :: (HasCallStack, MonadIO m) => m (Uri, [Diagnostic]) ->- Map.Map NormalizedUri [(DiagnosticSeverity, Cursor, T.Text, Maybe DiagnosticTag)] ->+ Map.Map NormalizedUri [ExpectedDiagnosticWithTag] -> m () expectDiagnosticsWithTags' next m | null m = do (_,actual) <- next@@ -170,14 +175,14 @@ <> show actual go $ Map.delete canonUri m -expectCurrentDiagnostics :: HasCallStack => TextDocumentIdentifier -> [(DiagnosticSeverity, Cursor, T.Text)] -> Session ()+expectCurrentDiagnostics :: HasCallStack => TextDocumentIdentifier -> [ExpectedDiagnostic] -> Session () expectCurrentDiagnostics doc expected = do diags <- getCurrentDiagnostics doc checkDiagnosticsForDoc doc expected diags -checkDiagnosticsForDoc :: HasCallStack => TextDocumentIdentifier -> [(DiagnosticSeverity, Cursor, T.Text)] -> [Diagnostic] -> Session ()+checkDiagnosticsForDoc :: HasCallStack => TextDocumentIdentifier -> [ExpectedDiagnostic] -> [Diagnostic] -> Session () checkDiagnosticsForDoc TextDocumentIdentifier {_uri} expected obtained = do- let expected' = Map.singleton nuri (map (\(ds, c, t) -> (ds, c, t, Nothing)) expected)+ let expected' = Map.singleton nuri (map expectedDiagnosticWithNothing expected) nuri = toNormalizedUri _uri expectDiagnosticsWithTags' (return (_uri, obtained)) expected'
src/Development/IDE/Test/Diagnostic.hs view
@@ -1,7 +1,9 @@+{-# LANGUAGE CPP #-} module Development.IDE.Test.Diagnostic where import Control.Lens ((^.)) import qualified Data.Text as T+import Development.IDE.GHC.Compat (GhcVersion (..), ghcVersion) import GHC.Stack (HasCallStack) import Language.LSP.Protocol.Lens import Language.LSP.Protocol.Types@@ -14,12 +16,41 @@ type ErrorMsg = String ++-- | Expected diagnostics have the following components:+--+-- 1. severity+-- 2. cursor (line and column numbers)+-- 3. infix of the message+-- 4. code (e.g. GHC-87543)+type ExpectedDiagnostic =+ ( DiagnosticSeverity+ , Cursor+ , T.Text+ , Maybe T.Text+ )++-- | Expected diagnostics with a tag have the following components:+--+-- 1. severity+-- 2. cursor (line and column numbers)+-- 3. infix of the message+-- 4. code (e.g. GHC-87543)+-- 5. tag (unnecessary or deprecated)+type ExpectedDiagnosticWithTag =+ ( DiagnosticSeverity+ , Cursor+ , T.Text+ , Maybe T.Text+ , Maybe DiagnosticTag+ )+ requireDiagnostic :: (Foldable f, Show (f Diagnostic), HasCallStack) => f Diagnostic- -> (DiagnosticSeverity, Cursor, T.Text, Maybe DiagnosticTag)+ -> ExpectedDiagnosticWithTag -> Maybe ErrorMsg-requireDiagnostic actuals expected@(severity, cursor, expectedMsg, expectedTag)+requireDiagnostic actuals expected@(severity, cursor, expectedMsg, mbExpectedCode, expectedTag) | any match actuals = Nothing | otherwise = Just $ "Could not find " <> show expected <>@@ -32,6 +63,15 @@ && standardizeQuotes (T.toLower expectedMsg) `T.isInfixOf` standardizeQuotes (T.toLower $ d ^. message) && hasTag expectedTag (d ^. tags)+ && codeMatches d++ codeMatches d+ | ghcVersion >= GHC96 =+ case (mbExpectedCode, _code d) of+ (Nothing, _) -> True+ (Just expectedCode, Nothing) -> False+ (Just expectedCode, Just actualCode) -> InR expectedCode == actualCode+ | otherwise = True hasTag :: Maybe DiagnosticTag -> Maybe [DiagnosticTag] -> Bool hasTag Nothing _ = True
src/Test/Hls.hs view
@@ -39,6 +39,10 @@ -- * Helpful re-exports PluginDescriptor, IdeState,+ -- * Helpers for expected test case failuers+ BrokenBehavior(..),+ ExpectBroken(..),+ unCurrent, -- * Assertion helper functions waitForProgressDone, waitForAllProgressDone,@@ -61,7 +65,9 @@ WithPriority(..), Recorder, Priority(..),- TestConfig(..),+ captureKickDiagnostics,+ kick,+ TestConfig(..) ) where @@ -69,6 +75,7 @@ import Control.Concurrent.Async (async, cancel, wait) import Control.Concurrent.Extra import Control.Exception.Safe+import Control.Lens ((^.)) import Control.Lens.Extras (is) import Control.Monad (guard, unless, void) import Control.Monad.Extra (forM)@@ -80,7 +87,7 @@ import Data.ByteString.Lazy (ByteString) import Data.Default (Default, def) import qualified Data.Map as M-import Data.Maybe (fromMaybe)+import Data.Maybe (fromMaybe, mapMaybe) import Data.Proxy (Proxy (Proxy)) import qualified Data.Text as T import qualified Data.Text.Lazy as TL@@ -114,6 +121,7 @@ pluginDescToIdePlugins) import Ide.Types import Language.LSP.Protocol.Capabilities+import qualified Language.LSP.Protocol.Lens as L import Language.LSP.Protocol.Message import qualified Language.LSP.Protocol.Message as LSP import Language.LSP.Protocol.Types hiding (Null)@@ -162,6 +170,15 @@ LogCleanup -> "Cleaned up temporary directory" LogNoCleanup -> "No cleanup of temporary directory" +data BrokenBehavior = Current | Ideal++data ExpectBroken (k :: BrokenBehavior) a where+ BrokenCurrent :: a -> ExpectBroken 'Current a+ BrokenIdeal :: a -> ExpectBroken 'Ideal a++unCurrent :: ExpectBroken 'Current a -> a+unCurrent (BrokenCurrent a) = a+ -- | Run 'defaultMainWithRerun', limiting each single test case running at most 10 minutes defaultTestRunner :: TestTree -> IO () defaultTestRunner = defaultMainWithRerun . adjustOption (const $ mkTimeout 600000000)@@ -231,14 +248,14 @@ :: Pretty b => TestConfig b -> TestName- -> FilePath+ -> VirtualFileTree -> FilePath -> FilePath -> FilePath -> (TextDocumentIdentifier -> Session ()) -> TestTree-goldenWithTestConfig config title testDataDir path desc ext act =- goldenGitDiff title (testDataDir </> path <.> desc <.> ext)+goldenWithTestConfig config title tree path desc ext act =+ goldenGitDiff title (vftOriginalRoot tree </> path <.> desc <.> ext) $ runSessionWithTestConfig config $ const $ TL.encodeUtf8 . TL.fromStrict <$> do@@ -869,6 +886,17 @@ -- requests! skipManyTill anyMessage (void configurationRequest) +captureKickDiagnostics :: Session () -> Session () -> Session [Diagnostic]+captureKickDiagnostics start done = do+ _ <- skipManyTill anyMessage start+ messages <- manyTill anyMessage done+ pure $ concat $ mapMaybe diagnostics messages+ where+ diagnostics :: FromServerMessage' a -> Maybe [Diagnostic]+ diagnostics = \msg -> case msg of+ FromServerMess SMethod_TextDocumentPublishDiagnostics diags -> Just (diags ^. L.params . L.diagnostics)+ _ -> Nothing+ waitForKickDone :: Session () waitForKickDone = void $ skipManyTill anyMessage nonTrivialKickDone @@ -880,6 +908,7 @@ nonTrivialKickStart :: Session () nonTrivialKickStart = kick (Proxy @"kick/start") >>= guard . not . null+ kick :: KnownSymbol k => Proxy k -> Session [FilePath] kick proxyMsg = do
src/Test/Hls/Util.hs view
@@ -7,6 +7,7 @@ ( -- * Test Capabilities codeActionResolveCaps , codeActionNoResolveCaps+ , codeActionNoInlayHintsCaps , codeActionSupportCaps , expectCodeAction -- * Environment specifications@@ -35,6 +36,7 @@ , inspectCodeAction , inspectCommand , inspectDiagnostic+ , inspectDiagnosticAny , waitForDiagnosticsFrom , waitForDiagnosticsFromSource , waitForDiagnosticsFromSourceWithTimeout@@ -107,6 +109,12 @@ codeActionNoResolveCaps = Test.fullLatestClientCaps & (L.textDocument . _Just . L.codeAction . _Just . L.resolveSupport) .~ Nothing & (L.textDocument . _Just . L.codeAction . _Just . L.dataSupport . _Just) .~ False++codeActionNoInlayHintsCaps :: ClientCapabilities+codeActionNoInlayHintsCaps = Test.fullLatestClientCaps+ & (L.textDocument . _Just . L.codeAction . _Just . L.resolveSupport) .~ Nothing+ & (L.textDocument . _Just . L.codeAction . _Just . L.dataSupport . _Just) .~ False+ & (L.textDocument . _Just . L.inlayHint) .~ Nothing -- --------------------------------------------------------------------- -- Environment specification for ignoring tests -- ---------------------------------------------------------------------@@ -239,6 +247,10 @@ inspectDiagnostic :: [Diagnostic] -> [T.Text] -> IO Diagnostic inspectDiagnostic diags s = onMatch diags (\ca -> all (`T.isInfixOf` (ca ^. L.message)) s) err where err = "expected diagnostic matching '" ++ show s ++ "' but did not find one"++inspectDiagnosticAny :: [Diagnostic] -> [T.Text] -> IO Diagnostic+inspectDiagnosticAny diags s = onMatch diags (\ca -> any (`T.isInfixOf` (ca ^. L.message)) s) err+ where err = "expected diagnostic matching one of'" ++ show s ++ "' but did not find one" expectDiagnostic :: [Diagnostic] -> [T.Text] -> IO () expectDiagnostic diags s = void $ inspectDiagnostic diags s