From 645b71d6b36ced5c72fec3c08b60b0aa6fca971b Mon Sep 17 00:00:00 2001 From: mujx Date: Thu, 10 Oct 2019 05:27:15 +0300 Subject: [PATCH] Functional tests setup for the LSP server (#1396) * Functional tests setup for the LSP server * Add nix derivation for lsp-test-0.6.1.0 * Register fixture files * Enable functional tests on appveyor * Attempt to fix Position errors * Fix `dhall-lsp-server` to specify a UTF8 locale Related to https://github.com/dhall-lang/dhall-haskell/issues/1356#issuecomment-536840612 * Specify utf8 encoding for tests * Add test for hovering functionality * Add glob to list fixture files * Remove extra do --- appveyor.yml | 3 +- dhall-lsp-server/app/Main.hs | 9 +- dhall-lsp-server/dhall-lsp-server.cabal | 18 ++ dhall-lsp-server/tests/Main.hs | 173 ++++++++++++++++++ .../tests/fixtures/completion/Bindings.dhall | 1 + .../fixtures/completion/CustomFunctions.dhall | 7 + .../fixtures/completion/CustomTypes.dhall | 3 + .../completion/ImportedFunctions.dhall | 1 + .../tests/fixtures/completion/Library.dhall | 3 + .../fixtures/diagnostics/InvalidImport.dhall | 1 + .../fixtures/diagnostics/InvalidSyntax.dhall | 1 + .../fixtures/diagnostics/MissingImport.dhall | 1 + .../fixtures/diagnostics/UnboundVar.dhall | 1 + .../fixtures/diagnostics/WrongType.dhall | 1 + .../tests/fixtures/hovering/Types.dhall | 11 ++ .../fixtures/linting/SuperfluousIn.dhall | 3 + .../fixtures/linting/UnusedBindings.dhall | 7 + nix/lsp-test.nix | 24 +++ stack.yaml | 2 +- 19 files changed, 266 insertions(+), 4 deletions(-) create mode 100644 dhall-lsp-server/tests/Main.hs create mode 100644 dhall-lsp-server/tests/fixtures/completion/Bindings.dhall create mode 100644 dhall-lsp-server/tests/fixtures/completion/CustomFunctions.dhall create mode 100644 dhall-lsp-server/tests/fixtures/completion/CustomTypes.dhall create mode 100644 dhall-lsp-server/tests/fixtures/completion/ImportedFunctions.dhall create mode 100644 dhall-lsp-server/tests/fixtures/completion/Library.dhall create mode 100644 dhall-lsp-server/tests/fixtures/diagnostics/InvalidImport.dhall create mode 100644 dhall-lsp-server/tests/fixtures/diagnostics/InvalidSyntax.dhall create mode 100644 dhall-lsp-server/tests/fixtures/diagnostics/MissingImport.dhall create mode 100644 dhall-lsp-server/tests/fixtures/diagnostics/UnboundVar.dhall create mode 100644 dhall-lsp-server/tests/fixtures/diagnostics/WrongType.dhall create mode 100644 dhall-lsp-server/tests/fixtures/hovering/Types.dhall create mode 100644 dhall-lsp-server/tests/fixtures/linting/SuperfluousIn.dhall create mode 100644 dhall-lsp-server/tests/fixtures/linting/UnusedBindings.dhall create mode 100644 nix/lsp-test.nix diff --git a/appveyor.yml b/appveyor.yml index 5d8e562..af6a16f 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -54,7 +54,8 @@ for: - chcp 65001 && stack test dhall - stack test dhall-json - stack test dhall-bash - # - stack test dhall-lsp-server # Disabled while the tests are broken. + # - stack test dhall-lsp-server:doctest # Disabled while the tests are broken. + - stack test dhall-lsp-server:tests - stack bench dhall --benchmark-arguments "--quick --min-duration=0 --include-first-iter" - diff --git a/dhall-lsp-server/app/Main.hs b/dhall-lsp-server/app/Main.hs index c3b49ac..45073d3 100644 --- a/dhall-lsp-server/app/Main.hs +++ b/dhall-lsp-server/app/Main.hs @@ -15,6 +15,7 @@ import Data.Monoid ((<>)) import qualified Data.Version import qualified Dhall.LSP.Server import qualified Paths_dhall_lsp_server +import qualified GHC.IO.Encoding -- | Top-level program options data Options = Options { @@ -74,5 +75,9 @@ runCommand Options {..} = do -- | Entry point for the @dhall-lsp-server@ executable main :: IO () -main = do options <- Options.Applicative.execParser parserInfoOptions - runCommand options +main = do + GHC.IO.Encoding.setLocaleEncoding GHC.IO.Encoding.utf8 + + options <- Options.Applicative.execParser parserInfoOptions + + runCommand options diff --git a/dhall-lsp-server/dhall-lsp-server.cabal b/dhall-lsp-server/dhall-lsp-server.cabal index d0858e2..47b922c 100644 --- a/dhall-lsp-server/dhall-lsp-server.cabal +++ b/dhall-lsp-server/dhall-lsp-server.cabal @@ -13,6 +13,10 @@ build-type: Simple extra-source-files: README.md ChangeLog.md + tests/fixtures/completion/*.dhall + tests/fixtures/diagnostics/*.dhall + tests/fixtures/linting/*.dhall + tests/fixtures/hovering/*.dhall source-repository head type: git @@ -102,3 +106,17 @@ Test-Suite doctest -- See: https://ghc.haskell.org/trac/ghc/ticket/10970 if impl(ghc < 8.0) Buildable: False + +Test-Suite tests + Type: exitcode-stdio-1.0 + Hs-Source-Dirs: tests + Main-Is: Main.hs + GHC-Options: -Wall + Build-Depends: + base , + haskell-lsp-types >= 0.15.0 && < 0.16 , + lsp-test >= 0.6 && < 0.7 , + tasty >= 0.11.2 && < 1.3 , + tasty-hspec >= 1.1 && < 1.2 , + text >= 0.11 && < 1.3 + Default-Language: Haskell2010 diff --git a/dhall-lsp-server/tests/Main.hs b/dhall-lsp-server/tests/Main.hs new file mode 100644 index 0000000..b9478dd --- /dev/null +++ b/dhall-lsp-server/tests/Main.hs @@ -0,0 +1,173 @@ +{-# LANGUAGE OverloadedStrings #-} + +import Control.Monad.IO.Class (liftIO) +import Data.Maybe (fromJust) +import qualified Data.Text as T +import qualified GHC.IO.Encoding +import Language.Haskell.LSP.Test +import Language.Haskell.LSP.Types + ( CompletionItem (..), + Diagnostic (..), + DiagnosticSeverity (..), + Hover (..), + HoverContents (..), + MarkupContent (..), + Position (..), + ) +import Test.Tasty +import Test.Tasty.Hspec + +baseDir :: FilePath -> FilePath +baseDir d = "tests/fixtures/" <> d + +hoveringSpec :: FilePath -> Spec +hoveringSpec dir = + describe "Dhall.Hover" + $ it "reports types on hover" + $ runSession "dhall-lsp-server" fullCaps dir + $ do + docId <- openDoc "Types.dhall" "dhall" + let typePos = Position 0 5 + functionPos = Position 2 7 + extractContents = _contents . fromJust + getValue = T.unpack . _value + typeHover <- getHover docId typePos + funcHover <- getHover docId functionPos + liftIO $ do + case (extractContents typeHover, extractContents funcHover) of + (HoverContents typeContent, HoverContents functionContent) -> do + getValue typeContent `shouldBe` "Type" + getValue functionContent `shouldBe` "{ home : Text, name : Text }" + _ -> error "test failed" + pure () + +lintingSpec :: FilePath -> Spec +lintingSpec fixtureDir = + describe "Dhall.Lint" $ do + it "reports unused bindings" + $ runSession "dhall-lsp-server" fullCaps fixtureDir + $ do + _ <- openDoc "UnusedBindings.dhall" "dhall" + diags <- waitForDiagnosticsSource "Dhall.Lint" + _ <- + liftIO $ + mapM + ( \diag -> do + _severity diag `shouldBe` Just DsHint + T.unpack (_message diag) `shouldContain` "Unused let binding" + ) + diags + pure () + it "reports multiple hints" + $ runSession "dhall-lsp-server" fullCaps fixtureDir + $ do + _ <- openDoc "SuperfluousIn.dhall" "dhall" + diags <- waitForDiagnosticsSource "Dhall.Lint" + liftIO $ length diags `shouldBe` 2 + let diag1 = head diags + diag2 = diags !! 1 + liftIO $ do + _severity diag1 `shouldBe` Just DsHint + T.unpack (_message diag1) `shouldContain` "Superfluous 'in'" + _severity diag2 `shouldBe` Just DsHint + T.unpack (_message diag2) `shouldContain` "Unused let binding" + +codeCompletionSpec :: FilePath -> Spec +codeCompletionSpec fixtureDir = + describe "Dhall.Completion" $ do + it "suggests user defined types" + $ runSession "dhall-lsp-server" fullCaps fixtureDir + $ do + docId <- openDoc "CustomTypes.dhall" "dhall" + cs <- getCompletions docId (Position {_line = 2, _character = 35}) + liftIO $ do + let firstItem = head cs + _label firstItem `shouldBe` "Config" + _detail firstItem `shouldBe` Just "Type" + it "suggests user defined functions" + $ runSession "dhall-lsp-server" fullCaps fixtureDir + $ do + docId <- openDoc "CustomFunctions.dhall" "dhall" + cs <- getCompletions docId (Position {_line = 6, _character = 7}) + liftIO $ do + let firstItem = head cs + _label firstItem `shouldBe` "makeUser" + _detail firstItem `shouldBe` Just "\8704(user : Text) \8594 { home : Text }" + it "suggests user defined bindings" + $ runSession "dhall-lsp-server" fullCaps fixtureDir + $ do + docId <- openDoc "Bindings.dhall" "dhall" + cs <- getCompletions docId (Position {_line = 0, _character = 59}) + liftIO $ do + let firstItem = head cs + _label firstItem `shouldBe` "bob" + _detail firstItem `shouldBe` Just "Text" + it "suggests functions from imports" + $ runSession "dhall-lsp-server" fullCaps fixtureDir + $ do + docId <- openDoc "ImportedFunctions.dhall" "dhall" + cs <- getCompletions docId (Position {_line = 0, _character = 33}) + liftIO $ do + let firstItem = head cs + _label firstItem `shouldBe` "makeUser" + _detail firstItem `shouldBe` Just "\8704(user : Text) \8594 { home : Text }" + +diagnosticsSpec :: FilePath -> Spec +diagnosticsSpec fixtureDir = do + describe "Dhall.TypeCheck" $ do + it "reports unbound variables" + $ runSession "dhall-lsp-server" fullCaps fixtureDir + $ do + _ <- openDoc "UnboundVar.dhall" "dhall" + [diag] <- waitForDiagnosticsSource "Dhall.TypeCheck" + liftIO $ do + _severity diag `shouldBe` Just DsError + T.unpack (_message diag) `shouldContain` "Unbound variable" + it "reports wrong type" + $ runSession "dhall-lsp-server" fullCaps fixtureDir + $ do + _ <- openDoc "WrongType.dhall" "dhall" + [diag] <- waitForDiagnosticsSource "Dhall.TypeCheck" + liftIO $ do + _severity diag `shouldBe` Just DsError + T.unpack (_message diag) `shouldContain` "Expression doesn't match annotation" + describe "Dhall.Import" $ do + it "reports invalid imports" + $ runSession "dhall-lsp-server" fullCaps fixtureDir + $ do + _ <- openDoc "InvalidImport.dhall" "dhall" + [diag] <- waitForDiagnosticsSource "Dhall.Import" + liftIO $ do + _severity diag `shouldBe` Just DsError + T.unpack (_message diag) `shouldContain` "Invalid input" + it "reports missing imports" + $ runSession "dhall-lsp-server" fullCaps fixtureDir + $ do + _ <- openDoc "MissingImport.dhall" "dhall" + [diag] <- waitForDiagnosticsSource "Dhall.Import" + liftIO $ do + _severity diag `shouldBe` Just DsError + T.unpack (_message diag) `shouldContain` "Missing file" + describe "Dhall.Parser" + $ it "reports invalid syntax" + $ runSession "dhall-lsp-server" fullCaps fixtureDir + $ do + _ <- openDoc "InvalidSyntax.dhall" "dhall" + [diag] <- waitForDiagnosticsSource "Dhall.Parser" + liftIO $ _severity diag `shouldBe` Just DsError + +main :: IO () +main = do + GHC.IO.Encoding.setLocaleEncoding GHC.IO.Encoding.utf8 + diagnostics <- testSpec "Diagnostics" (diagnosticsSpec (baseDir "diagnostics")) + linting <- testSpec "Linting" (lintingSpec (baseDir "linting")) + completion <- testSpec "Completion" (codeCompletionSpec (baseDir "completion")) + hovering <- testSpec "Hovering" (hoveringSpec (baseDir "hovering")) + defaultMain + ( testGroup "Tests" + [ diagnostics, + linting, + completion, + hovering + ] + ) diff --git a/dhall-lsp-server/tests/fixtures/completion/Bindings.dhall b/dhall-lsp-server/tests/fixtures/completion/Bindings.dhall new file mode 100644 index 0000000..4b45f1e --- /dev/null +++ b/dhall-lsp-server/tests/fixtures/completion/Bindings.dhall @@ -0,0 +1 @@ +let alice = "Alice" let bob = "Bob" in { result = bob ++ al } diff --git a/dhall-lsp-server/tests/fixtures/completion/CustomFunctions.dhall b/dhall-lsp-server/tests/fixtures/completion/CustomFunctions.dhall new file mode 100644 index 0000000..edcd3e2 --- /dev/null +++ b/dhall-lsp-server/tests/fixtures/completion/CustomFunctions.dhall @@ -0,0 +1,7 @@ +let makeUser = + λ(user : Text) + → let home = "/home/${user}" + + in { home = home } + +in [ m diff --git a/dhall-lsp-server/tests/fixtures/completion/CustomTypes.dhall b/dhall-lsp-server/tests/fixtures/completion/CustomTypes.dhall new file mode 100644 index 0000000..34ee7ec --- /dev/null +++ b/dhall-lsp-server/tests/fixtures/completion/CustomTypes.dhall @@ -0,0 +1,3 @@ +let Config = { name : Text, age : Natural } + +in { name = "alice", age = 20 } : C diff --git a/dhall-lsp-server/tests/fixtures/completion/ImportedFunctions.dhall b/dhall-lsp-server/tests/fixtures/completion/ImportedFunctions.dhall new file mode 100644 index 0000000..4caff9a --- /dev/null +++ b/dhall-lsp-server/tests/fixtures/completion/ImportedFunctions.dhall @@ -0,0 +1 @@ +let Lib = ./Library.dhall in Lib. diff --git a/dhall-lsp-server/tests/fixtures/completion/Library.dhall b/dhall-lsp-server/tests/fixtures/completion/Library.dhall new file mode 100644 index 0000000..f2aba48 --- /dev/null +++ b/dhall-lsp-server/tests/fixtures/completion/Library.dhall @@ -0,0 +1,3 @@ +let makeUser = λ(user : Text) → let home = "/home/${user}" in { home = home } + +in { makeUser = makeUser } diff --git a/dhall-lsp-server/tests/fixtures/diagnostics/InvalidImport.dhall b/dhall-lsp-server/tests/fixtures/diagnostics/InvalidImport.dhall new file mode 100644 index 0000000..2cf4f6d --- /dev/null +++ b/dhall-lsp-server/tests/fixtures/diagnostics/InvalidImport.dhall @@ -0,0 +1 @@ +./InvalidSyntax.dhall diff --git a/dhall-lsp-server/tests/fixtures/diagnostics/InvalidSyntax.dhall b/dhall-lsp-server/tests/fixtures/diagnostics/InvalidSyntax.dhall new file mode 100644 index 0000000..b51d474 --- /dev/null +++ b/dhall-lsp-server/tests/fixtures/diagnostics/InvalidSyntax.dhall @@ -0,0 +1 @@ +let a = 2 + 2/ diff --git a/dhall-lsp-server/tests/fixtures/diagnostics/MissingImport.dhall b/dhall-lsp-server/tests/fixtures/diagnostics/MissingImport.dhall new file mode 100644 index 0000000..0bff2a1 --- /dev/null +++ b/dhall-lsp-server/tests/fixtures/diagnostics/MissingImport.dhall @@ -0,0 +1 @@ +./NonExistent.dhall diff --git a/dhall-lsp-server/tests/fixtures/diagnostics/UnboundVar.dhall b/dhall-lsp-server/tests/fixtures/diagnostics/UnboundVar.dhall new file mode 100644 index 0000000..95cd3b1 --- /dev/null +++ b/dhall-lsp-server/tests/fixtures/diagnostics/UnboundVar.dhall @@ -0,0 +1 @@ +unboundVar diff --git a/dhall-lsp-server/tests/fixtures/diagnostics/WrongType.dhall b/dhall-lsp-server/tests/fixtures/diagnostics/WrongType.dhall new file mode 100644 index 0000000..b71beff --- /dev/null +++ b/dhall-lsp-server/tests/fixtures/diagnostics/WrongType.dhall @@ -0,0 +1 @@ +let a = "dhall" : List Text in a diff --git a/dhall-lsp-server/tests/fixtures/hovering/Types.dhall b/dhall-lsp-server/tests/fixtures/hovering/Types.dhall new file mode 100644 index 0000000..45da047 --- /dev/null +++ b/dhall-lsp-server/tests/fixtures/hovering/Types.dhall @@ -0,0 +1,11 @@ +let User = { name : Text, home : Text } + +let mkUser = + λ(_isAdmin : Bool) + → if _isAdmin + + then { name = "admin", home = "/home/admin" } + + else { name = "default", home = "/home/user" } + +in mkUser True : User diff --git a/dhall-lsp-server/tests/fixtures/linting/SuperfluousIn.dhall b/dhall-lsp-server/tests/fixtures/linting/SuperfluousIn.dhall new file mode 100644 index 0000000..243cbab --- /dev/null +++ b/dhall-lsp-server/tests/fixtures/linting/SuperfluousIn.dhall @@ -0,0 +1,3 @@ +let alice = { name = "Alice", age = 20 } + +in let carl = { name = "Carl", age = 22 } in alice diff --git a/dhall-lsp-server/tests/fixtures/linting/UnusedBindings.dhall b/dhall-lsp-server/tests/fixtures/linting/UnusedBindings.dhall new file mode 100644 index 0000000..74f7304 --- /dev/null +++ b/dhall-lsp-server/tests/fixtures/linting/UnusedBindings.dhall @@ -0,0 +1,7 @@ +let alice = { name = "Alice", age = 20 } + +let bob = { name = "Bob", age = 21 } + +let carl = { name = "Carl", age = 22 } + +in alice diff --git a/nix/lsp-test.nix b/nix/lsp-test.nix new file mode 100644 index 0000000..7b27e5e --- /dev/null +++ b/nix/lsp-test.nix @@ -0,0 +1,24 @@ +{ mkDerivation, aeson, aeson-pretty, ansi-terminal, async, base +, bytestring, conduit, conduit-parse, containers, data-default +, Diff, directory, filepath, haskell-lsp, hspec, lens, mtl +, parser-combinators, process, rope-utf16-splay, stdenv, text +, transformers, unix, unordered-containers +}: +mkDerivation { + pname = "lsp-test"; + version = "0.6.1.0"; + sha256 = "d15103bc8c84f74ff90220b66cacebe4bcd135ef1e31ddd10c808a94484db7a4"; + libraryHaskellDepends = [ + aeson aeson-pretty ansi-terminal async base bytestring conduit + conduit-parse containers data-default Diff directory filepath + haskell-lsp lens mtl parser-combinators process rope-utf16-splay + text transformers unix unordered-containers + ]; + testHaskellDepends = [ + aeson base data-default haskell-lsp hspec lens text + unordered-containers + ]; + homepage = "https://github.com/bubba/lsp-test#readme"; + description = "Functional test framework for LSP servers"; + license = stdenv.lib.licenses.bsd3; +} diff --git a/stack.yaml b/stack.yaml index 6db1e2c..a286e88 100644 --- a/stack.yaml +++ b/stack.yaml @@ -20,7 +20,7 @@ extra-deps: - HsYAML-0.2.0.0@sha256:4e554ee481650156a26a71b40f233979cd943f22ee887b70dae3b8b24de2932f,5273 - HsYAML-aeson-0.2.0.0@sha256:04796abfc01cffded83f37a10e6edba4f0c0a15d45bef44fc5bb4313d9c87757,1791 - ordered-containers-0.2.2@sha256:ebf2be3f592d9cf148ea6b8375f8af97148d44f82d8d04476899285e965afdbf,810 - + - lsp-test-0.6.1.0@sha256:df0fc403c03b6d036be13de3ff23d9951ae2506080135cd6862eded2c969a6da,3483 nix: packages: - ncurses