Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
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
4 changes: 4 additions & 0 deletions .github/memory/testing/ide.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,7 @@ public class MyTests
test source code.
- Keep tests focused — avoid unnecessary intermediary assertions; use `.Single()`
rather than asserting a count then indexing.
- Language Server orchestration tests can pass additional MEF parts to
`LanguageServerTestComposition.GetSharedExportProvider`. A controllable
`PartNotDiscoverable` project loader can provide deterministic design-time
build timing and results without invoking MSBuild.
Comment on lines +38 to +41

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
- Language Server orchestration tests can pass additional MEF parts to
`LanguageServerTestComposition.GetSharedExportProvider`. A controllable
`PartNotDiscoverable` project loader can provide deterministic design-time
build timing and results without invoking MSBuild.
- Language Server orchestration tests can pass additional MEF parts to
`LanguageServerTestComposition.GetSharedExportProvider` Adding parts
with `PartNotDiscoverable` can provide the ability to insert test code or mocks.

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
<ProjectReference Include="..\Microsoft.CodeAnalysis.LanguageServer\Microsoft.CodeAnalysis.LanguageServer.csproj" />
<ProjectReference Include="..\Protocol.TestUtilities\Microsoft.CodeAnalysis.LanguageServer.Protocol.Test.Utilities.csproj" />
<ProjectReference Include="..\Protocol\Microsoft.CodeAnalysis.LanguageServer.Protocol.csproj" />
<ProjectReference Include="..\..\Workspaces\MSBuild\Contracts\Microsoft.CodeAnalysis.Workspaces.MSBuild.Contracts.csproj" Aliases="MSBuildWorkspacesContracts" />
<ProjectReference Include="..\..\VisualStudio\DevKit\Impl\Microsoft.VisualStudio.LanguageServices.DevKit.csproj" ReferenceOutputAssembly="false" Private="false" />
</ItemGroup>

Expand All @@ -27,12 +28,6 @@
<InternalsVisibleTo Include="IdeCoreBenchmarks" />
</ItemGroup>

Comment thread
Copilot marked this conversation as resolved.
Resolved
<ItemGroup Label="Shared named-pipe helper for daemon client tests">
<!-- Tests connect to the daemon as a client using the same compiler-server pipe helper the thin client
uses, so the test's client stream agrees with the daemon's server stream on pipe path -->
<Compile Include="..\..\Compilers\Shared\NamedPipeUtil.cs" Link="Daemon\NamedPipeUtil.cs" />
</ItemGroup>

<!--
Copy files contained in the project to a RoslynLSP subdirectory to emulate deployment of the language server.
-->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using System.Collections.Immutable;
using System.Text;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Features.Workspaces;
using Microsoft.CodeAnalysis.LanguageServer.FileBasedPrograms;
using Microsoft.CodeAnalysis.LanguageServer.HostWorkspace;
using Microsoft.CodeAnalysis.LanguageServer.HostWorkspace.FileWatching;
Expand All @@ -26,6 +27,44 @@ namespace Microsoft.CodeAnalysis.LanguageServer.UnitTests.FileBasedPrograms;

