forked from haskell/haskell-language-server
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathMain.hs
235 lines (219 loc) · 13.4 KB
/
Main.hs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE ViewPatterns #-}
module Main
( main
) where
import Control.Lens (_Just, preview, toListOf, view)
import Data.Aeson (fromJSON)
import Data.Aeson.Types (Result (Success))
import Data.List (isInfixOf)
import Data.List.Extra (nubOrdOn)
import qualified Ide.Plugin.Eval as Eval
import Ide.Plugin.Eval.Types (EvalParams (..), Section (..),
testOutput)
import Language.LSP.Types.Lens (arguments, command, range, title)
import System.FilePath ((</>))
import Test.Hls
import qualified Data.Text as T
main :: IO ()
main = defaultTestRunner tests
evalPlugin :: PluginDescriptor IdeState
evalPlugin = Eval.descriptor "eval"
tests :: TestTree
tests =
testGroup "eval"
[ testCase "Produces Evaluate code lenses" $
runSessionWithServer evalPlugin testDataDir $ do
doc <- openDoc "T1.hs" "haskell"
lenses <- getCodeLenses doc
liftIO $ map (preview $ command . _Just . title) lenses @?= [Just "Evaluate..."]
, testCase "Produces Refresh code lenses" $
runSessionWithServer evalPlugin testDataDir $ do
doc <- openDoc "T2.hs" "haskell"
lenses <- getCodeLenses doc
liftIO $ map (preview $ command . _Just . title) lenses @?= [Just "Refresh..."]
, testCase "Code lenses have ranges" $
runSessionWithServer evalPlugin testDataDir $ do
doc <- openDoc "T1.hs" "haskell"
lenses <- getCodeLenses doc
liftIO $ map (view range) lenses @?= [Range (Position 4 0) (Position 5 0)]
, testCase "Multi-line expressions have a multi-line range" $ do
runSessionWithServer evalPlugin testDataDir $ do
doc <- openDoc "T3.hs" "haskell"
lenses <- getCodeLenses doc
liftIO $ map (view range) lenses @?= [Range (Position 3 0) (Position 5 0)]
, testCase "Executed expressions range covers only the expression" $ do
runSessionWithServer evalPlugin testDataDir $ do
doc <- openDoc "T2.hs" "haskell"
lenses <- getCodeLenses doc
liftIO $ map (view range) lenses @?= [Range (Position 4 0) (Position 5 0)]
, goldenWithEval "Evaluation of expressions" "T1" "hs"
, goldenWithEval "Reevaluation of expressions" "T2" "hs"
, goldenWithEval "Evaluation of expressions w/ imports" "T3" "hs"
, goldenWithEval "Evaluation of expressions w/ lets" "T4" "hs"
, goldenWithEval "Refresh an evaluation" "T5" "hs"
, goldenWithEval "Refresh an evaluation w/ lets" "T6" "hs"
, goldenWithEval "Refresh a multiline evaluation" "T7" "hs"
, testCase "Semantic and Lexical errors are reported" $ do
evalInFile "T8.hs" "-- >>> noFunctionWithThisName" "-- Variable not in scope: noFunctionWithThisName"
evalInFile "T8.hs" "-- >>> \"a\" + \"bc\"" $
if ghcVersion == GHC901
then "-- No instance for (Num String) arising from a use of ‘+’"
else "-- No instance for (Num [Char]) arising from a use of ‘+’"
evalInFile "T8.hs" "-- >>> \"" "-- lexical error in string/character literal at end of input"
evalInFile "T8.hs" "-- >>> 3 `div` 0" "-- divide by zero"
, goldenWithEval "Applies file LANGUAGE extensions" "T9" "hs"
, goldenWithEval "Evaluate a type with :kind!" "T10" "hs"
, goldenWithEval "Reports an error for an incorrect type with :kind!" "T11" "hs"
, goldenWithEval "Shows a kind with :kind" "T12" "hs"
, goldenWithEval "Reports an error for an incorrect type with :kind" "T13" "hs"
, goldenWithEval "Returns a fully-instantiated type for :type" "T14" "hs"
, goldenWithEval "Returns an uninstantiated type for :type +v, admitting multiple whitespaces around arguments" "T15" "hs"
, goldenWithEval "Returns defaulted type for :type +d, admitting multiple whitespaces around arguments" "T16" "hs"
, goldenWithEval ":type reports an error when given with unknown +x option" "T17" "hs"
, goldenWithEval "Reports an error when given with unknown command" "T18" "hs"
, goldenWithEval "Returns defaulted type for :type +d reflecting the default declaration specified in the >>> prompt" "T19" "hs"
, expectFailBecause "known issue - see a note in P.R. #361" $
goldenWithEval ":type +d reflects the `default' declaration of the module" "T20" "hs"
, testCase ":type handles a multilined result properly" $
evalInFile "T21.hs" "-- >>> :type fun" $ T.unlines [
"-- fun",
if ghcVersion == GHC901
then "-- :: forall {k1} {k2 :: Nat} {n :: Nat} {a :: k1}."
else "-- :: forall k1 (k2 :: Nat) (n :: Nat) (a :: k1).",
"-- (KnownNat k2, KnownNat n, Typeable a) =>",
"-- Proxy k2 -> Proxy n -> Proxy a -> ()"
]
, goldenWithEval ":t behaves exactly the same as :type" "T22" "hs"
, testCase ":type does \"dovetails\" for short identifiers" $
evalInFile "T23.hs" "-- >>> :type f" $ T.unlines [
if ghcVersion == GHC901
then "-- f :: forall {k1} {k2 :: Nat} {n :: Nat} {a :: k1}."
else "-- f :: forall k1 (k2 :: Nat) (n :: Nat) (a :: k1).",
"-- (KnownNat k2, KnownNat n, Typeable a) =>",
"-- Proxy k2 -> Proxy n -> Proxy a -> ()"
]
, goldenWithEval ":kind! treats a multilined result properly" "T24" "hs"
, goldenWithEval ":kind treats a multilined result properly" "T25" "hs"
, goldenWithEval "local imports" "T26" "hs"
, goldenWithEval "Preserves one empty comment line after prompt" "T27" "hs"
, goldenWithEval "Multi line comments" "TMulti" "hs"
, goldenWithEval "Multi line comments, with the last test line ends without newline" "TEndingMulti" "hs"
, goldenWithEval "Evaluate expressions in Plain comments in both single line and multi line format" "TPlainComment" "hs"
, goldenWithEval "Evaluate expressions in Haddock comments in both single line and multi line format" "THaddock" "hs"
, goldenWithEval "Compare results (for Haddock tests only)" "TCompare" "hs"
, goldenWithEval "Local Modules imports are accessible in a test" "TLocalImport" "hs"
-- , goldenWithEval "Local Modules can be imported in a test" "TLocalImportInTest" "hs"
, goldenWithEval "Setting language option TupleSections" "TLanguageOptionsTupleSections" "hs"
, goldenWithEval ":set accepts ghci flags" "TFlags" "hs"
, testCase ":set -fprint-explicit-foralls works" $ do
evalInFile "T8.hs" "-- >>> :t id" "-- id :: a -> a"
evalInFile "T8.hs" "-- >>> :set -fprint-explicit-foralls\n-- >>> :t id"
"-- id :: forall {a}. a -> a"
, goldenWithEval "The default language extensions for the eval plugin are the same as those for ghci" "TSameDefaultLanguageExtensionsAsGhci" "hs"
, goldenWithEval "IO expressions are supported, stdout/stderr output is ignored" "TIO" "hs"
, goldenWithEval "Property checking" "TProperty" "hs"
, goldenWithEval "Prelude has no special treatment, it is imported as stated in the module" "TPrelude" "hs"
, goldenWithEval "Don't panic on {-# UNPACK #-} pragma" "TUNPACK" "hs"
, goldenWithEval "Can handle eval inside nested comment properly" "TNested" "hs"
, goldenWithEval "Test on last line insert results correctly" "TLastLine" "hs"
, testGroup "with preprocessors"
[ knownBrokenInEnv [HostOS Windows, GhcVer GHC84, GhcVer GHC86]
"CPP eval on Windows and/or GHC <= 8.6 fails for some reasons" $
goldenWithEval "CPP support" "TCPP" "hs"
, knownBrokenForGhcVersions [GHC84, GHC86]
"Preprocessor known to fail on GHC <= 8.6" $
goldenWithEval "Literate Haskell Bird Style" "TLHS" "lhs"
-- , goldenWithEval "Literate Haskell LaTeX Style" "TLHSLateX" "lhs"
]
, goldenWithEval "Works with NoImplicitPrelude" "TNoImplicitPrelude" "hs"
, goldenWithEval "Variable 'it' works" "TIt" "hs"
, testGroup ":info command"
[ testCase ":info reports type, constructors and instances" $ do
[output] <- map (unlines . codeLensTestOutput) <$> evalLenses "TInfo.hs"
"data Foo = Foo1 | Foo2" `isInfixOf` output @? "Output does not include Foo data declaration"
"Eq Foo" `isInfixOf` output @? "Output does not include instance Eq Foo"
"Ord Foo" `isInfixOf` output @? "Output does not include instance Ord Foo"
not ("Baz Foo" `isInfixOf` output) @? "Output includes instance Baz Foo"
, testCase ":info reports type, constructors and instances for multiple types" $ do
[output] <- map (unlines . codeLensTestOutput) <$> evalLenses "TInfoMany.hs"
"data Foo = Foo1 | Foo2" `isInfixOf` output @? "Output does not include Foo data declaration"
"Eq Foo" `isInfixOf` output @? "Output does not include instance Eq Foo"
"Ord Foo" `isInfixOf` output @? "Output does not include instance Ord Foo"
not ("Baz Foo" `isInfixOf` output) @? "Output includes instance Baz Foo"
"data Bar = Bar1 | Bar2 | Bar3" `isInfixOf` output @? "Output does not include Bar data declaration"
"Eq Bar" `isInfixOf` output @? "Output does not include instance Eq Bar"
"Ord Bar" `isInfixOf` output @? "Output does not include instance Ord Bar"
not ("Baz Bar" `isInfixOf` output) @? "Output includes instance Baz Bar"
, testCase ":info! reports type, constructors and unfiltered instances" $ do
[output] <- map (unlines . codeLensTestOutput) <$> evalLenses "TInfoBang.hs"
"data Foo = Foo1 | Foo2" `isInfixOf` output @? "Output does not include Foo data declaration"
"Eq Foo" `isInfixOf` output @? "Output does not include instance Eq Foo"
"Ord Foo" `isInfixOf` output @? "Output does not include instance Ord Foo"
"Baz Foo" `isInfixOf` output @? "Output does not include instance Baz Foo"
, testCase ":info! reports type, constructors and unfiltered instances for multiple types" $ do
[output] <- map (unlines . codeLensTestOutput) <$> evalLenses "TInfoBangMany.hs"
"data Foo = Foo1 | Foo2" `isInfixOf` output @? "Output does not include Foo data declaration"
"Eq Foo" `isInfixOf` output @? "Output does not include instance Eq Foo"
"Ord Foo" `isInfixOf` output @? "Output does not include instance Ord Foo"
"Baz Foo" `isInfixOf` output @? "Output does not include instance Baz Foo"
"data Bar = Bar1 | Bar2 | Bar3" `isInfixOf` output @? "Output does not include Bar data declaration"
"Eq Bar" `isInfixOf` output @? "Output does not include instance Eq Bar"
"Ord Bar" `isInfixOf` output @? "Output does not include instance Ord Bar"
"Baz Bar" `isInfixOf` output @? "Output does not include instance Baz Bar"
, testCase ":i behaves exactly the same as :info" $ do
[output] <- map (unlines . codeLensTestOutput) <$> evalLenses "TI_Info.hs"
"data Foo = Foo1 | Foo2" `isInfixOf` output @? "Output does not include Foo data declaration"
"Eq Foo" `isInfixOf` output @? "Output does not include instance Eq Foo"
"Ord Foo" `isInfixOf` output @? "Output does not include instance Ord Foo"
not ("Baz Foo" `isInfixOf` output) @? "Output includes instance Baz Foo"
]
]
goldenWithEval :: TestName -> FilePath -> FilePath -> TestTree
goldenWithEval title path ext =
goldenWithHaskellDoc evalPlugin title testDataDir path "expected" ext executeLensesBackwards
-- | Execute lenses backwards, to avoid affecting their position in the source file
executeLensesBackwards :: TextDocumentIdentifier -> Session ()
executeLensesBackwards doc = do
codeLenses <- reverse <$> getCodeLenses doc
-- liftIO $ print codeLenses
-- Execute sequentially, nubbing elements to avoid
-- evaluating the same section with multiple tests
-- more than twice
mapM_ executeCmd $
nubOrdOn actSectionId [c | CodeLens{_command = Just c} <- codeLenses]
actSectionId :: Command -> Int
actSectionId Command{_arguments = Just (List [fromJSON -> Success EvalParams{..}])} = evalId
actSectionId _ = error "Invalid CodeLens"
-- Execute command and wait for result
executeCmd :: Command -> Session ()
executeCmd cmd = do
executeCommand cmd
_ <- skipManyTill anyMessage (message SWorkspaceApplyEdit)
-- liftIO $ print _resp
pure ()
evalLenses :: FilePath -> IO [CodeLens]
evalLenses path = runSessionWithServer evalPlugin testDataDir $ do
doc <- openDoc path "haskell"
executeLensesBackwards doc
getCodeLenses doc
codeLensTestOutput :: CodeLens -> [String]
codeLensTestOutput codeLens = do
CodeLens { _command = Just command } <- [codeLens]
Command { _arguments = Just (List args) } <- [command]
Success EvalParams { sections = sections } <- fromJSON @EvalParams <$> args
Section { sectionTests = sectionTests } <- sections
testOutput =<< sectionTests
testDataDir :: FilePath
testDataDir = "test" </> "testdata"
evalInFile :: FilePath -> T.Text -> T.Text -> IO ()
evalInFile fp e expected = runSessionWithServer evalPlugin testDataDir $ do
doc <- openDoc fp "haskell"
origin <- documentContents doc
let withEval = origin <> e
changeDoc doc [TextDocumentContentChangeEvent Nothing Nothing withEval]
executeLensesBackwards doc
result <- fmap T.strip . T.stripPrefix withEval <$> documentContents doc
liftIO $ result @?= Just (T.strip expected)