Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
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
3 changes: 2 additions & 1 deletion .github/workflows/flags.yml
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,8 @@ jobs:
cat cabal.project.local

- name: Build everything with non-default flags
run: cabal build all
# --semaphore (GHC -jsem) needs GHC >= 9.8, so skip it on 9.6.
run: cabal build --jobs ${{ matrix.ghc != '9.6' && '--semaphore' || '' }} all

flags_post_job:
if: always()
Expand Down
10 changes: 9 additions & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,8 @@ jobs:
os: ${{ runner.os }}

- name: Build
run: cabal build --max-backjumps 10000 ${CABAL_ARGS} all
# --semaphore (GHC -jsem) needs GHC >= 9.8, so skip it on 9.6.
run: cabal build --max-backjumps 10000 ${CABAL_ARGS} --jobs ${{ matrix.ghc != '9.6' && '--semaphore' || '' }} all

- name: Set test options
# See https://github.com/ocharles/tasty-rerun/issues/22 for why we need
Expand All @@ -119,6 +120,13 @@ jobs:
run: |
cabal configure --test-options="--rerun-update --rerun-filter failures,exceptions,new" --max-backjumps 10000

- if: matrix.os == 'windows-latest'
name: Run tests serially on Windows
# Windows runners are too slow to parallelise: concurrent in-process
# sessions starve each other into lsp-test message timeouts. Serialise
# the tests only -- builds keep their numProcessors/2 capabilities.
run: echo "GHCIDE_TEST_TASTY_THREADS=1" >> "$GITHUB_ENV"

- if: matrix.test
name: Test hls-graph
run: cabal test ${CABAL_ARGS} hls-graph
Expand Down
5 changes: 0 additions & 5 deletions cabal.project
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,6 @@ benchmarks: True

write-ghc-environment-files: never

-- Many of our tests only work single-threaded, and the only way to
-- ensure tasty runs everything purely single-threaded is to pass
-- this at the top-level
test-options: -j1

-- Make sure dependencies are build with haddock so we get
-- haddock shown on hover
package *
Expand Down
5 changes: 2 additions & 3 deletions ghcide-test/exe/CompletionTests.hs
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ testSessionSingleFile testName fp txt session =
completionTest :: HasCallStack => String -> [T.Text] -> Position -> [(T.Text, CompletionItemKind, T.Text, Bool, Bool, Maybe [TextEdit])] -> TestTree
completionTest name src pos expected = testSessionSingleFile name "A.hs" (T.unlines src) $ do
docId <- openDoc "A.hs" "haskell"
_ <- waitForDiagnostics
_ <- waitForTypecheck docId

compls <- getAndResolveCompletions docId pos
let compls' = [ (_label, _kind, _insertText, _additionalTextEdits) | CompletionItem{..} <- compls]
Expand Down Expand Up @@ -220,8 +220,7 @@ localCompletionTests = [
, " { field1 :: Int"
, " , field2 :: Int"
, " }"
, -- Without the following, this file doesn't trigger any diagnostics, so completionTest waits forever
"triggerDiag :: UnknownType"
, "triggerDiag :: UnknownType"
, "foo record = record.f"
]
(Position 7 21)
Expand Down
4 changes: 2 additions & 2 deletions ghcide-test/exe/Config.hs
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ testSessionWithPlugin fs plugin = runSessionWithTestConfig def
{ testPluginDescriptor = plugin
, testDirLocation = Right fs
, testConfigCaps = lspTestCaps
, testShiftRoot = True
, testCwdHandling = NoCwdShift
}

-- * A dummy plugin for testing ghcIde
Expand All @@ -78,7 +78,7 @@ runWithDummyPlugin' fs = runSessionWithTestConfig def
{ testPluginDescriptor = dummyPlugin
, testDirLocation = Right fs
, testConfigCaps = lspTestCaps
, testShiftRoot = True
, testCwdHandling = NoCwdShift
}

