Skip to content

Introduce common code for Recorders in Plugin Tests #3347

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 33 commits into from
Nov 28, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
fa573a3
Add generic test plugin recorder initialisation
fendor Nov 19, 2022
ee4bdc1
Migrate some plugins to the new test plugin recorder structure
fendor Nov 19, 2022
3256d6d
Introduce improved Test Logging Infrastructure
fendor Nov 26, 2022
9c97a16
HlsPlugins: Make sure every plugin is imported qualified
fendor Nov 26, 2022
f4ee7fe
hls-cabal-plugin: Unify logging infrastructure
fendor Nov 26, 2022
10f2116
hls-cabal-fmt-plugin: Unify logging infrastructure
fendor Nov 26, 2022
ac1fcd2
hls-alternate-number-format-plugin: Unify logging infrastructure
fendor Nov 26, 2022
5f1b907
hls-brittany-plugin: Unify logging infrastructure
fendor Nov 26, 2022
bf96936
hls-call-hierarchy-plugin: Unify logging infrastructure
fendor Nov 26, 2022
4103f7a
hls-change-type-signature-plugin: Unify logging infrastructure
fendor Nov 26, 2022
8540a04
hls-class-plugin: Unify logging infrastructure
fendor Nov 26, 2022
ad7fe9b
hls-code-range-plugin: Unify logging infrastructure
fendor Nov 26, 2022
3f2f36e
hls-eval-plugin: Unify logging infrastructure
fendor Nov 26, 2022
12ca6d0
hls-explicit-fixity-plugin: Unify logging infrastructure
fendor Nov 26, 2022
bee02f3
hls-explicit-imports-plugin: Unify logging infrastructure
fendor Nov 26, 2022
afb95c9
hls-explicit-record-fields-plugin: Unify logging infrastructure
fendor Nov 26, 2022
fc9215a
hls-floskell-plugin: Unify logging infrastructure
fendor Nov 26, 2022
b8ebea5
hls-fourmolu-plugin: Unify logging infrastructure
fendor Nov 26, 2022
de0591d
hls-gadt-plugin: Unify logging infrastructure
fendor Nov 26, 2022
1f84781
hls-haddock-comments-plugin: Unify logging infrastructure
fendor Nov 26, 2022
7b61c02
hls-hlint-plugin: Unify logging infrastructure
fendor Nov 26, 2022
cebc673
hls-module-name-plugin: Unify logging infrastructure
fendor Nov 26, 2022
e349fc5
hls-ormolu-plugin: Unify logging infrastructure
fendor Nov 26, 2022
c18a8b2
hls-pragmas-plugin: Unify logging infrastructure
fendor Nov 26, 2022
37dc5dc
hls-qualify-imported-names-plugin: Unify logging infrastructure
fendor Nov 26, 2022
23f3c6d
hls-refactor-plugin: Unify logging infrastructure
fendor Nov 26, 2022
3c31417
hls-refine-imports-plugin: Unify logging infrastructure
fendor Nov 26, 2022
b49486a
hls-rename-plugin: Unify logging infrastructure
fendor Nov 26, 2022
eda59c0
hls-splice-plugin: Unify logging infrastructure
fendor Nov 26, 2022
822671e
hls-stan-plugin: Unify logging infrastructure
fendor Nov 26, 2022
f2f11ca
hls-stylish-haskell-plugin: Unify logging infrastructure
fendor Nov 26, 2022
1c4c1d1
hls-tactics-plugin: Unify logging infrastructure
fendor Nov 26, 2022
e76f645
Merge branch 'master' into refactor/test-plugin-loggers
mergify[bot] Nov 28, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
174 changes: 146 additions & 28 deletions hls-test-utils/src/Test/Hls.hs
Original file line number Diff line number Diff line change
Expand Up @@ -20,21 +20,35 @@ module Test.Hls
goldenWithHaskellDocFormatter,
goldenWithCabalDocFormatter,
def,
-- * Running HLS for integration tests
runSessionWithServer,
runSessionWithServerAndCaps,
runSessionWithServerFormatter,
runSessionWithCabalServerFormatter,
runSessionWithServer',
waitForProgressDone,
waitForAllProgressDone,
-- * Helpful re-exports
PluginDescriptor,
IdeState,
-- * Assertion helper functions
waitForProgressDone,
waitForAllProgressDone,
waitForBuildQueue,
waitForTypecheck,
waitForAction,
sendConfigurationChanged,
getLastBuildKeys,
waitForKickDone,
waitForKickStart,
-- * Plugin descriptor helper functions for tests
PluginTestDescriptor,
pluginTestRecorder,
mkPluginTestDescriptor,
mkPluginTestDescriptor',
-- * Re-export logger types
-- Avoids slightly annoying ghcide imports when they are unnecessary.
WithPriority(..),
Recorder,
Priority(..),
)
where

