lsp-test 0.4.0.0 → 0.5.0.0
raw patch · 5 files changed
+65/−42 lines, 5 filesdep ~basePVP ok
version bump matches the API change (PVP)
Dependency ranges changed: base
API changes (from Hackage documentation)
+ Language.Haskell.LSP.Test: [lspConfig] :: SessionConfig -> Maybe Value
- Language.Haskell.LSP.Test: SessionConfig :: Int -> Bool -> Bool -> Bool -> SessionConfig
+ Language.Haskell.LSP.Test: SessionConfig :: Int -> Bool -> Bool -> Bool -> Maybe Value -> SessionConfig
Files
- README.md +2/−2
- lsp-test.cabal +3/−3
- src/Language/Haskell/LSP/Test.hs +4/−0
- src/Language/Haskell/LSP/Test/Session.hs +20/−7
- test/Test.hs +36/−30
README.md view
@@ -1,4 +1,4 @@-# lsp-test [](https://travis-ci.com/Bubba/lsp-test) [](https://hackage.haskell.org/package/lsp-test-0.1.0.0)+# lsp-test [](https://travis-ci.com/bubba/lsp-test) [](https://hackage.haskell.org/package/lsp-test-0.1.0.0) lsp-test is a functional testing framework for Language Server Protocol servers. ```haskell@@ -35,7 +35,7 @@ ``` Try out the example tests in the `example` directory with `cabal new-test`.-For more examples check the [Wiki](https://github.com/Bubba/lsp-test/wiki/Introduction)+For more examples check the [Wiki](https://github.com/bubba/lsp-test/wiki/Introduction) ## Developing To test make sure you have the following language servers installed:
lsp-test.cabal view
@@ -1,5 +1,5 @@ name: lsp-test-version: 0.4.0.0+version: 0.5.0.0 synopsis: Functional test framework for LSP servers. description: A test framework for writing tests against @@ -36,7 +36,7 @@ , parser-combinators:Control.Applicative.Combinators default-language: Haskell2010 build-depends: base >= 4.7 && < 5- , haskell-lsp == 0.8.*+ , haskell-lsp >= 0.8 && < 0.9 , aeson , aeson-pretty , ansi-terminal@@ -78,7 +78,7 @@ build-depends: base >= 4.7 && < 5 , hspec , lens- , haskell-lsp == 0.8.*+ , haskell-lsp >= 0.8 && < 0.9 , lsp-test , data-default , aeson
src/Language/Haskell/LSP/Test.hs view
@@ -155,6 +155,10 @@ sendNotification Initialized InitializedParams + case lspConfig config of+ Just cfg -> sendNotification WorkspaceDidChangeConfiguration (DidChangeConfigurationParams cfg)+ Nothing -> return ()+ -- Run the actual test result <- session
src/Language/Haskell/LSP/Test/Session.hs view
@@ -1,7 +1,9 @@+{-# LANGUAGE CPP #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE RankNTypes #-} module Language.Haskell.LSP.Test.Session ( Session@@ -32,6 +34,9 @@ import Control.Monad import Control.Monad.IO.Class import Control.Monad.Except+#if __GLASGOW_HASKELL__ >= 806+import Control.Monad.Fail+#endif import Control.Monad.Trans.Reader (ReaderT, runReaderT) import qualified Control.Monad.Trans.Reader as Reader (ask) import Control.Monad.Trans.State (StateT, runStateT)@@ -69,17 +74,25 @@ type Session = ParserStateReader FromServerMessage SessionState SessionContext IO +#if __GLASGOW_HASKELL__ >= 806+instance MonadFail Session where+ fail s = do+ lastMsg <- fromJust . lastReceivedMessage <$> get+ liftIO $ throw (UnexpectedMessage s lastMsg)+#endif+ -- | Stuff you can configure for a 'Session'. data SessionConfig = SessionConfig { messageTimeout :: Int -- ^ Maximum time to wait for a message in seconds, defaults to 60. , logStdErr :: Bool -- ^ Redirect the server's stderr to this stdout, defaults to False.- , logMessages :: Bool -- ^ Trace the messages sent and received to stdout, defaults to True.+ , logMessages :: Bool -- ^ Trace the messages sent and received to stdout, defaults to False. , logColor :: Bool -- ^ Add ANSI color to the logged messages, defaults to True.+ , lspConfig :: Maybe Value -- ^ The initial LSP config as JSON value, defaults to Nothing. } -- | The configuration used in 'Language.Haskell.LSP.Test.runSession'. defaultConfig :: SessionConfig-defaultConfig = SessionConfig 60 False False True+defaultConfig = SessionConfig 60 False False True Nothing instance Default SessionConfig where def = defaultConfig@@ -148,7 +161,7 @@ runSession context state session = runReaderT (runStateT conduit state) context where conduit = runConduit $ chanSource .| watchdog .| updateStateC .| runConduitParser (catchError session handler)- + handler (Unexpected "ConduitParser.empty") = do lastMsg <- fromJust . lastReceivedMessage <$> get name <- getParserName@@ -257,7 +270,7 @@ msg = NotificationMessage "2.0" TextDocumentDidOpen (DidOpenTextDocumentParams item) liftIO $ B.hPut (serverIn ctx) $ addHeader (encode msg) - modifyM $ \s -> do + modifyM $ \s -> do newVFS <- liftIO $ openVFS (vfs s) msg return $ s { vfs = newVFS } @@ -288,14 +301,14 @@ withTimeout :: Int -> Session a -> Session a withTimeout duration f = do chan <- asks messageChan- timeoutId <- curTimeoutId <$> get + timeoutId <- curTimeoutId <$> get modify $ \s -> s { overridingTimeout = True } liftIO $ forkIO $ do threadDelay (duration * 1000000) writeChan chan (TimeoutMessage timeoutId) res <- f modify $ \s -> s { curTimeoutId = timeoutId + 1,- overridingTimeout = False + overridingTimeout = False } return res @@ -319,5 +332,5 @@ color | t == LogServer = Magenta | otherwise = Cyan- + showPretty = B.unpack . encodePretty
test/Test.hs view
@@ -31,21 +31,21 @@ describe "Session" $ do it "fails a test" $ -- TODO: Catch the exception in haskell-lsp-test and provide nicer output- let session = runSession "hie --lsp" fullCaps "test/data/renamePass" $ do+ let session = runSession "hie" fullCaps "test/data/renamePass" $ do openDoc "Desktop/simple.hs" "haskell" skipMany loggingNotification anyRequest in session `shouldThrow` anyException- it "initializeResponse" $ runSession "hie --lsp" fullCaps "test/data/renamePass" $ do+ it "initializeResponse" $ runSession "hie" fullCaps "test/data/renamePass" $ do rsp <- initializeResponse liftIO $ rsp ^. result `shouldNotBe` Nothing it "runSessionWithConfig" $- runSession "hie --lsp" didChangeCaps "test/data/renamePass" $ return ()+ runSession "hie" didChangeCaps "test/data/renamePass" $ return () describe "withTimeout" $ do it "times out" $- let sesh = runSession "hie --lsp" fullCaps "test/data/renamePass" $ do+ let sesh = runSession "hie" fullCaps "test/data/renamePass" $ do openDoc "Desktop/simple.hs" "haskell" -- won't receive a request - will timeout -- incoming logging requests shouldn't increase the@@ -56,12 +56,12 @@ in timeout 6000000 sesh `shouldThrow` anySessionException it "doesn't time out" $- let sesh = runSession "hie --lsp" fullCaps "test/data/renamePass" $ do+ let sesh = runSession "hie" fullCaps "test/data/renamePass" $ do openDoc "Desktop/simple.hs" "haskell" withTimeout 5 $ skipManyTill anyMessage publishDiagnosticsNotification in void $ timeout 6000000 sesh - it "further timeout messages are ignored" $ runSession "hie --lsp" fullCaps "test/data/renamePass" $ do+ it "further timeout messages are ignored" $ runSession "hie" fullCaps "test/data/renamePass" $ do doc <- openDoc "Desktop/simple.hs" "haskell" withTimeout 3 $ getDocumentSymbols doc liftIO $ threadDelay 5000000@@ -71,7 +71,7 @@ it "overrides global message timeout" $ let sesh =- runSessionWithConfig (def { messageTimeout = 5 }) "hie --lsp" fullCaps "test/data/renamePass" $ do+ runSessionWithConfig (def { messageTimeout = 5 }) "hie" fullCaps "test/data/renamePass" $ do doc <- openDoc "Desktop/simple.hs" "haskell" -- shouldn't time out in here since we are overriding it withTimeout 10 $ liftIO $ threadDelay 7000000@@ -81,7 +81,7 @@ it "unoverrides global message timeout" $ let sesh =- runSessionWithConfig (def { messageTimeout = 5 }) "hie --lsp" fullCaps "test/data/renamePass" $ do+ runSessionWithConfig (def { messageTimeout = 5 }) "hie" fullCaps "test/data/renamePass" $ do doc <- openDoc "Desktop/simple.hs" "haskell" -- shouldn't time out in here since we are overriding it withTimeout 10 $ liftIO $ threadDelay 7000000@@ -93,13 +93,13 @@ describe "SessionException" $ do it "throw on time out" $- let sesh = runSessionWithConfig (def {messageTimeout = 10}) "hie --lsp" fullCaps "test/data/renamePass" $ do+ let sesh = runSessionWithConfig (def {messageTimeout = 10}) "hie" fullCaps "test/data/renamePass" $ do skipMany loggingNotification _ <- message :: Session ApplyWorkspaceEditRequest return () in sesh `shouldThrow` anySessionException - it "don't throw when no time out" $ runSessionWithConfig (def {messageTimeout = 5}) "hie --lsp" fullCaps "test/data/renamePass" $ do+ it "don't throw when no time out" $ runSessionWithConfig (def {messageTimeout = 5}) "hie" fullCaps "test/data/renamePass" $ do loggingNotification liftIO $ threadDelay 10 _ <- openDoc "Desktop/simple.hs" "haskell"@@ -109,7 +109,7 @@ it "throws when there's an unexpected message" $ let selector (UnexpectedMessage "Publish diagnostics notification" (NotLogMessage _)) = True selector _ = False- in runSession "hie --lsp" fullCaps "test/data/renamePass" publishDiagnosticsNotification `shouldThrow` selector+ in runSession "hie" fullCaps "test/data/renamePass" publishDiagnosticsNotification `shouldThrow` selector it "provides the correct types that were expected and received" $ let selector (UnexpectedMessage "ResponseMessage WorkspaceEdit" (RspDocumentSymbols _)) = True selector _ = False@@ -118,17 +118,17 @@ sendRequest TextDocumentDocumentSymbol (DocumentSymbolParams doc) skipMany anyNotification message :: Session RenameResponse -- the wrong type- in runSession "hie --lsp" fullCaps "test/data/renamePass" sesh+ in runSession "hie" fullCaps "test/data/renamePass" sesh `shouldThrow` selector describe "replaySession" $ -- This is too fickle at the moment -- it "passes a test" $- -- replaySession "hie --lsp" "test/data/renamePass"+ -- replaySession "hie" "test/data/renamePass" it "fails a test" $ let selector (ReplayOutOfOrder _ _) = True selector _ = False- in replaySession "hie --lsp" "test/data/renameFail" `shouldThrow` selector+ in replaySession "hie" "test/data/renameFail" `shouldThrow` selector describe "manual javascript session" $ it "passes a test" $@@ -145,7 +145,7 @@ describe "text document VFS" $ it "sends back didChange notifications" $- runSession "hie --lsp" def "test/data/refactor" $ do+ runSession "hie" def "test/data/refactor" $ do doc <- openDoc "Main.hs" "haskell" let args = toJSON $ AOP (doc ^. uri)@@ -168,7 +168,7 @@ describe "getDocumentEdit" $ it "automatically consumes applyedit requests" $- runSession "hie --lsp" fullCaps "test/data/refactor" $ do+ runSession "hie" fullCaps "test/data/refactor" $ do doc <- openDoc "Main.hs" "haskell" let args = toJSON $ AOP (doc ^. uri)@@ -188,7 +188,7 @@ liftIO $ action ^. title `shouldBe` "Apply hint:Redundant bracket" describe "getAllCodeActions" $- it "works" $ runSession "hie --lsp" fullCaps "test/data/refactor" $ do+ it "works" $ runSession "hie" fullCaps "test/data/refactor" $ do doc <- openDoc "Main.hs" "haskell" _ <- waitForDiagnostics actions <- getAllCodeActions doc@@ -198,7 +198,7 @@ action ^. command . _Just . command `shouldSatisfy` T.isSuffixOf ":applyrefact:applyOne" describe "getDocumentSymbols" $- it "works" $ runSession "hie --lsp" fullCaps "test/data/renamePass" $ do+ it "works" $ runSession "hie" fullCaps "test/data/renamePass" $ do doc <- openDoc "Desktop/simple.hs" "haskell" skipMany loggingNotification@@ -213,13 +213,13 @@ mainSymbol ^. range `shouldBe` Range (Position 3 0) (Position 5 30) describe "applyEdit" $ do- it "increments the version" $ runSession "hie --lsp" docChangesCaps "test/data/renamePass" $ do+ it "increments the version" $ runSession "hie" docChangesCaps "test/data/renamePass" $ do doc <- openDoc "Desktop/simple.hs" "haskell" VersionedTextDocumentIdentifier _ (Just oldVersion) <- getVersionedDoc doc let edit = TextEdit (Range (Position 1 1) (Position 1 3)) "foo" VersionedTextDocumentIdentifier _ (Just newVersion) <- applyEdit doc edit liftIO $ newVersion `shouldBe` oldVersion + 1- it "changes the document contents" $ runSession "hie --lsp" fullCaps "test/data/renamePass" $ do+ it "changes the document contents" $ runSession "hie" fullCaps "test/data/renamePass" $ do doc <- openDoc "Desktop/simple.hs" "haskell" let edit = TextEdit (Range (Position 0 0) (Position 0 2)) "foo" applyEdit doc edit@@ -227,8 +227,14 @@ liftIO $ contents `shouldSatisfy` T.isPrefixOf "foodule" describe "getCompletions" $- it "works" $ runSession "hie --lsp" def "test/data/renamePass" $ do+ it "works" $ runSession "hie" def "test/data/renamePass" $ do doc <- openDoc "Desktop/simple.hs" "haskell"++ -- wait for module to be loaded+ skipMany loggingNotification+ noDiagnostics+ noDiagnostics+ item:_ <- getCompletions doc (Position 5 5) liftIO $ do item ^. label `shouldBe` "interactWithUser"@@ -236,7 +242,7 @@ item ^. detail `shouldBe` Just "Items -> IO ()\nMain" describe "getReferences" $- it "works" $ runSession "hie --lsp" fullCaps "test/data/renamePass" $ do+ it "works" $ runSession "hie" fullCaps "test/data/renamePass" $ do doc <- openDoc "Desktop/simple.hs" "haskell" let pos = Position 40 3 -- interactWithUser uri = doc ^. LSP.uri@@ -248,14 +254,14 @@ ] describe "getDefinitions" $- it "works" $ runSession "hie --lsp" fullCaps "test/data/renamePass" $ do+ it "works" $ runSession "hie" fullCaps "test/data/renamePass" $ do doc <- openDoc "Desktop/simple.hs" "haskell" let pos = Position 49 25 -- addItem defs <- getDefinitions doc pos liftIO $ defs `shouldBe` [Location (doc ^. uri) (mkRange 28 0 28 7)] describe "waitForDiagnosticsSource" $- it "works" $ runSession "hie --lsp" fullCaps "test/data" $ do+ it "works" $ runSession "hie" fullCaps "test/data" $ do openDoc "Error.hs" "haskell" [diag] <- waitForDiagnosticsSource "ghcmod" liftIO $ do@@ -263,13 +269,13 @@ diag ^. source `shouldBe` Just "ghcmod" describe "rename" $- it "works" $ runSession "hie --lsp" fullCaps "test/data" $ do+ it "works" $ runSession "hie" fullCaps "test/data" $ do doc <- openDoc "Rename.hs" "haskell" rename doc (Position 1 0) "bar" documentContents doc >>= liftIO . shouldBe "main = bar\nbar = return 42\n" describe "getHover" $- it "works" $ runSession "hie --lsp" fullCaps "test/data/renamePass" $ do+ it "works" $ runSession "hie" fullCaps "test/data/renamePass" $ do doc <- openDoc "Desktop/simple.hs" "haskell" -- hover returns nothing until module is loaded skipManyTill loggingNotification $ count 2 noDiagnostics@@ -277,21 +283,21 @@ liftIO $ hover `shouldSatisfy` isJust describe "getHighlights" $- it "works" $ runSession "hie --lsp" fullCaps "test/data/renamePass" $ do+ it "works" $ runSession "hie" fullCaps "test/data/renamePass" $ do doc <- openDoc "Desktop/simple.hs" "haskell" skipManyTill loggingNotification $ count 2 noDiagnostics highlights <- getHighlights doc (Position 27 4) -- addItem liftIO $ length highlights `shouldBe` 4 describe "formatDoc" $- it "works" $ runSession "hie --lsp" fullCaps "test/data" $ do+ it "works" $ runSession "hie" fullCaps "test/data" $ do doc <- openDoc "Format.hs" "haskell" oldContents <- documentContents doc formatDoc doc (FormattingOptions 4 True) documentContents doc >>= liftIO . (`shouldNotBe` oldContents) describe "formatRange" $- it "works" $ runSession "hie --lsp" fullCaps "test/data" $ do+ it "works" $ runSession "hie" fullCaps "test/data" $ do doc <- openDoc "Format.hs" "haskell" oldContents <- documentContents doc formatRange doc (FormattingOptions 4 True) (Range (Position 1 10) (Position 2 10))@@ -300,7 +306,7 @@ describe "closeDoc" $ it "works" $ let sesh =- runSession "hie --lsp" fullCaps "test/data" $ do+ runSession "hie" fullCaps "test/data" $ do doc <- openDoc "Format.hs" "haskell" closeDoc doc -- need to evaluate to throw