@@ -20,17 +20,26 @@ module Test.Hls
20
20
defaultTestRunner ,
21
21
goldenGitDiff ,
22
22
goldenWithHaskellDoc ,
23
+ goldenWithHaskellDocInTmpDir ,
23
24
goldenWithHaskellAndCaps ,
25
+ goldenWithHaskellAndCapsInTmpDir ,
24
26
goldenWithCabalDoc ,
25
27
goldenWithHaskellDocFormatter ,
28
+ goldenWithHaskellDocFormatterInTmpDir ,
26
29
goldenWithCabalDocFormatter ,
30
+ goldenWithCabalDocFormatterInTmpDir ,
27
31
def ,
28
32
-- * Running HLS for integration tests
29
33
runSessionWithServer ,
30
34
runSessionWithServerAndCaps ,
31
35
runSessionWithServerFormatter ,
32
36
runSessionWithCabalServerFormatter ,
37
+ runSessionWithServerInTmpDir ,
38
+ runSessionWithServerAndCapsInTmpDir ,
39
+ runSessionWithServerFormatterInTmpDir ,
40
+ runSessionWithCabalServerFormatterInTmpDir ,
33
41
runSessionWithServer' ,
42
+ runSessionWithServerInTmpDir' ,
34
43
-- * Helpful re-exports
35
44
PluginDescriptor ,
36
45
IdeState ,
@@ -90,11 +99,13 @@ import GHC.Stack (emptyCallStack)
90
99
import GHC.TypeLits
91
100
import Ide.Logger (Doc , Logger (Logger ),
92
101
Pretty (pretty ),
93
- Priority (Debug ),
102
+ Priority (.. ),
94
103
Recorder (Recorder , logger_ ),
95
104
WithPriority (WithPriority , priority ),
96
105
cfilter , cmapWithPrio ,
97
- makeDefaultStderrRecorder )
106
+ logWith ,
107
+ makeDefaultStderrRecorder ,
108
+ (<+>) )
98
109
import Ide.Types
99
110
import Language.LSP.Protocol.Capabilities
100
111
import Language.LSP.Protocol.Message
@@ -105,9 +116,12 @@ import System.Directory (getCurrentDirectory,
105
116
setCurrentDirectory )
106
117
import System.Environment (lookupEnv )
107
118
import System.FilePath
119
+ import System.IO.Extra (newTempDir , withTempDir )
108
120
import System.IO.Unsafe (unsafePerformIO )
109
121
import System.Process.Extra (createPipe )
110
122
import System.Time.Extra
123
+ import qualified Test.Hls.FileSystem as FS
124
+ import Test.Hls.FileSystem
111
125
import Test.Hls.Util
112
126
import Test.Tasty hiding (Timeout )
113
127
import Test.Tasty.ExpectedFailure
@@ -116,11 +130,26 @@ import Test.Tasty.HUnit
116
130
import Test.Tasty.Ingredients.Rerun
117
131
import Test.Tasty.Runners (NumThreads (.. ))
118
132
119
- newtype Log = LogIDEMain IDEMain. Log
133
+ data Log
134
+ = LogIDEMain IDEMain. Log
135
+ | LogTestHarness LogTestHarness
120
136
121
137
instance Pretty Log where
122
138
pretty = \ case
123
- LogIDEMain log -> pretty log
139
+ LogIDEMain log -> pretty log
140
+ LogTestHarness log -> pretty log
141
+
142
+ data LogTestHarness
143
+ = LogTestDir FilePath
144
+ | LogCleanup
145
+ | LogNoCleanup
146
+
147
+
148
+ instance Pretty LogTestHarness where
149
+ pretty = \ case
150
+ LogTestDir dir -> " Test Project located in directory:" <+> pretty dir
151
+ LogCleanup -> " Cleaned up temporary directory"
152
+ LogNoCleanup -> " No cleanup of temporary directory"
124
153
125
154
-- | Run 'defaultMainWithRerun', limiting each single test case running at most 10 minutes
126
155
defaultTestRunner :: TestTree -> IO ()
@@ -144,6 +173,18 @@ goldenWithHaskellDoc
144
173
-> TestTree
145
174
goldenWithHaskellDoc = goldenWithDoc " haskell"
146
175
176
+ goldenWithHaskellDocInTmpDir
177
+ :: Pretty b
178
+ => PluginTestDescriptor b
179
+ -> TestName
180
+ -> VirtualFileTree
181
+ -> FilePath
182
+ -> FilePath
183
+ -> FilePath
184
+ -> (TextDocumentIdentifier -> Session () )
185
+ -> TestTree
186
+ goldenWithHaskellDocInTmpDir = goldenWithDocInTmpDir " haskell"
187
+
147
188
goldenWithHaskellAndCaps
148
189
:: Pretty b
149
190
=> ClientCapabilities
@@ -165,6 +206,27 @@ goldenWithHaskellAndCaps clientCaps plugin title testDataDir path desc ext act =
165
206
act doc
166
207
documentContents doc
167
208
209
+ goldenWithHaskellAndCapsInTmpDir
210
+ :: Pretty b
211
+ => ClientCapabilities
212
+ -> PluginTestDescriptor b
213
+ -> TestName
214
+ -> VirtualFileTree
215
+ -> FilePath
216
+ -> FilePath
217
+ -> FilePath
218
+ -> (TextDocumentIdentifier -> Session () )
219
+ -> TestTree
220
+ goldenWithHaskellAndCapsInTmpDir clientCaps plugin title tree path desc ext act =
221
+ goldenGitDiff title (vftTestDataRoot tree </> path <.> desc <.> ext)
222
+ $ runSessionWithServerAndCapsInTmpDir plugin clientCaps tree
223
+ $ TL. encodeUtf8 . TL. fromStrict
224
+ <$> do
225
+ doc <- openDoc (path <.> ext) " haskell"
226
+ void waitForBuildQueue
227
+ act doc
228
+ documentContents doc
229
+
168
230
goldenWithCabalDoc
169
231
:: Pretty b
170
232
=> PluginTestDescriptor b
@@ -198,6 +260,27 @@ goldenWithDoc fileType plugin title testDataDir path desc ext act =
198
260
act doc
199
261
documentContents doc
200
262
263
+ goldenWithDocInTmpDir
264
+ :: Pretty b
265
+ => T. Text
266
+ -> PluginTestDescriptor b
267
+ -> TestName
268
+ -> VirtualFileTree
269
+ -> FilePath
270
+ -> FilePath
271
+ -> FilePath
272
+ -> (TextDocumentIdentifier -> Session () )
273
+ -> TestTree
274
+ goldenWithDocInTmpDir fileType plugin title tree path desc ext act =
275
+ goldenGitDiff title (vftTestDataRoot tree </> path <.> desc <.> ext)
276
+ $ runSessionWithServerInTmpDir plugin tree
277
+ $ TL. encodeUtf8 . TL. fromStrict
278
+ <$> do
279
+ doc <- openDoc (path <.> ext) fileType
280
+ void waitForBuildQueue
281
+ act doc
282
+ documentContents doc
283
+
201
284
-- ------------------------------------------------------------
202
285
-- Helper function for initialising plugins under test
203
286
-- ------------------------------------------------------------
@@ -308,6 +391,90 @@ runSessionWithServerFormatter plugin formatter conf fp act = do
308
391
fp
309
392
act
310
393
394
+ runSessionWithServerInTmpDir :: Pretty b => PluginTestDescriptor b -> VirtualFileTree -> Session a -> IO a
395
+ runSessionWithServerInTmpDir plugin tree act = do
396
+ recorder <- pluginTestRecorder
397
+ runSessionWithServerInTmpDir' (plugin recorder) def def fullCaps tree act
398
+
399
+ runSessionWithServerAndCapsInTmpDir :: Pretty b => PluginTestDescriptor b -> ClientCapabilities -> VirtualFileTree -> Session a -> IO a
400
+ runSessionWithServerAndCapsInTmpDir plugin caps tree act = do
401
+ recorder <- pluginTestRecorder
402
+ runSessionWithServerInTmpDir' (plugin recorder) def def caps tree act
403
+
404
+ runSessionWithServerFormatterInTmpDir :: Pretty b => PluginTestDescriptor b -> String -> PluginConfig -> VirtualFileTree -> Session a -> IO a
405
+ runSessionWithServerFormatterInTmpDir plugin formatter conf tree act = do
406
+ recorder <- pluginTestRecorder
407
+ runSessionWithServerInTmpDir'
408
+ (plugin recorder)
409
+ def
410
+ { formattingProvider = T. pack formatter
411
+ , plugins = M. singleton (PluginId $ T. pack formatter) conf
412
+ }
413
+ def
414
+ fullCaps
415
+ tree
416
+ act
417
+
418
+ -- | Host a server, and run a test session on it.
419
+ --
420
+ -- Creates a temporary directory, and materializes the VirtualFileTree
421
+ -- in the temporary directory.
422
+ --
423
+ -- To debug test cases and verify the file system is correctly set up,
424
+ -- you should set the environment variable 'HLS_TEST_HARNESS_NO_TESTDIR_CLEANUP=1'.
425
+ -- Further, we log the temporary directory location on startup. To view
426
+ -- the logs, set the environment variable 'HLS_TEST_HARNESS_STDERR=1'.
427
+ --
428
+ -- Example invocation to debug test cases:
429
+ --
430
+ -- @
431
+ -- HLS_TEST_HARNESS_NO_TESTDIR_CLEANUP=1 HLS_TEST_HARNESS_STDERR=1 cabal test <plugin-name>
432
+ -- @
433
+ --
434
+ -- Don't forget to use 'TASTY_PATTERN' to debug only a subset of tests.
435
+ --
436
+ -- For plugin test logs, look at the documentation of 'mkPluginTestDescriptor'.
437
+ --
438
+ -- Note: cwd will be shifted into a temporary directory in @Session a@
439
+ runSessionWithServerInTmpDir' ::
440
+ -- | Plugins to load on the server.
441
+ --
442
+ -- For improved logging, make sure these plugins have been initalised with
443
+ -- the recorder produced by @pluginTestRecorder@.
444
+ IdePlugins IdeState ->
445
+ -- | lsp config for the server
446
+ Config ->
447
+ -- | config for the test session
448
+ SessionConfig ->
449
+ ClientCapabilities ->
450
+ VirtualFileTree ->
451
+ Session a ->
452
+ IO a
453
+ runSessionWithServerInTmpDir' plugins conf sessConf caps tree act = withLock lockForTempDirs $ do
454
+ (recorder, _) <- initialiseTestRecorder
455
+ [" LSP_TEST_LOG_STDERR" , " HLS_TEST_HARNESS_STDERR" , " HLS_TEST_LOG_STDERR" ]
456
+
457
+ -- Do not clean up the temporary directory if this variable is set to anything but '0'.
458
+ -- Aids debugging.
459
+ cleanupTempDir <- lookupEnv " HLS_TEST_HARNESS_NO_TESTDIR_CLEANUP"
460
+ let runTestInDir = case cleanupTempDir of
461
+ Just val
462
+ | val /= " 0" -> \ action -> do
463
+ (tempDir, _) <- newTempDir
464
+ a <- action tempDir
465
+ logWith recorder Debug $ LogNoCleanup
466
+ pure a
467
+
468
+ _ -> \ action -> do
469
+ a <- withTempDir action
470
+ logWith recorder Debug $ LogCleanup
471
+ pure a
472
+
473
+ runTestInDir $ \ tmpDir -> do
474
+ logWith recorder Info $ LogTestDir tmpDir
475
+ _fs <- FS. materialiseVFT tmpDir tree
476
+ runSessionWithServer' plugins conf sessConf caps tmpDir act
477
+
311
478
goldenWithHaskellDocFormatter
312
479
:: Pretty b
313
480
=> PluginTestDescriptor b -- ^ Formatter plugin to be used
@@ -352,6 +519,50 @@ goldenWithCabalDocFormatter plugin formatter conf title testDataDir path desc ex
352
519
act doc
353
520
documentContents doc
354
521
522
+ goldenWithHaskellDocFormatterInTmpDir
523
+ :: Pretty b
524
+ => PluginTestDescriptor b -- ^ Formatter plugin to be used
525
+ -> String -- ^ Name of the formatter to be used
526
+ -> PluginConfig
527
+ -> TestName -- ^ Title of the test
528
+ -> VirtualFileTree -- ^ Virtual representation of the test project
529
+ -> FilePath -- ^ Path to the testdata to be used within the directory
530
+ -> FilePath -- ^ Additional suffix to be appended to the output file
531
+ -> FilePath -- ^ Extension of the output file
532
+ -> (TextDocumentIdentifier -> Session () )
533
+ -> TestTree
534
+ goldenWithHaskellDocFormatterInTmpDir plugin formatter conf title tree path desc ext act =
535
+ goldenGitDiff title (vftTestDataRoot tree </> path <.> desc <.> ext)
536
+ $ runSessionWithServerFormatterInTmpDir plugin formatter conf tree
537
+ $ TL. encodeUtf8 . TL. fromStrict
538
+ <$> do
539
+ doc <- openDoc (path <.> ext) " haskell"
540
+ void waitForBuildQueue
541
+ act doc
542
+ documentContents doc
543
+
544
+ goldenWithCabalDocFormatterInTmpDir
545
+ :: Pretty b
546
+ => PluginTestDescriptor b -- ^ Formatter plugin to be used
547
+ -> String -- ^ Name of the formatter to be used
548
+ -> PluginConfig
549
+ -> TestName -- ^ Title of the test
550
+ -> VirtualFileTree -- ^ Virtual representation of the test project
551
+ -> FilePath -- ^ Path to the testdata to be used within the directory
552
+ -> FilePath -- ^ Additional suffix to be appended to the output file
553
+ -> FilePath -- ^ Extension of the output file
554
+ -> (TextDocumentIdentifier -> Session () )
555
+ -> TestTree
556
+ goldenWithCabalDocFormatterInTmpDir plugin formatter conf title tree path desc ext act =
557
+ goldenGitDiff title (vftTestDataRoot tree </> path <.> desc <.> ext)
558
+ $ runSessionWithCabalServerFormatterInTmpDir plugin formatter conf tree
559
+ $ TL. encodeUtf8 . TL. fromStrict
560
+ <$> do
561
+ doc <- openDoc (path <.> ext) " cabal"
562
+ void waitForBuildQueue
563
+ act doc
564
+ documentContents doc
565
+
355
566
runSessionWithCabalServerFormatter :: Pretty b => PluginTestDescriptor b -> String -> PluginConfig -> FilePath -> Session a -> IO a
356
567
runSessionWithCabalServerFormatter plugin formatter conf fp act = do
357
568
recorder <- pluginTestRecorder
@@ -363,7 +574,22 @@ runSessionWithCabalServerFormatter plugin formatter conf fp act = do
363
574
}
364
575
def
365
576
fullCaps
366
- fp act
577
+ fp
578
+ act
579
+
580
+ runSessionWithCabalServerFormatterInTmpDir :: Pretty b => PluginTestDescriptor b -> String -> PluginConfig -> VirtualFileTree -> Session a -> IO a
581
+ runSessionWithCabalServerFormatterInTmpDir plugin formatter conf tree act = do
582
+ recorder <- pluginTestRecorder
583
+ runSessionWithServerInTmpDir'
584
+ (plugin recorder)
585
+ def
586
+ { cabalFormattingProvider = T. pack formatter
587
+ , plugins = M. singleton (PluginId $ T. pack formatter) conf
588
+ }
589
+ def
590
+ fullCaps
591
+ tree
592
+ act
367
593
368
594
-- | Restore cwd after running an action
369
595
keepCurrentDirectory :: IO a -> IO a
@@ -374,6 +600,12 @@ keepCurrentDirectory = bracket getCurrentDirectory setCurrentDirectory . const
374
600
lock :: Lock
375
601
lock = unsafePerformIO newLock
376
602
603
+
604
+ {-# NOINLINE lockForTempDirs #-}
605
+ -- | Never run in parallel
606
+ lockForTempDirs :: Lock
607
+ lockForTempDirs = unsafePerformIO newLock
608
+
377
609
-- | Host a server, and run a test session on it
378
610
-- Note: cwd will be shifted into @root@ in @Session a@
379
611
runSessionWithServer' ::
@@ -390,7 +622,7 @@ runSessionWithServer' ::
390
622
FilePath ->
391
623
Session a ->
392
624
IO a
393
- runSessionWithServer' plugins conf sconf caps root s = withLock lock $ keepCurrentDirectory $ do
625
+ runSessionWithServer' plugins conf sconf caps root s = withLock lock $ keepCurrentDirectory $ do
394
626
(inR, inW) <- createPipe
395
627
(outR, outW) <- createPipe
396
628
0 commit comments