Expand All @@ -43,6 +57,7 @@ import Control.Concurrent.Async (async, cancel, wait)
import Control.Concurrent.Extra
import Control.Exception.Base
import Control.Monad (guard, unless, void)
import Control.Monad.Extra (forM)
import Control.Monad.IO.Class
import Data.Aeson (Result (Success),
Value (Null), fromJSON,
Expand All @@ -62,7 +77,7 @@ import qualified Development.IDE.Main as IDEMain
import Development.IDE.Plugin.Test (TestRequest (GetBuildKeysBuilt, WaitForIdeRule, WaitForShakeQueue),
WaitForIdeRuleResult (ideResultSuccess))
import qualified Development.IDE.Plugin.Test as Test
import Development.IDE.Types.Logger (Logger (Logger),
import Development.IDE.Types.Logger (Doc, Logger (Logger),
Pretty (pretty),
Priority (Debug),
Recorder (Recorder, logger_),
Expand Down Expand Up @@ -117,7 +132,8 @@ goldenGitDiff :: TestName -> FilePath -> IO ByteString -> TestTree
goldenGitDiff name = goldenVsStringDiff name gitDiff

goldenWithHaskellDoc
:: PluginDescriptor IdeState
:: Pretty b
=> PluginTestDescriptor b
-> TestName
-> FilePath
-> FilePath
Expand All @@ -128,7 +144,8 @@ goldenWithHaskellDoc
goldenWithHaskellDoc = goldenWithDoc "haskell"

goldenWithCabalDoc
:: PluginDescriptor IdeState
:: Pretty b
=> PluginTestDescriptor b
-> TestName
-> FilePath
-> FilePath
Expand All @@ -139,8 +156,9 @@ goldenWithCabalDoc
goldenWithCabalDoc = goldenWithDoc "cabal"

goldenWithDoc
:: T.Text
-> PluginDescriptor IdeState
:: Pretty b
=> T.Text
-> PluginTestDescriptor b
-> TestName
-> FilePath
-> FilePath
Expand All @@ -158,23 +176,119 @@ goldenWithDoc fileType plugin title testDataDir path desc ext act =
act doc
documentContents doc

-- ------------------------------------------------------------
-- Helper function for initialising plugins under test
-- ------------------------------------------------------------

-- | Plugin under test where a fitting recorder is injected.
type PluginTestDescriptor b = Recorder (WithPriority b) -> PluginDescriptor IdeState

-- | Wrap a plugin you want to test, and inject a fitting recorder as required.
--
-- If you want to write the logs to stderr, run your tests with
-- "HLS_TEST_PLUGIN_LOG_STDERR=1", e.g.
--
-- @
-- HLS_TEST_PLUGIN_LOG_STDERR=1 cabal test <test-suite-of-plugin>
-- @
--
--
-- To write all logs to stderr, including logs of the server, use:
--
-- @
-- HLS_TEST_LOG_STDERR=1 cabal test <test-suite-of-plugin>
-- @
mkPluginTestDescriptor
:: (Recorder (WithPriority b) -> PluginId -> PluginDescriptor IdeState)
-> PluginId
-> PluginTestDescriptor b
mkPluginTestDescriptor pluginDesc plId recorder = pluginDesc recorder plId

-- | Wrap a plugin you want to test.
--
-- Ideally, try to migrate this plugin to co-log logger style architecture.
-- Therefore, you should prefer 'mkPluginTestDescriptor' to this if possible.
mkPluginTestDescriptor'
:: (PluginId -> PluginDescriptor IdeState)
-> PluginId
-> PluginTestDescriptor b
mkPluginTestDescriptor' pluginDesc plId _recorder = pluginDesc plId

-- | Initialise a recorder that can be instructed to write to stderr by
-- setting the environment variable "HLS_TEST_PLUGIN_LOG_STDERR=1" before
-- running the tests.
--
-- On the cli, use for example:
--
-- @
-- HLS_TEST_PLUGIN_LOG_STDERR=1 cabal test <test-suite-of-plugin>
-- @
--
-- To write all logs to stderr, including logs of the server, use:
--
-- @
-- HLS_TEST_LOG_STDERR=1 cabal test <test-suite-of-plugin>
-- @
pluginTestRecorder :: Pretty a => IO (Recorder (WithPriority a))
pluginTestRecorder = do
(recorder, _) <- initialiseTestRecorder ["HLS_TEST_PLUGIN_LOG_STDERR", "HLS_TEST_LOG_STDERR"]
pure recorder

-- | Generic recorder initialisation for plugins and the HLS server for test-cases.
--
-- The created recorder writes to stderr if any of the given environment variables
-- have been set to a value different to @0@.
-- We allow multiple values, to make it possible to define a single environment variable
-- that instructs all recorders in the test-suite to write to stderr.
--
-- We have to return the base logger function for HLS server logging initialisation.
-- See 'runSessionWithServer'' for details.
initialiseTestRecorder :: Pretty a => [String] -> IO (Recorder (WithPriority a), WithPriority (Doc ann) -> IO ())
initialiseTestRecorder envVars = do
docWithPriorityRecorder <- makeDefaultStderrRecorder Nothing Debug
-- There are potentially multiple environment variables that enable this logger
definedEnvVars <- forM envVars (\var -> fromMaybe "0" <$> lookupEnv var)
let logStdErr = any (/= "0") definedEnvVars

docWithFilteredPriorityRecorder =
if logStdErr then cfilter (\WithPriority{ priority } -> priority >= Debug) docWithPriorityRecorder
else mempty

Recorder {logger_} = docWithFilteredPriorityRecorder

pure (cmapWithPrio pretty docWithFilteredPriorityRecorder, logger_)

runSessionWithServer :: PluginDescriptor IdeState -> FilePath -> Session a -> IO a
runSessionWithServer plugin = runSessionWithServer' [plugin] def def fullCaps
-- ------------------------------------------------------------
-- Run an HLS server testing a specific plugin
-- ------------------------------------------------------------

runSessionWithServerFormatter :: PluginDescriptor IdeState -> String -> PluginConfig -> FilePath -> Session a -> IO a
runSessionWithServerFormatter plugin formatter conf =
runSessionWithServer :: Pretty b => PluginTestDescriptor b -> FilePath -> Session a -> IO a
runSessionWithServer plugin fp act = do
recorder <- pluginTestRecorder
runSessionWithServer' [plugin recorder] def def fullCaps fp act

runSessionWithServerAndCaps :: Pretty b => PluginTestDescriptor b -> ClientCapabilities -> FilePath -> Session a -> IO a
runSessionWithServerAndCaps plugin caps fp act = do
recorder <- pluginTestRecorder
runSessionWithServer' [plugin recorder] def def caps fp act

runSessionWithServerFormatter :: Pretty b => PluginTestDescriptor b -> String -> PluginConfig -> FilePath -> Session a -> IO a
runSessionWithServerFormatter plugin formatter conf fp act = do
recorder <- pluginTestRecorder
runSessionWithServer'
[plugin]
[plugin recorder]
def
{ formattingProvider = T.pack formatter
, plugins = M.singleton (T.pack formatter) conf
}
def
fullCaps
fp
act

goldenWithHaskellDocFormatter
:: PluginDescriptor IdeState -- ^ Formatter plugin to be used
:: Pretty b
=> PluginTestDescriptor b -- ^ Formatter plugin to be used
-> String -- ^ Name of the formatter to be used
-> PluginConfig
-> TestName -- ^ Title of the test
Expand All @@ -195,7 +309,8 @@ goldenWithHaskellDocFormatter plugin formatter conf title testDataDir path desc
documentContents doc

goldenWithCabalDocFormatter
:: PluginDescriptor IdeState -- ^ Formatter plugin to be used
:: Pretty b
=> PluginTestDescriptor b -- ^ Formatter plugin to be used
-> String -- ^ Name of the formatter to be used
-> PluginConfig
-> TestName -- ^ Title of the test
Expand All @@ -215,16 +330,18 @@ goldenWithCabalDocFormatter plugin formatter conf title testDataDir path desc ex
act doc
documentContents doc

runSessionWithCabalServerFormatter :: PluginDescriptor IdeState -> String -> PluginConfig -> FilePath -> Session a -> IO a
runSessionWithCabalServerFormatter plugin formatter conf =
runSessionWithCabalServerFormatter :: Pretty b => PluginTestDescriptor b -> String -> PluginConfig -> FilePath -> Session a -> IO a
runSessionWithCabalServerFormatter plugin formatter conf fp act = do
recorder <- pluginTestRecorder
runSessionWithServer'
[plugin]
[plugin recorder]
def
{ cabalFormattingProvider = T.pack formatter
, plugins = M.singleton (T.pack formatter) conf
}
def
fullCaps
fp act

-- | Restore cwd after running an action
keepCurrentDirectory :: IO a -> IO a
Expand All @@ -235,11 +352,13 @@ keepCurrentDirectory = bracket getCurrentDirectory setCurrentDirectory . const
lock :: Lock
lock = unsafePerformIO newLock


-- | Host a server, and run a test session on it
-- Note: cwd will be shifted into @root@ in @Session a@
runSessionWithServer' ::
-- | plugins to load on the server
-- | Plugins to load on the server.
--
-- For improved logging, make sure these plugins have been initalised with
-- the recorder produced by @pluginTestRecorder@.
[PluginDescriptor IdeState] ->
-- | lsp config for the server
Config ->
Expand All @@ -253,20 +372,19 @@ runSessionWithServer' plugins conf sconf caps root s = withLock lock $ keepCurre
(inR, inW) <- createPipe
(outR, outW) <- createPipe

docWithPriorityRecorder <- makeDefaultStderrRecorder Nothing Debug

logStdErr <- fromMaybe "0" <$> lookupEnv "LSP_TEST_LOG_STDERR"
-- Allow three environment variables, because "LSP_TEST_LOG_STDERR" has been used before,
-- (thus, backwards compatibility) and "HLS_TEST_SERVER_LOG_STDERR" because it
-- uses a more descriptive name.
-- It is also in better accordance with 'pluginTestRecorder' which uses "HLS_TEST_PLUGIN_LOG_STDERR".
-- At last, "HLS_TEST_LOG_STDERR" is intended to enable all logging for the server and the plugins
-- under test.
(recorder, logger_) <- initialiseTestRecorder
["LSP_TEST_LOG_STDERR", "HLS_TEST_SERVER_LOG_STDERR", "HLS_TEST_LOG_STDERR"]

let
docWithFilteredPriorityRecorder@Recorder{ logger_ } =
if logStdErr == "0" then mempty
else cfilter (\WithPriority{ priority } -> priority >= Debug) docWithPriorityRecorder

-- exists until old logging style is phased out
logger = Logger $ \p m -> logger_ (WithPriority p emptyCallStack (pretty m))

recorder = cmapWithPrio pretty docWithFilteredPriorityRecorder

arguments@Arguments{ argsHlsPlugins, argsIdeOptions, argsLogger } = defaultArguments (cmapWithPrio LogIDEMain recorder) logger

hlsPlugins =
Expand Down
4 changes: 2 additions & 2 deletions plugins/hls-alternate-number-format-plugin/test/Main.hs
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ import Text.Regex.TDFA ((=~))
main :: IO ()
main = defaultTestRunner test

alternateNumberFormatPlugin :: PluginDescriptor IdeState
alternateNumberFormatPlugin = AlternateNumberFormat.descriptor mempty "alternateNumberFormat"
alternateNumberFormatPlugin :: PluginTestDescriptor AlternateNumberFormat.Log
alternateNumberFormatPlugin = mkPluginTestDescriptor AlternateNumberFormat.descriptor "alternateNumberFormat"

-- NOTE: For whatever reason, this plugin does not play nice with creating Code Actions on time.
-- As a result tests will mostly pass if `import Prelude` is added at the top. We (mostly fendor) surmise this has something
Expand Down
4 changes: 2 additions & 2 deletions plugins/hls-brittany-plugin/test/Main.hs
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ import Test.Hls
main :: IO ()
main = defaultTestRunner tests

brittanyPlugin :: PluginDescriptor IdeState
brittanyPlugin = Brittany.descriptor "brittany"
brittanyPlugin :: PluginTestDescriptor ()
brittanyPlugin = mkPluginTestDescriptor' Brittany.descriptor "brittany"

tests :: TestTree
tests = testGroup "brittany"
Expand Down
4 changes: 2 additions & 2 deletions plugins/hls-cabal-fmt-plugin/test/Main.hs
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@ main = do
foundCabalFmt <- isCabalFmtFound
defaultTestRunner (tests foundCabalFmt)

cabalFmtPlugin :: PluginDescriptor IdeState
cabalFmtPlugin = CabalFmt.descriptor mempty "cabal-fmt"
cabalFmtPlugin :: PluginTestDescriptor CabalFmt.Log
cabalFmtPlugin = mkPluginTestDescriptor CabalFmt.descriptor "cabal-fmt"

tests :: CabalFmtFound -> TestTree
tests found = testGroup "cabal-fmt"
Expand Down
Loading