testWithDummyPlugin :: String -> FS.VirtualFileTree -> Session () -> TestTree
Expand Down
5 changes: 3 additions & 2 deletions ghcide-test/exe/CradleTests.hs
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,8 @@ import Language.LSP.Protocol.Types hiding
mkRange)
import Language.LSP.Test
import System.FilePath
import Test.Hls (TestConfig (..), def,
import Test.Hls (CwdHandling (..),
TestConfig (..), def,
runSessionWithTestConfig,
waitForBuildQueue)
import Test.Hls.FileSystem
Expand Down Expand Up @@ -284,7 +285,7 @@ runWithExtraFilesMultiComponent dirName action = do
{ testPluginDescriptor = dummyPlugin
, testDirLocation = Right vfs
, testConfigCaps = lspTestCaps
, testShiftRoot = True
, testCwdHandling = NoCwdShift
, testDisableKick = True
, testLspConfig = lspConfig
}
Expand Down
16 changes: 11 additions & 5 deletions ghcide-test/exe/DependentFileTest.hs
Original file line number Diff line number Diff line change
Expand Up @@ -14,33 +14,39 @@ import Language.LSP.Protocol.Types hiding
SemanticTokensEdit (..),
mkRange)
import Language.LSP.Test
import System.FilePath ((</>))
import Test.Hls
import Test.Hls.FileSystem


