@@ -34,6 +34,8 @@ module Test.Hls
34
34
runSessionWithServer ,
35
35
runSessionWithServerInTmpDir ,
36
36
runSessionWithTestConfig ,
37
+ -- * Running parameterised tests for a set of test configurations
38
+ parameterisedCursorTest ,
37
39
-- * Helpful re-exports
38
40
PluginDescriptor ,
39
41
IdeState ,
@@ -64,74 +66,76 @@ module Test.Hls
64
66
where
65
67
66
68
import Control.Applicative.Combinators
67
- import Control.Concurrent.Async (async , cancel , wait )
69
+ import Control.Concurrent.Async (async , cancel , wait )
68
70
import Control.Concurrent.Extra
69
71
import Control.Exception.Safe
70
- import Control.Lens.Extras (is )
71
- import Control.Monad (guard , unless , void )
72
- import Control.Monad.Extra (forM )
72
+ import Control.Lens.Extras (is )
73
+ import Control.Monad (guard , unless , void )
74
+ import Control.Monad.Extra (forM )
73
75
import Control.Monad.IO.Class
74
- import Data.Aeson (Result (Success ),
75
- Value (Null ), fromJSON ,
76
- toJSON )
77
- import qualified Data.Aeson as A
78
- import Data.ByteString.Lazy (ByteString )
79
- import Data.Default (Default , def )
80
- import qualified Data.Map as M
81
- import Data.Maybe (fromMaybe )
82
- import Data.Proxy (Proxy (Proxy ))
83
- import qualified Data.Text as T
84
- import qualified Data.Text.Lazy as TL
85
- import qualified Data.Text.Lazy.Encoding as TL
86
- import Development.IDE (IdeState ,
87
- LoggingColumn (ThreadIdColumn ),
88
- defaultLayoutOptions ,
89
- layoutPretty , renderStrict )
90
- import qualified Development.IDE.LSP.Notifications as Notifications
91
- import Development.IDE.Main hiding (Log )
92
- import qualified Development.IDE.Main as IDEMain
93
- import Development.IDE.Plugin.Test (TestRequest (GetBuildKeysBuilt , WaitForIdeRule , WaitForShakeQueue ),
94
- WaitForIdeRuleResult (ideResultSuccess ))
95
- import qualified Development.IDE.Plugin.Test as Test
76
+ import Data.Aeson (Result (Success ),
77
+ Value (Null ),
78
+ fromJSON , toJSON )
79
+ import qualified Data.Aeson as A
80
+ import Data.ByteString.Lazy (ByteString )
81
+ import Data.Default (Default , def )
82
+ import qualified Data.Map as M
83
+ import Data.Maybe (fromMaybe )
84
+ import Data.Proxy (Proxy (Proxy ))
85
+ import qualified Data.Text as T
86
+ import qualified Data.Text.Lazy as TL
87
+ import qualified Data.Text.Lazy.Encoding as TL
88
+ import Development.IDE (IdeState ,
89
+ LoggingColumn (ThreadIdColumn ),
90
+ defaultLayoutOptions ,
91
+ layoutPretty ,
92
+ renderStrict )
93
+ import Development.IDE.Main hiding (Log )
94
+ import qualified Development.IDE.Main as IDEMain
95
+ import Development.IDE.Plugin.Completions.Types (PosPrefixInfo )
96
+ import Development.IDE.Plugin.Test (TestRequest (GetBuildKeysBuilt , WaitForIdeRule , WaitForShakeQueue ),
97
+ WaitForIdeRuleResult (ideResultSuccess ))
98
+ import qualified Development.IDE.Plugin.Test as Test
96
99
import Development.IDE.Types.Options
97
100
import GHC.IO.Handle
98
101
import GHC.TypeLits
99
- import Ide.Logger (Pretty (pretty ),
100
- Priority (.. ), Recorder ,
101
- WithPriority (WithPriority , priority ),
102
- cfilter , cmapWithPrio ,
103
- defaultLoggingColumns ,
104
- logWith ,
105
- makeDefaultStderrRecorder ,
106
- (<+>) )
107
- import qualified Ide.Logger as Logger
108
- import Ide.Plugin.Properties ((&) )
109
- import Ide.PluginUtils (idePluginsToPluginDesc ,
110
- pluginDescToIdePlugins )
102
+ import Ide.Logger (Pretty (pretty ),
103
+ Priority (.. ),
104
+ Recorder ,
105
+ WithPriority (WithPriority , priority ),
106
+ cfilter ,
107
+ cmapWithPrio ,
108
+ defaultLoggingColumns ,
109
+ logWith ,
110
+ makeDefaultStderrRecorder ,
111
+ (<+>) )
112
+ import qualified Ide.Logger as Logger
113
+ import Ide.PluginUtils (idePluginsToPluginDesc ,
114
+ pluginDescToIdePlugins )
111
115
import Ide.Types
112
116
import Language.LSP.Protocol.Capabilities
113
117
import Language.LSP.Protocol.Message
114
- import qualified Language.LSP.Protocol.Message as LSP
115
- import Language.LSP.Protocol.Types hiding (Null )
116
- import qualified Language.LSP.Server as LSP
118
+ import qualified Language.LSP.Protocol.Message as LSP
119
+ import Language.LSP.Protocol.Types hiding (Null )
120
+ import qualified Language.LSP.Server as LSP
117
121
import Language.LSP.Test
118
- import Prelude hiding (log )
119
- import System.Directory (canonicalizePath ,
120
- createDirectoryIfMissing ,
121
- getCurrentDirectory ,
122
- getTemporaryDirectory ,
123
- makeAbsolute ,
124
- setCurrentDirectory )
125
- import System.Environment (lookupEnv , setEnv )
122
+ import Prelude hiding (log )
123
+ import System.Directory (canonicalizePath ,
124
+ createDirectoryIfMissing ,
125
+ getCurrentDirectory ,
126
+ getTemporaryDirectory ,
127
+ makeAbsolute ,
128
+ setCurrentDirectory )
129
+ import System.Environment (lookupEnv , setEnv )
126
130
import System.FilePath
127
- import System.IO.Extra (newTempDirWithin )
128
- import System.IO.Unsafe (unsafePerformIO )
129
- import System.Process.Extra (createPipe )
131
+ import System.IO.Extra (newTempDirWithin )
132
+ import System.IO.Unsafe (unsafePerformIO )
133
+ import System.Process.Extra (createPipe )
130
134
import System.Time.Extra
131
- import qualified Test.Hls.FileSystem as FS
135
+ import qualified Test.Hls.FileSystem as FS
132
136
import Test.Hls.FileSystem
133
137
import Test.Hls.Util
134
- import Test.Tasty hiding (Timeout )
138
+ import Test.Tasty hiding (Timeout )
135
139
import Test.Tasty.ExpectedFailure
136
140
import Test.Tasty.Golden
137
141
import Test.Tasty.HUnit
@@ -328,6 +332,56 @@ goldenWithDocInTmpDir languageKind config plugin title tree path desc ext act =
328
332
act doc
329
333
documentContents doc
330
334
335
+ -- | A parameterised test is similar to a normal test case but allows to run
336
+ -- the same test case multiple times with different inputs.
337
+ -- A 'parameterisedCursorTest' allows to define a test case based on an input file
338
+ -- that specifies one or many cursor positions via the identification value '^'.
339
+ --
340
+ -- For example:
341
+ --
342
+ -- @
343
+ -- parameterisedCursorTest "Cursor Test" [trimming|
344
+ -- foo = 2
345
+ -- ^
346
+ -- bar = 3
347
+ -- baz = foo + bar
348
+ -- ^
349
+ -- |]
350
+ -- ["foo", "baz"]
351
+ -- (\input cursor -> findFunctionNameUnderCursor input cursor)
352
+ -- @
353
+ --
354
+ -- Assuming a fitting implementation for 'findFunctionNameUnderCursor'.
355
+ --
356
+ -- This test definition will run the test case 'findFunctionNameUnderCursor' for
357
+ -- each cursor position, each in its own isolated 'testCase'.
358
+ -- Cursor positions are identified via the character '^', which points to the
359
+ -- above line as the actual cursor position.
360
+ -- Lines containing '^' characters, are removed from the final text, that is
361
+ -- passed to the testing function.
362
+ --
363
+ -- TODO: Many Haskell and Cabal source may contain '^' characters for good reasons.
364
+ -- We likely need a way to change the character for certain test cases in the future.
365
+ --
366
+ -- The quasi quoter 'trimming' is very helpful to define such tests, as it additionally
367
+ -- allows to interpolate haskell values and functions. We reexport this quasi quoter
368
+ -- for easier usage.
369
+ parameterisedCursorTest :: (Show a , Eq a ) => String -> T. Text -> [a ] -> (T. Text -> PosPrefixInfo -> IO a ) -> TestTree
370
+ parameterisedCursorTest title content expectations act
371
+ | lenPrefs /= lenExpected = error $ " parameterisedCursorTest: Expected " <> show lenExpected <> " cursors but found: " <> show lenPrefs
372
+ | otherwise = testGroup title $
373
+ map singleTest testCaseSpec
374
+ where
375
+ lenPrefs = length prefInfos
376
+ lenExpected = length expectations
377
+ (cleanText, prefInfos) = extractCursorPositions content
378
+
379
+ testCaseSpec = zip [1 :: Int .. ] (zip expectations prefInfos )
380
+
381
+ singleTest (n, (expected, info)) = testCase (title <> " " <> show n) $ do
382
+ actual <- act cleanText info
383
+ assertEqual (mkParameterisedLabel info) expected actual
384
+
331
385
-- ------------------------------------------------------------
332
386
-- Helper function for initialising plugins under test
333
387
-- ------------------------------------------------------------
@@ -429,6 +483,7 @@ initializeTestRecorder envVars = do
429
483
-- ------------------------------------------------------------
430
484
-- Run an HLS server testing a specific plugin
431
485
-- ------------------------------------------------------------
486
+
432
487
runSessionWithServerInTmpDir :: Pretty b => Config -> PluginTestDescriptor b -> VirtualFileTree -> Session a -> IO a
433
488
runSessionWithServerInTmpDir config plugin tree act =
434
489
runSessionWithTestConfig def
0 commit comments