Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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.
<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