on master help sign in
hie-core/test: Dedicated test suite for hie-core (#2243)

* hie-core/test: Dedicated test suite for hie-core * Apply hlint suggestion Use System.Environment.Blank's setEnv which has a non-overwrite mode that implements precisely what we were doing with more code before. * buildifier fixes * hie-core-exe works on Windows now - ghc-paths was fixed

commit8bce2ddee2a8f0d568a80c5d7ecfe25fa7df52cd authorAndreas Herrmann <42969706+aherrmann-da@users.noreply.github.com> dateMon 22 Jul 2019, 15:42 (7 years ago) parent2b61d2c
4 files changed+206−2
BUILD.bazel+7−2 test/BUILD.bazel+50 test/exe/Main.hs+75 test/src/Development/IDE/Test.hs+74
BUILD.bazel+7−2
@@ -1,7 +1,12 @@
11# Copyright (c) 2019 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
22# SPDX-License-Identifier: Apache-2.0
33
4−load("//bazel_tools:haskell.bzl", "da_haskell_binary", "da_haskell_library")
4+load(
5+ "//bazel_tools:haskell.bzl",
6+ "da_haskell_binary",
7+ "da_haskell_library",
8+ "da_haskell_test",
9+)
510load("@os_info//:os_info.bzl", "is_windows")
611
712depends = [
@@ -109,4 +114,4 @@ da_haskell_binary(
109114 deps = [
110115 "hie-core-public",
111116 ],
112−) if not is_windows else None # Disable on Windows until ghc-paths is fixed upstream
117+)
test/BUILD.bazelnew file+50
@@ -0,0 +1,50 @@
1+# Copyright (c) 2019 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
2+# SPDX-License-Identifier: Apache-2.0
3+
4+load(
5+ "//bazel_tools:haskell.bzl",
6+ "da_haskell_library",
7+ "da_haskell_test",
8+)
9+
10+da_haskell_library(
11+ name = "hie-core-testing",
12+ srcs = glob(["src/**/*.hs"]),
13+ hazel_deps = [
14+ "base",
15+ "containers",
16+ "haskell-lsp-types",
17+ "lens",
18+ "lsp-test",
19+ "parser-combinators",
20+ "tasty-hunit",
21+ "text",
22+ ],
23+ src_strip_prefix = "src",
24+ visibility = ["//visibility:public"],
25+ deps = [
26+ "//compiler/hie-core",
27+ ],
28+)
29+
30+da_haskell_test(
31+ name = "hie-core-tests",
32+ srcs = glob(["exe/**/*.hs"]),
33+ data = ["//compiler/hie-core:hie-core-exe"],
34+ hazel_deps = [
35+ "base",
36+ "extra",
37+ "filepath",
38+ "haskell-lsp-types",
39+ "lsp-test",
40+ "tasty",
41+ "tasty-hunit",
42+ "text",
43+ ],
44+ src_strip_prefix = "exe",
45+ deps = [
46+ "//compiler/hie-core",
47+ "//compiler/hie-core/test:hie-core-testing",
48+ "//libs-haskell/bazel-runfiles",
49+ ],
50+)
test/exe/Main.hsnew file+75
@@ -0,0 +1,75 @@
1+-- Copyright (c) 2019 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
2+-- SPDX-License-Identifier: Apache-2.0
3+
4+{-# LANGUAGE OverloadedStrings #-}
5+
6+module Main (main) where
7+
8+import Control.Monad (void)
9+import qualified Data.Text as T
10+import Development.IDE.Test
11+import Language.Haskell.LSP.Test
12+import Language.Haskell.LSP.Types
13+import System.Environment.Blank (setEnv)
14+import System.FilePath
15+import System.IO.Extra
16+import Test.Tasty
17+import Test.Tasty.HUnit
18+
19+import DA.Bazel.Runfiles
20+
21+
22+main :: IO ()
23+main = defaultMain $ testGroup "HIE"
24+ [ testSession "open close" $ do
25+ doc <- openDoc' "Testing.hs" "haskell" ""
26+ void (message :: Session ProgressStartNotification)
27+ closeDoc doc
28+ void (message :: Session ProgressDoneNotification)
29+ , testSession "fix syntax error" $ do
30+ let content = T.unlines [ "module Testing wher" ]
31+ doc <- openDoc' "Testing.hs" "haskell" content
32+ expectDiagnostics [("Testing.hs", [(DsError, (0, 15), "parse error")])]
33+ let change = TextDocumentContentChangeEvent
34+ { _range = Just (Range (Position 0 15) (Position 0 19))
35+ , _rangeLength = Nothing
36+ , _text = "where"
37+ }
38+ changeDoc doc [change]
39+ expectDiagnostics [("Testing.hs", [])]
40+ , testSession "introduce syntax error" $ do
41+ let content = T.unlines [ "module Testing where" ]
42+ doc <- openDoc' "Testing.hs" "haskell" content
43+ void (message :: Session ProgressStartNotification)
44+ let change = TextDocumentContentChangeEvent
45+ { _range = Just (Range (Position 0 15) (Position 0 18))
46+ , _rangeLength = Nothing
47+ , _text = "wher"
48+ }
49+ changeDoc doc [change]
50+ expectDiagnostics [("Testing.hs", [(DsError, (0, 15), "parse error")])]
51+ ]
52+
53+
54+----------------------------------------------------------------------
55+-- Utils
56+
57+
58+testSession :: String -> Session () -> TestTree
59+testSession name = testCase name . run
60+
61+
62+run :: Session a -> IO a
63+run s = withTempDir $ \dir -> do
64+ let hieCoreExePath = mainWorkspace </> exe "compiler/hie-core/hie-core-exe"
65+ hieCoreExe <- locateRunfiles hieCoreExePath
66+ let cmd = unwords [hieCoreExe, "--lsp", "--cwd", dir]
67+ -- HIE calls getXgdDirectory which assumes that HOME is set.
68+ -- Only sets HOME if it wasn't already set.
69+ setEnv "HOME" "/homeless-shelter" False
70+ runSessionWithConfig conf cmd fullCaps dir s
71+ where
72+ conf = defaultConfig
73+ -- If you uncomment this you can see all messages
74+ -- which can be quite useful for debugging.
75+ -- { logMessages = True, logColor = False, logStdErr = True }
test/src/Development/IDE/Test.hsnew file+74
@@ -0,0 +1,74 @@
1+-- Copyright (c) 2019 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
2+-- SPDX-License-Identifier: Apache-2.0
3+
4+module Development.IDE.Test
5+ ( Cursor
6+ , cursorPosition
7+ , requireDiagnostic
8+ , expectDiagnostics
9+ ) where
10+
11+import Control.Applicative.Combinators
12+import Control.Lens hiding (List)
13+import Control.Monad
14+import Control.Monad.IO.Class
15+import qualified Data.Map.Strict as Map
16+import qualified Data.Text as T
17+import Language.Haskell.LSP.Test hiding (message, openDoc')
18+import qualified Language.Haskell.LSP.Test as LspTest
19+import Language.Haskell.LSP.Types
20+import Language.Haskell.LSP.Types.Lens as Lsp
21+import Test.Tasty.HUnit
22+
23+
24+-- | (0-based line number, 0-based column number)
25+type Cursor = (Int, Int)
26+
27+cursorPosition :: Cursor -> Position
28+cursorPosition (line, col) = Position line col
29+
30+requireDiagnostic :: List Diagnostic -> (DiagnosticSeverity, Cursor, T.Text) -> Assertion
31+requireDiagnostic actuals expected@(severity, cursor, expectedMsg) = do
32+ unless (any match actuals) $
33+ assertFailure $
34+ "Could not find " <> show expected <>
35+ " in " <> show actuals
36+ where
37+ match :: Diagnostic -> Bool
38+ match d =
39+ Just severity == _severity d
40+ && cursorPosition cursor == d ^. range . start
41+ && standardizeQuotes (T.toLower expectedMsg) `T.isInfixOf`
42+ standardizeQuotes (T.toLower $ d ^. message)
43+
44+expectDiagnostics :: [(FilePath, [(DiagnosticSeverity, Cursor, T.Text)])] -> Session ()
45+expectDiagnostics expected = do
46+ expected' <- Map.fromListWith (<>) <$> traverseOf (traverse . _1) (fmap toNormalizedUri . getDocUri) expected
47+ go expected'
48+ where
49+ go m
50+ | Map.null m = pure ()
51+ | otherwise = do
52+ diagsNot <- skipManyTill anyMessage LspTest.message :: Session PublishDiagnosticsNotification
53+ let fileUri = diagsNot ^. params . uri
54+ case Map.lookup (diagsNot ^. params . uri . to toNormalizedUri) m of
55+ Nothing -> liftIO $ assertFailure $
56+ "Got diagnostics for " <> show fileUri <>
57+ " but only expected diagnostics for " <> show (Map.keys m)
58+ Just expected -> do
59+ let actual = diagsNot ^. params . diagnostics
60+ liftIO $ mapM_ (requireDiagnostic actual) expected
61+ liftIO $ unless (length expected == length actual) $
62+ assertFailure $
63+ "Incorrect number of diagnostics for " <> show fileUri <>
64+ ", expected " <> show expected <>
65+ " but got " <> show actual
66+ go $ Map.delete (diagsNot ^. params . uri . to toNormalizedUri) m
67+
68+standardizeQuotes :: T.Text -> T.Text
69+standardizeQuotes msg = let
70+ repl '‘' = '\''
71+ repl '’' = '\''
72+ repl '`' = '\''
73+ repl c = c
74+ in T.map repl msg