tests :: TestTree
tests = testGroup "addDependentFile"
[testGroup "file-changed" [testCase "test" $ runSessionWithTestConfig def
{ testShiftRoot = True
{ testCwdHandling = NoCwdShift
, testDirLocation = Right (mkIdeTestFs [])
, testPluginDescriptor = dummyPlugin
} test]
]
where
test :: FilePath -> Session ()
test _ = do
test sessionDir = do
-- If the file contains B then no type error
-- otherwise type error
let depFilePath = "dep-file.txt"
-- Absolute path so the splice's qRunIO/readFile and the watched-file
-- notification resolve identically regardless of the process CWD.
let depFilePath = sessionDir </> "dep-file.txt"
-- show gives a properly escaped Haskell string literal, so a Windows
-- path's backslashes survive the splice into Foo's source.
let depFileLit = T.pack (show depFilePath)
liftIO $ atomicFileWriteString depFilePath "A"
let fooContent = T.unlines
[ "{-# LANGUAGE TemplateHaskell #-}"
, "module Foo where"
, "import Language.Haskell.TH.Syntax"
, "foo :: Int"
, "foo = 1 + $(do"
, " qAddDependentFile \"" <> T.pack depFilePath <> "\""
, " f <- qRunIO (readFile \"" <> T.pack depFilePath <> "\")"
, " qAddDependentFile " <> depFileLit
, " f <- qRunIO (readFile " <> depFileLit <> ")"
, " if f == \"B\" then [| 1 |] else lift f)"
]
let bazContent = T.unlines ["module Baz where", "import Foo ()"]
Expand Down
2 changes: 1 addition & 1 deletion ghcide-test/exe/FindDefinitionAndHoverTests.hs
Original file line number Diff line number Diff line change
Expand Up @@ -307,7 +307,7 @@ linkToTests =
{ testPluginDescriptor = dummyPlugin
, testDirLocation = Right (mkIdeTestFs [copyDir "hover"])
, testConfigCaps = lspTestCaps
, testShiftRoot = True
, testCwdHandling = NoCwdShift
, testLspConfig = lspConf
}
hoverCheck pos fp expects = do
Expand Down
8 changes: 4 additions & 4 deletions ghcide-test/exe/ResolveTests.hs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import Language.LSP.Test hiding (resolveCompletion)
import Test.Hls (IdeState, SMethod (..), liftIO,
mkPluginTestDescriptor,
someMethodToMethodString,
waitForAllProgressDone)
waitForTypecheck)
import qualified Test.Hls.FileSystem as FS
import Test.Tasty
import Test.Tasty.HUnit
Expand Down Expand Up @@ -100,7 +100,7 @@ resolveRequests =
, "data Foo = Foo { foo :: Int }"
, "bar = Foo 4"
]
waitForAllProgressDone
_ <- waitForTypecheck doc
items <- getCompletions doc (Position 2 7)
let resolveCompItems = filter (\i -> "test item" `T.isPrefixOf` (i ^. J.label)) items
liftIO $ assertEqual "There must be exactly two results" 2 (length resolveCompItems)
Expand All @@ -113,7 +113,7 @@ resolveRequests =
, "data Foo = Foo { foo :: Int }"
, "bar = Foo 4"
]
waitForAllProgressDone
_ <- waitForTypecheck doc
-- Cant use 'getAllCodeActions', as this lsp-test function queries the diagnostic
-- locations and we don't have diagnostics in these tests.
cas <- Maybe.mapMaybe (preview _R) <$> getCodeActions doc (Range (Position 0 0) (Position 1 0))
Expand All @@ -128,7 +128,7 @@ resolveRequests =
, "data Foo = Foo { foo :: Int }"
, "bar = Foo 4"
]
waitForAllProgressDone
_ <- waitForTypecheck doc
cd <- getCodeLenses doc
let resolveCodeLenses = filter (\i -> case i ^. J.command of
Just cmd -> "test item" `T.isPrefixOf` (cmd ^. J.title)
Expand Down
4 changes: 2 additions & 2 deletions ghcide-test/exe/RootUriTests.hs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import System.FilePath
-- import Test.QuickCheck.Instances ()
import Config
import Data.Default (def)
import Test.Hls (TestConfig (..),
import Test.Hls (CwdHandling (..), TestConfig (..),
runSessionWithTestConfig)
import Test.Hls.FileSystem (copyDir)
import Test.Tasty
Expand All @@ -33,7 +33,7 @@ tests = testCase "use rootUri" . runTest "dirA" "dirB" $ \dir -> do
, testDirLocation = Right $ mkIdeTestFs [copyDir "rootUri"]
, testServerRoot = Just dir1
, testClientRoot = Just dir2
, testShiftRoot = True
, testCwdHandling = NoCwdShift
}


21 changes: 20 additions & 1 deletion ghcide-test/exe/THTests.hs
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,16 @@
module THTests (tests) where

import Config
import Control.Applicative ((<|>))
import Control.Lens ((^.))
import Control.Monad.IO.Class (liftIO)
import qualified Data.Text as T
import Development.IDE.GHC.Compat (GhcVersion (..), ghcVersion)
import Development.IDE.GHC.Util
import Development.IDE.Test (expectCurrentDiagnostics,
expectDiagnostics,
expectNoMoreDiagnostics)
import qualified Language.LSP.Protocol.Lens as L
import Language.LSP.Protocol.Types hiding (SemanticTokenAbsolute (..),
SemanticTokenRelative (..),
SemanticTokensEdit (..), mkRange)
Expand Down Expand Up @@ -288,7 +291,23 @@ thLinkingTest unboxed = testCase name $ runWithExtraFiles dir $ \dir -> do
-- modify b too
let bSource' = T.unlines $ init (T.lines bSource) ++ ["$th"]
changeDoc bdoc [TextDocumentContentChangeEvent . InR $ TextDocumentContentChangeWholeDocument bSource']
_ <- waitForDiagnostics

-- The reload renames THA's splice (th_a -> th) and re-splices it in THB.
-- While THA relinks, THB transiently reports "th_a not in scope", and a single
-- 'waitForDiagnostics' could catch that transient (see the note in Main.hs).
-- Wait for THB's own settled "Top-level binding" warning, matched on THB's uri.
let bUri = bdoc ^. L.uri
settledTHB params =
params ^. L.uri == bUri && case params ^. L.diagnostics of
[d] -> d ^. L.severity == Just DiagnosticSeverity_Warning
&& "Top-level binding" `T.isInfixOf` (d ^. L.message)
_ -> False
-- next PublishDiagnostics, skipping any non-diagnostic messages
nextPublishDiagnostics = publishDiagnosticsNotification <|> (anyMessage *> nextPublishDiagnostics)
waitForSettledTHB = do
notif <- nextPublishDiagnostics
if settledTHB (notif ^. L.params) then pure () else waitForSettledTHB
waitForSettledTHB

expectCurrentDiagnostics bdoc [(DiagnosticSeverity_Warning, (4,1), "Top-level binding", Just "GHC-38417")]

Expand Down
4 changes: 2 additions & 2 deletions ghcide-test/exe/WatchedFileTests.hs
Original file line number Diff line number Diff line change
Expand Up @@ -78,9 +78,9 @@ tests = testGroup "watched files"
_ <- openDoc hsFile "haskell"
expectDiagnostics [(hsFile, [(DiagnosticSeverity_Error, (2, 7), "Could not load module \8216Data.List.Split\8217", Nothing)])]
let cabalFile = "reload.cabal"
cabalContent <- liftIO $ T.readFile cabalFile
cabalContent <- liftIO $ T.readFile (sessionDir </> cabalFile)
let fix = T.replace "build-depends: base" "build-depends: base, split"
liftIO $ atomicFileWriteText cabalFile (fix cabalContent)
liftIO $ atomicFileWriteText (sessionDir </> cabalFile) (fix cabalContent)
sendNotification SMethod_WorkspaceDidChangeWatchedFiles $ DidChangeWatchedFilesParams
[ FileEvent (filePathToUri $ sessionDir </> cabalFile) FileChangeType_Changed ]
expectDiagnostics [(hsFile, [])]
Expand Down
24 changes: 21 additions & 3 deletions ghcide/session-loader/Development/IDE/Session/Ghc.hs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
{-# LANGUAGE CPP #-}

Check warning on line 1 in ghcide/session-loader/Development/IDE/Session/Ghc.hs

View workflow job for this annotation

GitHub Actions / Hlint check run

Warning in module Development.IDE.Session.Ghc: Use module export list ▫︎ Found: "module Development.IDE.Session.Ghc where" ▫︎ Perhaps: "module Development.IDE.Session.Ghc (\n module Development.IDE.Session.Ghc\n ) where" ▫︎ Note: an explicit list is usually better
module Development.IDE.Session.Ghc where

import Control.Monad
Expand Down Expand Up @@ -253,7 +253,7 @@
where
initMulti unitArgFiles =
forM unitArgFiles $ \f -> do
args <- liftIO $ expandResponse [f]
args <- liftIO $ expandResponse [rebaseResponseFile compRoot f]
-- The reponse files may contain arguments like "+RTS",
-- and hie-bios doesn't expand the response files of @-unit@ arguments.
-- Thus, we need to do the stripping here.
Expand Down Expand Up @@ -296,6 +296,13 @@
dflags''
return (HomeUnitConfig dflags''' targets mHash)

-- | Rebase a relative @file response-file arg onto the component root, since
-- 'expandResponse' would otherwise resolve it against the process CWD.
rebaseResponseFile :: FilePath -> String -> String
rebaseResponseFile root arg = case arg of
'@' : path -> '@' : toAbsolute root path
_ -> arg

addComponentInfo ::
MonadUnliftIO m =>
Recorder (WithPriority Log) ->
Expand Down Expand Up @@ -450,9 +457,20 @@
-- keeping the path short and clean.
getCacheDirsDefault :: String -> Maybe B.ByteString -> [String] -> IO CacheDirs
getCacheDirsDefault prefix mFirstHash opts = do
dir <- Just <$> getXdgDirectory XdgCache (cacheDir </> prefix' ++ "-" ++ opts_hash)
return $ CacheDirs dir dir dir
base <- getXdgDirectory XdgCache cacheDir
pure $ cacheDirsUnder base prefix mFirstHash opts

-- | Like 'getCacheDirsDefault', but roots the cache under @base@ instead of
-- 'XdgCache', so callers can isolate a cache without touching @XDG_CACHE_HOME@.
getCacheDirsIn :: FilePath -> String -> Maybe B.ByteString -> [String] -> IO CacheDirs
getCacheDirsIn base prefix mFirstHash opts =
pure $ cacheDirsUnder (base </> cacheDir) prefix mFirstHash opts

-- | The per-component cache folder under @base@, see Note [Avoiding bad interface files].
cacheDirsUnder :: FilePath -> String -> Maybe B.ByteString -> [String] -> CacheDirs
cacheDirsUnder base prefix mFirstHash opts = CacheDirs dir dir dir
where
dir = Just (base </> prefix' ++ "-" ++ opts_hash)
-- Create a unique folder per set of different GHC options.
prefix' = if isJust mFirstHash then "main" else prefix
basectx = case mFirstHash of
Expand Down
12 changes: 10 additions & 2 deletions ghcide/src/Development/IDE/LSP/LanguageServer.hs
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,8 @@ data ServerLifecycleContext config = ServerLifecycleContext
-- ^ Logger for recording server events and diagnostics
, ctxDefaultRoot :: FilePath
-- ^ Default root directory for the workspace, see Note [Root Directory]
, ctxDisableInitialCwdShift :: Bool
-- ^ Skip the init-time setCurrentDirectory so in-process test servers can run in parallel, see Note [Root Directory]
, ctxGetHieDbLoc :: FilePath -> IO FilePath
-- ^ Function to determine the HIE database location for a given root path
, ctxGetIdeState :: LSP.LanguageContextEnv config -> FilePath -> WithHieDb -> ThreadQueue -> IO IdeState
Expand Down Expand Up @@ -191,12 +193,13 @@ setupLSP ::
forall config.
Recorder (WithPriority Log)
-> FilePath -- ^ root directory, see Note [Root Directory]
-> Bool -- ^ disable the initial setCurrentDirectory to the rootUri (for parallel in-process tests)
-> (FilePath -> IO FilePath) -- ^ Map root paths to the location of the hiedb for the project
-> LSP.Handlers (ServerM config)
-> (LSP.LanguageContextEnv config -> FilePath -> WithHieDb -> ThreadQueue -> IO IdeState)
-> MVar ()
-> IO (Setup config (ServerM config) IdeState)
setupLSP recorder defaultRoot getHieDbLoc userHandlers getIdeState clientMsgVar = do
setupLSP recorder defaultRoot disableInitialCwdShift getHieDbLoc userHandlers getIdeState clientMsgVar = do
-- Send everything over a channel, since you need to wait until after initialise before
-- LspFuncs is available
clientMsgChan :: Chan ReactorMessage <- newChan
Expand Down Expand Up @@ -254,6 +257,7 @@ setupLSP recorder defaultRoot getHieDbLoc userHandlers getIdeState clientMsgVar
let lifecycleCtx = ServerLifecycleContext
{ ctxRecorder = recorder
, ctxDefaultRoot = defaultRoot
, ctxDisableInitialCwdShift = disableInitialCwdShift
, ctxGetHieDbLoc = getHieDbLoc
, ctxGetIdeState = getIdeState
, ctxUntilReactorStopSignal = untilReactorStopSignal
Expand Down Expand Up @@ -285,7 +289,11 @@ handleInit lifecycleCtx env (TRequestMessage _ _ m params) = otTracedHandler "In
untilReactorStopSignal = ctxUntilReactorStopSignal lifecycleCtx
lifetimeConfirm = ctxConfirmReactorShutdown lifecycleCtx
root <- case LSP.resRootPath env of
Just lspRoot | lspRoot /= defaultRoot -> setCurrentDirectory lspRoot >> return lspRoot
-- Skip the CWD shift under the test harness so in-process servers can run
-- in parallel. See Note [Root Directory].
Just lspRoot | lspRoot /= defaultRoot -> do
unless (ctxDisableInitialCwdShift lifecycleCtx) $ setCurrentDirectory lspRoot
return lspRoot
_ -> pure defaultRoot
dbLoc <- ctxGetHieDbLoc lifecycleCtx root
let initConfig = parseConfiguration params
Expand Down
Loading
Loading