@@ -34,6 +34,8 @@ module Test.Hls
3434 runSessionWithServer ,
3535 runSessionWithServerInTmpDir ,
3636 runSessionWithTestConfig ,
37+ -- * Running parameterised tests for a set of test configurations
38+ parameterisedCursorTest ,
3739 -- * Helpful re-exports
3840 PluginDescriptor ,
3941 IdeState ,
@@ -64,74 +66,76 @@ module Test.Hls
6466where
6567
6668import Control.Applicative.Combinators
67- import Control.Concurrent.Async (async , cancel , wait )
69+ import Control.Concurrent.Async (async , cancel , wait )
6870import Control.Concurrent.Extra
6971import 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 )
7375import 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
9699import Development.IDE.Types.Options
97100import GHC.IO.Handle
98101import 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 )
111115import Ide.Types
112116import Language.LSP.Protocol.Capabilities
113117import 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
117121import 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 )
126130import 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 )
130134import System.Time.Extra
131- import qualified Test.Hls.FileSystem as FS
135+ import qualified Test.Hls.FileSystem as FS
132136import Test.Hls.FileSystem
133137import Test.Hls.Util
134- import Test.Tasty hiding (Timeout )
138+ import Test.Tasty hiding (Timeout )
135139import Test.Tasty.ExpectedFailure
136140import Test.Tasty.Golden
137141import Test.Tasty.HUnit
@@ -328,6 +332,56 @@ goldenWithDocInTmpDir languageKind config plugin title tree path desc ext act =
328332 act doc
329333 documentContents doc
330334
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+
331385-- ------------------------------------------------------------
332386-- Helper function for initialising plugins under test
333387-- ------------------------------------------------------------
@@ -429,6 +483,7 @@ initializeTestRecorder envVars = do
429483-- ------------------------------------------------------------
430484-- Run an HLS server testing a specific plugin
431485-- ------------------------------------------------------------
486+
432487runSessionWithServerInTmpDir :: Pretty b => Config -> PluginTestDescriptor b -> VirtualFileTree -> Session a -> IO a
433488runSessionWithServerInTmpDir config plugin tree act =
434489 runSessionWithTestConfig def
0 commit comments