@@ -20,17 +20,26 @@ module Test.Hls
2020 defaultTestRunner ,
2121 goldenGitDiff ,
2222 goldenWithHaskellDoc ,
23+ goldenWithHaskellDocInTmpDir ,
2324 goldenWithHaskellAndCaps ,
25+ goldenWithHaskellAndCapsInTmpDir ,
2426 goldenWithCabalDoc ,
2527 goldenWithHaskellDocFormatter ,
28+ goldenWithHaskellDocFormatterInTmpDir ,
2629 goldenWithCabalDocFormatter ,
30+ goldenWithCabalDocFormatterInTmpDir ,
2731 def ,
2832 -- * Running HLS for integration tests
2933 runSessionWithServer ,
3034 runSessionWithServerAndCaps ,
3135 runSessionWithServerFormatter ,
3236 runSessionWithCabalServerFormatter ,
37+ runSessionWithServerInTmpDir ,
38+ runSessionWithServerAndCapsInTmpDir ,
39+ runSessionWithServerFormatterInTmpDir ,
40+ runSessionWithCabalServerFormatterInTmpDir ,
3341 runSessionWithServer' ,
42+ runSessionWithServerInTmpDir' ,
3443 -- * Helpful re-exports
3544 PluginDescriptor ,
3645 IdeState ,
@@ -90,11 +99,13 @@ import GHC.Stack (emptyCallStack)
9099import GHC.TypeLits
91100import Ide.Logger (Doc , Logger (Logger ),
92101 Pretty (pretty ),
93- Priority (Debug ),
102+ Priority (.. ),
94103 Recorder (Recorder , logger_ ),
95104 WithPriority (WithPriority , priority ),
96105 cfilter , cmapWithPrio ,
97- makeDefaultStderrRecorder )
106+ logWith ,
107+ makeDefaultStderrRecorder ,
108+ (<+>) )
98109import Ide.Types
99110import Language.LSP.Protocol.Capabilities
100111import Language.LSP.Protocol.Message
@@ -105,9 +116,12 @@ import System.Directory (getCurrentDirectory,
105116 setCurrentDirectory )
106117import System.Environment (lookupEnv )
107118import System.FilePath
119+ import System.IO.Extra (newTempDir , withTempDir )
108120import System.IO.Unsafe (unsafePerformIO )
109121import System.Process.Extra (createPipe )
110122import System.Time.Extra
123+ import qualified Test.Hls.FileSystem as FS
124+ import Test.Hls.FileSystem
111125import Test.Hls.Util
112126import Test.Tasty hiding (Timeout )
113127import Test.Tasty.ExpectedFailure
@@ -116,11 +130,26 @@ import Test.Tasty.HUnit
116130import Test.Tasty.Ingredients.Rerun
117131import Test.Tasty.Runners (NumThreads (.. ))
118132
119- newtype Log = LogIDEMain IDEMain. Log
133+ data Log
134+ = LogIDEMain IDEMain. Log
135+ | LogTestHarness LogTestHarness
120136
121137instance Pretty Log where
122138 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"
124153
125154-- | Run 'defaultMainWithRerun', limiting each single test case running at most 10 minutes
126155defaultTestRunner :: TestTree -> IO ()
@@ -144,6 +173,18 @@ goldenWithHaskellDoc
144173 -> TestTree
145174goldenWithHaskellDoc = goldenWithDoc " haskell"
146175
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+
147188goldenWithHaskellAndCaps
148189 :: Pretty b
149190 => ClientCapabilities
@@ -165,6 +206,27 @@ goldenWithHaskellAndCaps clientCaps plugin title testDataDir path desc ext act =
165206 act doc
166207 documentContents doc
167208
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 (vftOriginalRoot 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+
168230goldenWithCabalDoc
169231 :: Pretty b
170232 => PluginTestDescriptor b
@@ -198,6 +260,27 @@ goldenWithDoc fileType plugin title testDataDir path desc ext act =
198260 act doc
199261 documentContents doc
200262
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 (vftOriginalRoot 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+
201284-- ------------------------------------------------------------
202285-- Helper function for initialising plugins under test
203286-- ------------------------------------------------------------
@@ -308,6 +391,90 @@ runSessionWithServerFormatter plugin formatter conf fp act = do
308391 fp
309392 act
310393
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+
311478goldenWithHaskellDocFormatter
312479 :: Pretty b
313480 => PluginTestDescriptor b -- ^ Formatter plugin to be used
@@ -352,6 +519,50 @@ goldenWithCabalDocFormatter plugin formatter conf title testDataDir path desc ex
352519 act doc
353520 documentContents doc
354521
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 (vftOriginalRoot 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 (vftOriginalRoot 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+
355566runSessionWithCabalServerFormatter :: Pretty b => PluginTestDescriptor b -> String -> PluginConfig -> FilePath -> Session a -> IO a
356567runSessionWithCabalServerFormatter plugin formatter conf fp act = do
357568 recorder <- pluginTestRecorder
@@ -363,7 +574,22 @@ runSessionWithCabalServerFormatter plugin formatter conf fp act = do
363574 }
364575 def
365576 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
367593
368594-- | Restore cwd after running an action
369595keepCurrentDirectory :: IO a -> IO a
@@ -374,6 +600,12 @@ keepCurrentDirectory = bracket getCurrentDirectory setCurrentDirectory . const
374600lock :: Lock
375601lock = unsafePerformIO newLock
376602
603+
604+ {-# NOINLINE lockForTempDirs #-}
605+ -- | Never run in parallel
606+ lockForTempDirs :: Lock
607+ lockForTempDirs = unsafePerformIO newLock
608+
377609-- | Host a server, and run a test session on it
378610-- Note: cwd will be shifted into @root@ in @Session a@
379611runSessionWithServer' ::
@@ -390,7 +622,7 @@ runSessionWithServer' ::
390622 FilePath ->
391623 Session a ->
392624 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
394626 (inR, inW) <- createPipe
395627 (outR, outW) <- createPipe
396628
0 commit comments