From d1855cd1182c51333c82bf964fc96c8ec3ec76b7 Mon Sep 17 00:00:00 2001 From: Luke Lau Date: Wed, 1 Aug 2018 14:10:38 +0100 Subject: [PATCH] Complete documentation and update .cabal --- lsp-test.cabal | 20 +++++++++- src/Language/Haskell/LSP/Test.hs | 39 +++++++++++--------- src/Language/Haskell/LSP/Test/Parsing.hs | 47 +++++++++++++++++++++++- src/Language/Haskell/LSP/Test/Session.hs | 7 +--- 4 files changed, 85 insertions(+), 28 deletions(-) diff --git a/lsp-test.cabal b/lsp-test.cabal index 07169a5..c293534 100644 --- a/lsp-test.cabal +++ b/lsp-test.cabal @@ -1,7 +1,18 @@ name: lsp-test version: 0.1.0.0 synopsis: Functional test framework for LSP servers. --- description: +description: + A test framework for writing tests against + . + @lsp-test@ launches your server as a subprocess and allows you to simulate a session + down to the wire. + Used for testing in . + > runSession "hie" fullCaps "path/to/root/dir" $ do + > doc <- openDoc "Desktop/simple.hs" "haskell" + > diags <- waitForDiagnostics + > let pos = Position 12 5 + > params = TextDocumentPositionParams doc + > hover <- request TextDocumentHover params homepage: https://github.com/Bubba/haskell-lsp-test#readme license: BSD3 license-file: LICENSE @@ -12,8 +23,13 @@ bug-reports: https://github.com/Bubba/haskell-lsp-test/issues copyright: 2018 Luke Lau category: Testing build-type: Simple -cabal-version: >=1.10 +cabal-version: 2.0 extra-source-files: README.md +tested-with: GHC == 8.2.2 , GHC == 8.4.2 , GHC == 8.4.3 + +source-repository head + type: git + location: https://github.com/Bubba/haskell-lsp-test/ library hs-source-dirs: src diff --git a/src/Language/Haskell/LSP/Test.hs b/src/Language/Haskell/LSP/Test.hs index b7057c2..ed60e58 100644 --- a/src/Language/Haskell/LSP/Test.hs +++ b/src/Language/Haskell/LSP/Test.hs @@ -10,9 +10,9 @@ Maintainer : luke_lau@icloud.com Stability : experimental Portability : POSIX -A framework for testing - -functionally. +Provides the framework to start functionally testing +. +You should import "Language.Haskell.LSP.Types" alongside this. -} module Language.Haskell.LSP.Test ( @@ -25,8 +25,7 @@ module Language.Haskell.LSP.Test , defaultConfig , module Language.Haskell.LSP.Test.Capabilities -- ** Exceptions - , SessionException(..) - , anySessionException + , module Language.Haskell.LSP.Test.Exceptions , withTimeout -- * Sending , request @@ -35,16 +34,10 @@ module Language.Haskell.LSP.Test , sendNotification , sendResponse -- * Receving - , message - , anyRequest - , anyResponse - , anyNotification - , anyMessage - , loggingNotification - , publishDiagnosticsNotification - -- * Combinators - , satisfy + , module Language.Haskell.LSP.Test.Parsing -- * Utilities + -- | Quick helper functions for common tasks. + -- ** Initialization , initializeResponse -- ** Documents , openDoc @@ -114,6 +107,13 @@ import System.FilePath import qualified Yi.Rope as Rope -- | Starts a new session. +-- +-- > runSession "hie" fullCaps "path/to/root/dir" $ do +-- > doc <- openDoc "Desktop/simple.hs" "haskell" +-- > diags <- waitForDiagnostics +-- > let pos = Position 12 5 +-- > params = TextDocumentPositionParams doc +-- > hover <- request TextDocumentHover params runSession :: String -- ^ The command to run the server. -> LSP.ClientCapabilities -- ^ The capabilities that the client should declare. -> FilePath -- ^ The filepath to the root directory for the session. @@ -121,7 +121,7 @@ runSession :: String -- ^ The command to run the server. -> IO a runSession = runSessionWithConfig def --- | Starts a new sesion with a client with the specified capabilities. +-- | Starts a new sesion with a custom configuration. runSessionWithConfig :: SessionConfig -- ^ Configuration options for the session. -> String -- ^ The command to run the server. -> LSP.ClientCapabilities -- ^ The capabilities that the client should declare. @@ -254,7 +254,7 @@ sendNotification :: ToJSON a -> a -- ^ The notification parameters. -> Session () --- | Open a virtual file if we send a did open text document notification +-- Open a virtual file if we send a did open text document notification sendNotification TextDocumentDidOpen params = do let params' = fromJust $ decode $ encode params n :: DidOpenTextDocumentNotification @@ -264,7 +264,7 @@ sendNotification TextDocumentDidOpen params = do modify (\s -> s { vfs = newVFS }) sendMessage n --- | Close a virtual file if we send a close text document notification +-- Close a virtual file if we send a close text document notification sendNotification TextDocumentDidClose params = do let params' = fromJust $ decode $ encode params n :: DidCloseTextDocumentNotification @@ -276,6 +276,7 @@ sendNotification TextDocumentDidClose params = do sendNotification method params = sendMessage (NotificationMessage "2.0" method params) +-- | Sends a response to the server. sendResponse :: ToJSON a => ResponseMessage a -> Session () sendResponse = sendMessage @@ -327,6 +328,8 @@ waitForDiagnostics = do let (List diags) = diagsNot ^. params . LSP.diagnostics return diags +-- | The same as 'waitForDiagnostics', but will only match a specific +-- 'Language.Haskell.LSP.Types._source'. waitForDiagnosticsSource :: String -> Session [Diagnostic] waitForDiagnosticsSource src = do diags <- waitForDiagnostics @@ -462,7 +465,7 @@ getDefinitions doc pos = let params = TextDocumentPositionParams doc pos in getResponseResult <$> request TextDocumentDefinition params --- ^ Renames the term at the specified position. +-- | Renames the term at the specified position. rename :: TextDocumentIdentifier -> Position -> String -> Session () rename doc pos newName = do let params = RenameParams doc pos (T.pack newName) diff --git a/src/Language/Haskell/LSP/Test/Parsing.hs b/src/Language/Haskell/LSP/Test/Parsing.hs index b683035..128cfb4 100644 --- a/src/Language/Haskell/LSP/Test/Parsing.hs +++ b/src/Language/Haskell/LSP/Test/Parsing.hs @@ -2,7 +2,18 @@ {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE RankNTypes #-} {-# LANGUAGE OverloadedStrings #-} -module Language.Haskell.LSP.Test.Parsing where + +module Language.Haskell.LSP.Test.Parsing + ( -- $receiving + message + , anyRequest + , anyResponse + , anyNotification + , anyMessage + , loggingNotification + , publishDiagnosticsNotification + , responseForId + ) where import Control.Applicative import Control.Concurrent @@ -16,10 +27,38 @@ import Data.Maybe import qualified Data.Text as T import Data.Typeable import Language.Haskell.LSP.Messages -import Language.Haskell.LSP.Types as LSP hiding (error) +import Language.Haskell.LSP.Types as LSP hiding (error, message) import Language.Haskell.LSP.Test.Messages import Language.Haskell.LSP.Test.Session +-- $receiving +-- To receive a message, just specify the type that expect: +-- +-- @ +-- msg1 <- message :: Session ApplyWorkspaceEditRequest +-- msg2 <- message :: Session HoverResponse +-- @ +-- +-- 'Language.Haskell.LSP.Test.Session' is actually just a parser +-- that operates on messages under the hood. This means that you +-- can create and combine parsers to match speicifc sequences of +-- messages that you expect. +-- +-- For example, if you wanted to match either a definition or +-- references request: +-- +-- > defOrImpl = (message :: Session DefinitionRequest) +-- > <|> (message :: Session ReferencesRequest) +-- +-- If you wanted to match any number of telemetry +-- notifications immediately followed by a response: +-- +-- @ +-- logThenDiags = +-- skipManyTill (message :: Session TelemetryNotification) +-- anyResponse +-- @ + satisfy :: (FromServerMessage -> Bool) -> Session FromServerMessage satisfy pred = do @@ -64,12 +103,14 @@ anyRequest = named "Any request" $ satisfy isServerRequest anyResponse :: Session FromServerMessage anyResponse = named "Any response" $ satisfy isServerResponse +-- | Matches a response for a specific id. responseForId :: forall a. FromJSON a => LspId -> Session (ResponseMessage a) responseForId lid = named (T.pack $ "Response for id: " ++ show lid) $ do let parser = decode . encodeMsg :: FromServerMessage -> Maybe (ResponseMessage a) x <- satisfy (maybe False (\z -> z ^. LSP.id == responseId lid) . parser) return $ castMsg x +-- | Matches any type of message. anyMessage :: Session FromServerMessage anyMessage = satisfy (const True) @@ -91,6 +132,8 @@ loggingNotification = named "Logging notification" $ satisfy shouldSkip shouldSkip (ReqShowMessage _) = True shouldSkip _ = False +-- | Matches a 'Language.Haskell.LSP.Test.PublishDiagnosticsNotification' +-- (textDocument/publishDiagnostics) notification. publishDiagnosticsNotification :: Session PublishDiagnosticsNotification publishDiagnosticsNotification = named "Publish diagnostics notification" $ do NotPublishDiagnostics diags <- satisfy test diff --git a/src/Language/Haskell/LSP/Test/Session.hs b/src/Language/Haskell/LSP/Test/Session.hs index 1fee2be..8e4754f 100644 --- a/src/Language/Haskell/LSP/Test/Session.hs +++ b/src/Language/Haskell/LSP/Test/Session.hs @@ -65,12 +65,7 @@ import System.IO -- You can send and receive messages to the server within 'Session' via 'getMessage', -- 'sendRequest' and 'sendNotification'. -- --- @ --- runSession \"path\/to\/root\/dir\" $ do --- docItem <- getDocItem "Desktop/simple.hs" "haskell" --- sendNotification TextDocumentDidOpen (DidOpenTextDocumentParams docItem) --- diagnostics <- getMessage :: Session PublishDiagnosticsNotification --- @ + type Session = ParserStateReader FromServerMessage SessionState SessionContext IO -- | Stuff you can configure for a 'Session'. -- 2.30.2