public sealed class FileBasedProgramsWorkspaceTests(ITestOutputHelper testOutputHelper) : AbstractLspMiscellaneousFilesWorkspaceTests(testOutputHelper)
{
[Fact]
public async Task GetOrLoadEntryPointDocument_NormalizesPath()
{
await using var testLspServer = await CreateTestLspServerAsync(string.Empty, mutatingLspWorkspace: false, new InitializationOptions { ServerKind = WellKnownLspServerKinds.CSharpVisualBasicLspServer });
var projectSystem = (FileBasedProgramsProjectSystem)testLspServer.GetRequiredLspService<ILspMiscellaneousFilesWorkspaceProvider>();
var sourceFile = CreateTempDirectoryWithGlobalJson().CreateFile("SomeFile.cs");
var nonCanonicalPath = Path.Combine(Path.GetDirectoryName(sourceFile.Path)!, "directory", "..", Path.GetFileName(sourceFile.Path));
var sourceText = SourceText.From("Console.WriteLine(\"Hello World!\");");
var languageInformation = new LanguageInformation(LanguageNames.CSharp, "csx");

var documents = await projectSystem.GetOrLoadEntryPointDocumentAsync(
nonCanonicalPath, new SourceTextLoader(sourceText, nonCanonicalPath), languageInformation, SourceHashAlgorithms.Default, doDesignTimeBuild: false);
var document = Assert.Single(documents);
var documentsFromCanonicalPath = await projectSystem.GetOrLoadEntryPointDocumentAsync(
sourceFile.Path, new SourceTextLoader(sourceText, sourceFile.Path), languageInformation, SourceHashAlgorithms.Default, doDesignTimeBuild: false);

Assert.Equal(sourceFile.Path, document.FilePath);
Assert.Equal(document.Id, Assert.Single(documentsFromCanonicalPath).Id);
}

[ConditionalFact(typeof(WindowsOnly))]
public async Task GetOrLoadEntryPointDocument_MatchesPathCaseInsensitively()
{
await using var testLspServer = await CreateTestLspServerAsync(string.Empty, mutatingLspWorkspace: false, new InitializationOptions { ServerKind = WellKnownLspServerKinds.CSharpVisualBasicLspServer });
var projectSystem = (FileBasedProgramsProjectSystem)testLspServer.GetRequiredLspService<ILspMiscellaneousFilesWorkspaceProvider>();
var sourceFile = CreateTempDirectoryWithGlobalJson().CreateFile("SomeFile.cs");
var sourceText = SourceText.From("Console.WriteLine(\"Hello World!\");");
var languageInformation = new LanguageInformation(LanguageNames.CSharp, "csx");

var document = Assert.Single(await projectSystem.GetOrLoadEntryPointDocumentAsync(
sourceFile.Path, new SourceTextLoader(sourceText, sourceFile.Path), languageInformation, SourceHashAlgorithms.Default, doDesignTimeBuild: false));
var pathWithDifferentCasing = sourceFile.Path.ToUpperInvariant();
var documentsFromDifferentCasing = await projectSystem.GetOrLoadEntryPointDocumentAsync(
pathWithDifferentCasing, new SourceTextLoader(sourceText, pathWithDifferentCasing), languageInformation, SourceHashAlgorithms.Default, doDesignTimeBuild: false);

Assert.Equal(document.Id, Assert.Single(documentsFromDifferentCasing).Id);
}

[Theory, CombinatorialData]
public async Task TestFileBasedProgram_Simple(bool mutatingLspWorkspace)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,14 @@ public static Task<ExportProvider> CreateLanguageServerExportProviderAsync(
return LanguageServerExportProviderBuilder.CreateExportProviderAsync(TestPaths.GetLanguageServerDirectory(), extensionManager, assemblyLoader, serverConfiguration, cacheDirectory, loggerFactory, CancellationToken.None);
}

public static ExportProvider GetSharedExportProvider(ServerConfiguration serverConfiguration, ILoggerFactory loggerFactory)
public static ExportProvider GetSharedExportProvider(ServerConfiguration serverConfiguration, ILoggerFactory loggerFactory, params Type[] additionalParts)
{
Contract.ThrowIfTrue(serverConfiguration.ExtensionAssemblyPaths.Any(), "Tests that require extension assemblies should use AbstractLanguageServerMefHost instead");
var exportProvider = serverConfiguration.DevKitDependencyPath != null
? s_devKit.ExportProviderFactory.CreateExportProvider()
: s_languageServer.ExportProviderFactory.CreateExportProvider();
var composition = serverConfiguration.DevKitDependencyPath != null ? s_devKit : s_languageServer;
if (additionalParts.Length > 0)
composition = composition.AddParts(additionalParts);

var exportProvider = composition.ExportProviderFactory.CreateExportProvider();

LanguageServerExportProviderBuilder.TestAccessor.InitializeManualExports(exportProvider, new ExtensionAssemblyManager([], [], []), loggerFactory, serverConfiguration);
return exportProvider;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ public Task OnInitializedAsync(ClientCapabilities clientCapabilities, RequestCon
return Task.CompletedTask;
}

private void OnWorkspaceFoldersChanged()
private void OnWorkspaceFoldersChanged(object? sender, EventArgs e)
=> _discoveryQueue.AddWork();

public void Dispose()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -283,13 +283,14 @@ public async ValueTask TryBeginLoadingFileBasedAppAsync(string documentFilePath)

public async ValueTask<ImmutableArray<TextDocument>> GetOrLoadEntryPointDocumentAsync(string documentFilePath, TextLoader textLoader, LanguageInformation languageInformation, SourceHashAlgorithm checksumAlgorithm, bool doDesignTimeBuild)
{
documentFilePath = NormalizeProjectPath(documentFilePath);
var projects = await base.GetOrLoadProjectAsync(documentFilePath, _workspaceFactory.MiscellaneousFilesWorkspaceProjectFactory, CreatePrimordialProjectInfo, doDesignTimeBuild);
return projects.Select(p => LookupExistingDocument(p)).WhereNotNull().ToImmutableArray();

TextDocument? LookupExistingDocument(Project project)
{
var document = project.Documents.FirstOrDefault(document => document.FilePath == documentFilePath)
?? project.AdditionalDocuments.FirstOrDefault(document => document.FilePath == documentFilePath);
var document = project.Documents.FirstOrDefault(document => PathUtilities.Comparer.Equals(document.FilePath, documentFilePath))
?? project.AdditionalDocuments.FirstOrDefault(document => PathUtilities.Comparer.Equals(document.FilePath, documentFilePath));
if (document is null)
{
_logger.LogWarning("Could not get a document for '{documentFilePath}' because its project doesn't contain a document for it", documentFilePath);
Expand All @@ -298,11 +299,11 @@ public async ValueTask<ImmutableArray<TextDocument>> GetOrLoadEntryPointDocument
return document;
}

ProjectInfo CreatePrimordialProjectInfo(ProjectSystemProjectFactory projectFactory)
ProjectInfo CreatePrimordialProjectInfo(ProjectSystemProjectFactory projectFactory, string normalizedDocumentFilePath)
{
var enableFileBasedPrograms = GlobalOptionService.GetOption(LanguageServerProjectSystemOptionsStorage.EnableFileBasedPrograms);
return MiscellaneousFileUtilities.CreateMiscellaneousProjectInfoForDocument(
projectFactory.Workspace, documentFilePath, textLoader, languageInformation, checksumAlgorithm, projectFactory.Workspace.Services.SolutionServices, [], enableFileBasedPrograms);
projectFactory.Workspace, normalizedDocumentFilePath, textLoader, languageInformation, checksumAlgorithm, projectFactory.Workspace.Services.SolutionServices, [], enableFileBasedPrograms);
}
}

Expand Down

This file was deleted.

Loading
Loading