|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
#nullable disable
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces;
using Microsoft.CodeAnalysis.Formatting;
using Microsoft.CodeAnalysis.Options;
using Microsoft.CodeAnalysis.Remote;
using Microsoft.CodeAnalysis.Remote.Testing;
using Microsoft.CodeAnalysis.Serialization;
using Microsoft.CodeAnalysis.SolutionCrawler;
using Microsoft.CodeAnalysis.Test.Utilities;
using Microsoft.CodeAnalysis.Text;
using Roslyn.Test.Utilities;
using Roslyn.Utilities;
using Xunit;
namespace Roslyn.VisualStudio.Next.UnitTests.Remote
{
[UseExportProvider]
[Trait(Traits.Feature, Traits.Features.RemoteHost)]
public class SolutionServiceTests
{
private static RemoteWorkspace CreateRemoteWorkspace()
=> new RemoteWorkspace(FeaturesTestCompositions.RemoteHost.GetHostServices());
[Fact]
public async Task TestCreation()
{
var code = @"class Test { void Method() { } }";
using var workspace = TestWorkspace.CreateCSharp(code);
using var remoteWorkspace = CreateRemoteWorkspace();
var solution = workspace.CurrentSolution;
var assetProvider = await GetAssetProviderAsync(workspace, remoteWorkspace, solution);
var solutionChecksum = await solution.State.GetChecksumAsync(CancellationToken.None);
var synched = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, solutionChecksum, updatePrimaryBranch: false, workspaceVersion: -1, CancellationToken.None);
Assert.Equal(solutionChecksum, await synched.State.GetChecksumAsync(CancellationToken.None));
}
[Theory]
[CombinatorialData]
public async Task TestGetSolutionWithPrimaryFlag(bool updatePrimaryBranch)
{
var code1 = @"class Test1 { void Method() { } }";
using var workspace = TestWorkspace.CreateCSharp(code1);
using var remoteWorkspace = CreateRemoteWorkspace();
var solution = workspace.CurrentSolution;
var solutionChecksum = await solution.State.GetChecksumAsync(CancellationToken.None);
var assetProvider = await GetAssetProviderAsync(workspace, remoteWorkspace, solution);
var synched = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, solutionChecksum, updatePrimaryBranch, solution.WorkspaceVersion, cancellationToken: CancellationToken.None);
Assert.Equal(solutionChecksum, await synched.State.GetChecksumAsync(CancellationToken.None));
Assert.Equal(WorkspaceKind.RemoteWorkspace, synched.WorkspaceKind);
}
[Fact]
public async Task TestStrongNameProvider()
{
using var workspace = new AdhocWorkspace();
using var remoteWorkspace = CreateRemoteWorkspace();
var filePath = typeof(SolutionServiceTests).Assembly.Location;
workspace.AddProject(
ProjectInfo.Create(
ProjectId.CreateNewId(), VersionStamp.Create(), "test", "test.dll", LanguageNames.CSharp,
filePath: filePath, outputFilePath: filePath));
var assetProvider = await GetAssetProviderAsync(workspace, remoteWorkspace, workspace.CurrentSolution);
var solutionChecksum = await workspace.CurrentSolution.State.GetChecksumAsync(CancellationToken.None);
var solution = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, solutionChecksum, updatePrimaryBranch: false, workspaceVersion: -1, CancellationToken.None);
var compilationOptions = solution.Projects.First().CompilationOptions;
Assert.IsType<DesktopStrongNameProvider>(compilationOptions.StrongNameProvider);
Assert.IsType<XmlFileResolver>(compilationOptions.XmlReferenceResolver);
var dirName = PathUtilities.GetDirectoryName(filePath);
var array = new[] { dirName, dirName };
Assert.Equal(Hash.CombineValues(array, StringComparer.Ordinal), compilationOptions.StrongNameProvider.GetHashCode());
Assert.Equal(((XmlFileResolver)compilationOptions.XmlReferenceResolver).BaseDirectory, dirName);
}
[Fact]
public async Task TestStrongNameProviderEmpty()
{
using var workspace = new AdhocWorkspace();
using var remoteWorkspace = CreateRemoteWorkspace();
var filePath = "testLocation";
workspace.AddProject(
ProjectInfo.Create(
ProjectId.CreateNewId(), VersionStamp.Create(), "test", "test.dll", LanguageNames.CSharp,
filePath: filePath, outputFilePath: filePath));
var assetProvider = await GetAssetProviderAsync(workspace, remoteWorkspace, workspace.CurrentSolution);
var solutionChecksum = await workspace.CurrentSolution.State.GetChecksumAsync(CancellationToken.None);
var solution = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, solutionChecksum, updatePrimaryBranch: false, workspaceVersion: -1, CancellationToken.None);
var compilationOptions = solution.Projects.First().CompilationOptions;
Assert.True(compilationOptions.StrongNameProvider is DesktopStrongNameProvider);
Assert.True(compilationOptions.XmlReferenceResolver is XmlFileResolver);
var array = new string[] { };
Assert.Equal(Hash.CombineValues(array, StringComparer.Ordinal), compilationOptions.StrongNameProvider.GetHashCode());
Assert.Null(((XmlFileResolver)compilationOptions.XmlReferenceResolver).BaseDirectory);
}
[Fact]
public async Task TestCache()
{
var code = @"class Test { void Method() { } }";
using var workspace = TestWorkspace.CreateCSharp(code);
using var remoteWorkspace = CreateRemoteWorkspace();
var solution = workspace.CurrentSolution;
var assetProvider = await GetAssetProviderAsync(workspace, remoteWorkspace, solution);
var solutionChecksum = await solution.State.GetChecksumAsync(CancellationToken.None);
var first = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, solutionChecksum, updatePrimaryBranch: false, workspaceVersion: -1, CancellationToken.None);
var second = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, solutionChecksum, updatePrimaryBranch: false, workspaceVersion: -1, CancellationToken.None);
// same instance from cache
Assert.True(object.ReferenceEquals(first, second));
Assert.Equal(WorkspaceKind.RemoteWorkspace, first.WorkspaceKind);
}
[Fact]
public async Task TestUpdatePrimaryWorkspace()
{
var code = @"class Test { void Method() { } }";
await VerifySolutionUpdate(code, s => s.WithDocumentText(s.Projects.First().DocumentIds.First(), SourceText.From(code + " ")));
}
[Fact]
public async Task ProjectProperties()
{
using var workspace = TestWorkspace.CreateCSharp("");
static Solution SetProjectProperties(Solution solution, int version)
{
var projectId = solution.ProjectIds.Single();
return solution
.WithProjectName(projectId, "Name" + version)
.WithProjectAssemblyName(projectId, "AssemblyName" + version)
.WithProjectFilePath(projectId, "FilePath" + version)
.WithProjectOutputFilePath(projectId, "OutputFilePath" + version)
.WithProjectOutputRefFilePath(projectId, "OutputRefFilePath" + version)
.WithProjectCompilationOutputInfo(projectId, new CompilationOutputInfo("AssemblyPath" + version))
.WithProjectDefaultNamespace(projectId, "DefaultNamespace" + version)
.WithProjectChecksumAlgorithm(projectId, SourceHashAlgorithm.Sha1 + version)
.WithHasAllInformation(projectId, (version % 2) != 0)
.WithRunAnalyzers(projectId, (version % 2) != 0);
}
static void ValidateProperties(Solution solution, int version)
{
var project = solution.Projects.Single();
Assert.Equal("Name" + version, project.Name);
Assert.Equal("AssemblyName" + version, project.AssemblyName);
Assert.Equal("FilePath" + version, project.FilePath);
Assert.Equal("OutputFilePath" + version, project.OutputFilePath);
Assert.Equal("OutputRefFilePath" + version, project.OutputRefFilePath);
Assert.Equal("AssemblyPath" + version, project.CompilationOutputInfo.AssemblyPath);
Assert.Equal("DefaultNamespace" + version, project.DefaultNamespace);
Assert.Equal(SourceHashAlgorithm.Sha1 + version, project.State.ChecksumAlgorithm);
Assert.Equal((version % 2) != 0, project.State.HasAllInformation);
Assert.Equal((version % 2) != 0, project.State.RunAnalyzers);
}
Assert.True(workspace.SetCurrentSolution(s => SetProjectProperties(s, version: 0), WorkspaceChangeKind.SolutionChanged));
await VerifySolutionUpdate(workspace,
newSolutionGetter: s => SetProjectProperties(s, version: 1),
oldSolutionValidator: s => ValidateProperties(s, version: 0),
newSolutionValidator: s => ValidateProperties(s, version: 1)).ConfigureAwait(false);
}
[Fact]
public async Task TestUpdateDocumentInfo()
{
var code = @"class Test { void Method() { } }";
await VerifySolutionUpdate(code, s => s.WithDocumentFolders(s.Projects.First().Documents.First().Id, new[] { "test" }));
}
[Fact]
public async Task TestAddUpdateRemoveProjects()
{
var code = @"class Test { void Method() { } }";
await VerifySolutionUpdate(code, s =>
{
var existingProjectId = s.ProjectIds.First();
s = s.AddProject("newProject", "newProject", LanguageNames.CSharp).Solution;
var project = s.GetProject(existingProjectId);
project = project.WithCompilationOptions(project.CompilationOptions.WithModuleName("modified"));
var existingDocumentId = project.DocumentIds.First();
project = project.AddDocument("newDocument", SourceText.From("// new text")).Project;
var document = project.GetDocument(existingDocumentId);
document = document.WithSourceCodeKind(SourceCodeKind.Script);
return document.Project.Solution;
});
}
[Fact]
public async Task TestAdditionalDocument()
{
var code = @"class Test { void Method() { } }";
using var workspace = TestWorkspace.CreateCSharp(code);
var projectId = workspace.CurrentSolution.ProjectIds.First();
var additionalDocumentId = DocumentId.CreateNewId(projectId);
var additionalDocumentInfo = DocumentInfo.Create(
additionalDocumentId, "additionalFile",
loader: TextLoader.From(TextAndVersion.Create(SourceText.From("test"), VersionStamp.Create())));
await VerifySolutionUpdate(workspace, s =>
{
return s.AddAdditionalDocument(additionalDocumentInfo);
});
workspace.OnAdditionalDocumentAdded(additionalDocumentInfo);
await VerifySolutionUpdate(workspace, s =>
{
return s.WithAdditionalDocumentText(additionalDocumentId, SourceText.From("changed"));
});
await VerifySolutionUpdate(workspace, s =>
{
return s.RemoveAdditionalDocument(additionalDocumentId);
});
}
[Fact]
public async Task TestAnalyzerConfigDocument()
{
var configPath = Path.Combine(Path.GetTempPath(), ".editorconfig");
var code = @"class Test { void Method() { } }";
using var workspace = TestWorkspace.CreateCSharp(code);
var projectId = workspace.CurrentSolution.ProjectIds.First();
var analyzerConfigDocumentId = DocumentId.CreateNewId(projectId);
var analyzerConfigDocumentInfo = DocumentInfo.Create(
analyzerConfigDocumentId,
name: ".editorconfig",
loader: TextLoader.From(TextAndVersion.Create(SourceText.From("root = true"), VersionStamp.Create(), filePath: configPath)),
filePath: configPath);
await VerifySolutionUpdate(workspace, s =>
{
return s.AddAnalyzerConfigDocuments(ImmutableArray.Create(analyzerConfigDocumentInfo));
});
workspace.OnAnalyzerConfigDocumentAdded(analyzerConfigDocumentInfo);
await VerifySolutionUpdate(workspace, s =>
{
return s.WithAnalyzerConfigDocumentText(analyzerConfigDocumentId, SourceText.From("root = false"));
});
await VerifySolutionUpdate(workspace, s =>
{
return s.RemoveAnalyzerConfigDocument(analyzerConfigDocumentId);
});
}
[Fact, Trait(Traits.Feature, Traits.Features.RemoteHost)]
public async Task TestDocument()
{
var code = @"class Test { void Method() { } }";
using var workspace = TestWorkspace.CreateCSharp(code);
var projectId = workspace.CurrentSolution.ProjectIds.First();
var documentId = DocumentId.CreateNewId(projectId);
var documentInfo = DocumentInfo.Create(
documentId, "sourceFile",
loader: TextLoader.From(TextAndVersion.Create(SourceText.From("class A { }"), VersionStamp.Create())));
await VerifySolutionUpdate(workspace, s =>
{
return s.AddDocument(documentInfo);
});
workspace.OnDocumentAdded(documentInfo);
await VerifySolutionUpdate(workspace, s =>
{
return s.WithDocumentText(documentId, SourceText.From("class Changed { }"));
});
await VerifySolutionUpdate(workspace, s =>
{
return s.RemoveDocument(documentId);
});
}
[Fact]
public async Task TestRemoteWorkspaceSolutionCrawler()
{
var code = @"class Test { void Method() { } }";
// create base solution
using var workspace = TestWorkspace.CreateCSharp(code);
using var remoteWorkspace = CreateRemoteWorkspace();
// Start solution crawler in the remote workspace:
remoteWorkspace.Services.GetRequiredService<ISolutionCrawlerRegistrationService>().Register(remoteWorkspace);
// create solution service
var solution = workspace.CurrentSolution;
var assetProvider = await GetAssetProviderAsync(workspace, remoteWorkspace, solution);
// update primary workspace
var solutionChecksum = await solution.State.GetChecksumAsync(CancellationToken.None);
await remoteWorkspace.UpdatePrimaryBranchSolutionAsync(assetProvider, solutionChecksum, solution.WorkspaceVersion, CancellationToken.None);
// get solution in remote host
var remoteSolution = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, solutionChecksum, updatePrimaryBranch: false, workspaceVersion: -1, CancellationToken.None);
// get solution cralwer in remote host
var solutionCrawlerService = remoteSolution.Services.GetService<ISolutionCrawlerRegistrationService>() as SolutionCrawlerRegistrationService;
Assert.NotNull(solutionCrawlerService);
// check remote workspace has enabled solution crawler in remote host
var testAnalyzerProvider = new TestAnalyzerProvider();
solutionCrawlerService.AddAnalyzerProvider(
testAnalyzerProvider,
new IncrementalAnalyzerProviderMetadata("Test", highPriorityForActiveFile: false, workspaceKinds: WorkspaceKind.RemoteWorkspace));
// check our solution crawler has ran
Assert.True(await testAnalyzerProvider.Analyzer.Called);
testAnalyzerProvider.Analyzer.Reset();
// update remote workspace
remoteSolution = remoteSolution.WithDocumentText(remoteSolution.Projects.First().Documents.First().Id, SourceText.From(code + " class Test2 { }"));
await remoteWorkspace.GetTestAccessor().TryUpdateWorkspaceCurrentSolutionAsync(remoteSolution, solution.WorkspaceVersion + 1);
// check solution update correctly ran solution crawler
Assert.True(await testAnalyzerProvider.Analyzer.Called);
}
[Fact]
public async Task TestRemoteWorkspace()
{
var code = @"class Test { void Method() { } }";
// create base solution
using var workspace = TestWorkspace.CreateCSharp(code);
using var remoteWorkspace = CreateRemoteWorkspace();
// create solution service
var solution1 = workspace.CurrentSolution;
var assetProvider = await GetAssetProviderAsync(workspace, remoteWorkspace, solution1);
var remoteSolution1 = await GetInitialOOPSolutionAsync(remoteWorkspace, assetProvider, solution1);
await Verify(remoteWorkspace, solution1, remoteSolution1, expectRemoteSolutionToCurrent: true);
var version = solution1.WorkspaceVersion;
// update remote workspace
var currentSolution = remoteSolution1.WithDocumentText(remoteSolution1.Projects.First().Documents.First().Id, SourceText.From(code + " class Test2 { }"));
var (oopSolution2, _) = await remoteWorkspace.GetTestAccessor().TryUpdateWorkspaceCurrentSolutionAsync(currentSolution, ++version);
await Verify(remoteWorkspace, currentSolution, oopSolution2, expectRemoteSolutionToCurrent: true);
// move backward
await Verify(remoteWorkspace, remoteSolution1, (await remoteWorkspace.GetTestAccessor().TryUpdateWorkspaceCurrentSolutionAsync(remoteSolution1, solution1.WorkspaceVersion)).solution, expectRemoteSolutionToCurrent: false);
// move forward
currentSolution = oopSolution2.WithDocumentText(oopSolution2.Projects.First().Documents.First().Id, SourceText.From(code + " class Test3 { }"));
var remoteSolution3 = (await remoteWorkspace.GetTestAccessor().TryUpdateWorkspaceCurrentSolutionAsync(currentSolution, ++version)).solution;
await Verify(remoteWorkspace, currentSolution, remoteSolution3, expectRemoteSolutionToCurrent: true);
// move to new solution backward
var solutionInfo2 = await assetProvider.CreateSolutionInfoAsync(await solution1.State.GetChecksumAsync(CancellationToken.None), CancellationToken.None);
var solution2 = remoteWorkspace.GetTestAccessor().CreateSolutionFromInfo(solutionInfo2);
Assert.False((await remoteWorkspace.GetTestAccessor().TryUpdateWorkspaceCurrentSolutionAsync(
solution2, solution1.WorkspaceVersion)).updated);
// move to new solution forward
var (solution3, updated3) = await remoteWorkspace.GetTestAccessor().TryUpdateWorkspaceCurrentSolutionAsync(
solution2, ++version);
Assert.NotNull(solution3);
Assert.True(updated3);
await Verify(remoteWorkspace, solution1, solution3, expectRemoteSolutionToCurrent: true);
static async Task<Solution> GetInitialOOPSolutionAsync(RemoteWorkspace remoteWorkspace, AssetProvider assetProvider, Solution solution)
{
// set up initial solution
var solutionChecksum = await solution.State.GetChecksumAsync(CancellationToken.None);
await remoteWorkspace.UpdatePrimaryBranchSolutionAsync(assetProvider, solutionChecksum, solution.WorkspaceVersion, CancellationToken.None);
// get solution in remote host
return await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, solutionChecksum, updatePrimaryBranch: false, workspaceVersion: -1, CancellationToken.None);
}
static async Task Verify(RemoteWorkspace remoteWorkspace, Solution givenSolution, Solution remoteSolution, bool expectRemoteSolutionToCurrent)
{
// verify we got solution expected
Assert.Equal(await givenSolution.State.GetChecksumAsync(CancellationToken.None), await remoteSolution.State.GetChecksumAsync(CancellationToken.None));
// verify remote workspace got updated
Assert.True(expectRemoteSolutionToCurrent == (remoteSolution == remoteWorkspace.CurrentSolution));
}
}
[Theory, CombinatorialData]
[WorkItem("https://github.com/dotnet/roslyn/issues/48564")]
public async Task TestAddingProjectsWithExplicitOptions(bool useDefaultOptionValue)
{
using var workspace = TestWorkspace.CreateCSharp(@"public class C { }");
using var remoteWorkspace = CreateRemoteWorkspace();
// Initial empty solution
var solution = workspace.CurrentSolution;
solution = solution.RemoveProject(solution.ProjectIds.Single());
var assetProvider = await GetAssetProviderAsync(workspace, remoteWorkspace, solution);
var solutionChecksum = await solution.State.GetChecksumAsync(CancellationToken.None);
var synched = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, solutionChecksum, updatePrimaryBranch: true, workspaceVersion: 0, CancellationToken.None);
Assert.Equal(solutionChecksum, await synched.State.GetChecksumAsync(CancellationToken.None));
// Add a C# project and a VB project, set some options, and check again
var csharpDocument = new TestHostDocument("public class C { }");
var csharpProject = new TestHostProject(workspace, csharpDocument, language: LanguageNames.CSharp, name: "project2");
var csharpProjectInfo = csharpProject.ToProjectInfo();
var vbDocument = new TestHostDocument("Public Class D \r\n Inherits C\r\nEnd Class");
var vbProject = new TestHostProject(workspace, vbDocument, language: LanguageNames.VisualBasic, name: "project3");
var vbProjectInfo = vbProject.ToProjectInfo();
solution = solution.AddProject(csharpProjectInfo).AddProject(vbProjectInfo);
var newOptionValue = useDefaultOptionValue
? FormattingOptions2.NewLine.DefaultValue
: FormattingOptions2.NewLine.DefaultValue + FormattingOptions2.NewLine.DefaultValue;
solution = solution.WithOptions(solution.Options
.WithChangedOption(FormattingOptions.NewLine, LanguageNames.CSharp, newOptionValue)
.WithChangedOption(FormattingOptions.NewLine, LanguageNames.VisualBasic, newOptionValue));
assetProvider = await GetAssetProviderAsync(workspace, remoteWorkspace, solution);
solutionChecksum = await solution.State.GetChecksumAsync(CancellationToken.None);
synched = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, solutionChecksum, updatePrimaryBranch: true, workspaceVersion: 2, CancellationToken.None);
Assert.Equal(solutionChecksum, await synched.State.GetChecksumAsync(CancellationToken.None));
}
[Fact]
public async Task TestFrozenSourceGeneratedDocument()
{
using var workspace = TestWorkspace.CreateCSharp(@"");
using var remoteWorkspace = CreateRemoteWorkspace();
var solution = workspace.CurrentSolution
.Projects.Single()
.AddAnalyzerReference(new AnalyzerFileReference(typeof(Microsoft.CodeAnalysis.TestSourceGenerator.HelloWorldGenerator).Assembly.Location, new TestAnalyzerAssemblyLoader()))
.Solution;
// First sync the solution over that has a generator
var assetProvider = await GetAssetProviderAsync(workspace, remoteWorkspace, solution);
var solutionChecksum = await solution.State.GetChecksumAsync(CancellationToken.None);
var synched = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, solutionChecksum, updatePrimaryBranch: true, workspaceVersion: 0, CancellationToken.None);
Assert.Equal(solutionChecksum, await synched.State.GetChecksumAsync(CancellationToken.None));
// Now freeze with some content
var documentIdentity = (await solution.Projects.Single().GetSourceGeneratedDocumentsAsync()).First().Identity;
var frozenText1 = SourceText.From("// Hello, World!");
var frozenSolution1 = solution.WithFrozenSourceGeneratedDocument(documentIdentity, frozenText1).Project.Solution;
assetProvider = await GetAssetProviderAsync(workspace, remoteWorkspace, frozenSolution1);
solutionChecksum = await frozenSolution1.State.GetChecksumAsync(CancellationToken.None);
synched = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, solutionChecksum, updatePrimaryBranch: true, workspaceVersion: 1, CancellationToken.None);
Assert.Equal(solutionChecksum, await synched.State.GetChecksumAsync(CancellationToken.None));
// Try freezing with some different content from the original solution
var frozenText2 = SourceText.From("// Hello, World! A second time!");
var frozenSolution2 = solution.WithFrozenSourceGeneratedDocument(documentIdentity, frozenText2).Project.Solution;
assetProvider = await GetAssetProviderAsync(workspace, remoteWorkspace, frozenSolution2);
solutionChecksum = await frozenSolution2.State.GetChecksumAsync(CancellationToken.None);
synched = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, solutionChecksum, updatePrimaryBranch: true, workspaceVersion: 2, CancellationToken.None);
Assert.Equal(solutionChecksum, await synched.State.GetChecksumAsync(CancellationToken.None));
}
[Fact]
public async Task TestPartialProjectSync_GetSolutionFirst()
{
var code = @"class Test { void Method() { } }";
using var workspace = TestWorkspace.CreateCSharp(code);
using var remoteWorkspace = CreateRemoteWorkspace();
var solution = workspace.CurrentSolution;
var project1 = solution.Projects.Single();
var project2 = solution.AddProject("P2", "P2", LanguageNames.CSharp);
solution = project2.Solution;
var assetProvider = await GetAssetProviderAsync(workspace, remoteWorkspace, solution);
var solutionChecksum = await solution.State.GetChecksumAsync(CancellationToken.None);
var syncedFullSolution = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, solutionChecksum, updatePrimaryBranch: false, workspaceVersion: -1, CancellationToken.None);
Assert.Equal(solutionChecksum, await syncedFullSolution.State.GetChecksumAsync(CancellationToken.None));
Assert.Equal(2, syncedFullSolution.Projects.Count());
// Syncing project1 should do nothing as syncing the solution already synced it over.
var project1Checksum = await solution.State.GetChecksumAsync(project1.Id, CancellationToken.None);
var project1SyncedSolution = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, project1Checksum, updatePrimaryBranch: false, workspaceVersion: -1, CancellationToken.None);
Assert.Equal(2, project1SyncedSolution.Projects.Count());
// Syncing project2 should do nothing as syncing the solution already synced it over.
var project2Checksum = await solution.State.GetChecksumAsync(project2.Id, CancellationToken.None);
var project2SyncedSolution = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, project2Checksum, updatePrimaryBranch: false, workspaceVersion: -1, CancellationToken.None);
Assert.Equal(2, project2SyncedSolution.Projects.Count());
}
[Fact]
public async Task TestPartialProjectSync_GetSolutionLast()
{
var code = @"class Test { void Method() { } }";
using var workspace = TestWorkspace.CreateCSharp(code);
using var remoteWorkspace = CreateRemoteWorkspace();
var solution = workspace.CurrentSolution;
var project1 = solution.Projects.Single();
var project2 = solution.AddProject("P2", "P2", LanguageNames.CSharp);
solution = project2.Solution;
var map = new Dictionary<Checksum, object>();
var assetProvider = new AssetProvider(
Checksum.Create(ImmutableArray.CreateRange(Guid.NewGuid().ToByteArray())), new SolutionAssetCache(), new SimpleAssetSource(workspace.Services.GetService<ISerializerService>(), map), remoteWorkspace.Services.GetService<ISerializerService>());
// Syncing project 1 should just since it over.
await solution.AppendAssetMapAsync(map, project1.Id, CancellationToken.None);
var project1Checksum = await solution.State.GetChecksumAsync(project1.Id, CancellationToken.None);
var project1SyncedSolution = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, project1Checksum, updatePrimaryBranch: false, workspaceVersion: -1, CancellationToken.None);
Assert.Equal(1, project1SyncedSolution.Projects.Count());
Assert.Equal(project1.Name, project1SyncedSolution.Projects.Single().Name);
// Syncing project 2 should end up with p1 and p2 synced over.
await solution.AppendAssetMapAsync(map, project2.Id, CancellationToken.None);
var project2Checksum = await solution.State.GetChecksumAsync(project2.Id, CancellationToken.None);
var project2SyncedSolution = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, project2Checksum, updatePrimaryBranch: false, workspaceVersion: -1, CancellationToken.None);
Assert.Equal(2, project2SyncedSolution.Projects.Count());
// then syncing the whole project should have no effect.
await solution.AppendAssetMapAsync(map, CancellationToken.None);
var solutionChecksum = await solution.State.GetChecksumAsync(CancellationToken.None);
var syncedFullSolution = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, solutionChecksum, updatePrimaryBranch: false, workspaceVersion: -1, CancellationToken.None);
Assert.Equal(solutionChecksum, await syncedFullSolution.State.GetChecksumAsync(CancellationToken.None));
Assert.Equal(2, syncedFullSolution.Projects.Count());
}
[Fact]
public async Task TestPartialProjectSync_GetDependentProjects1()
{
var code = @"class Test { void Method() { } }";
using var workspace = TestWorkspace.CreateCSharp(code);
using var remoteWorkspace = CreateRemoteWorkspace();
var solution = workspace.CurrentSolution;
var project1 = solution.Projects.Single();
var project2 = solution.AddProject("P2", "P2", LanguageNames.CSharp);
var project3 = project2.Solution.AddProject("P3", "P3", LanguageNames.CSharp);
solution = project3.Solution.AddProjectReference(project3.Id, new(project3.Solution.Projects.Single(p => p.Name == "P2").Id));
var map = new Dictionary<Checksum, object>();
var assetProvider = new AssetProvider(
Checksum.Create(ImmutableArray.CreateRange(Guid.NewGuid().ToByteArray())), new SolutionAssetCache(), new SimpleAssetSource(workspace.Services.GetService<ISerializerService>(), map), remoteWorkspace.Services.GetService<ISerializerService>());
await solution.AppendAssetMapAsync(map, project2.Id, CancellationToken.None);
var project2Checksum = await solution.State.GetChecksumAsync(project2.Id, CancellationToken.None);
var project2SyncedSolution = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, project2Checksum, updatePrimaryBranch: false, workspaceVersion: -1, CancellationToken.None);
Assert.Equal(1, project2SyncedSolution.Projects.Count());
Assert.Equal(project2.Name, project2SyncedSolution.Projects.Single().Name);
// syncing project 3 should sync project 2 as well because of the p2p ref
await solution.AppendAssetMapAsync(map, project3.Id, CancellationToken.None);
var project3Checksum = await solution.State.GetChecksumAsync(project3.Id, CancellationToken.None);
var project3SyncedSolution = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, project3Checksum, updatePrimaryBranch: false, workspaceVersion: -1, CancellationToken.None);
Assert.Equal(2, project3SyncedSolution.Projects.Count());
}
[Fact]
public async Task TestPartialProjectSync_GetDependentProjects2()
{
var code = @"class Test { void Method() { } }";
using var workspace = TestWorkspace.CreateCSharp(code);
using var remoteWorkspace = CreateRemoteWorkspace();
var solution = workspace.CurrentSolution;
var project1 = solution.Projects.Single();
var project2 = solution.AddProject("P2", "P2", LanguageNames.CSharp);
var project3 = project2.Solution.AddProject("P3", "P3", LanguageNames.CSharp);
solution = project3.Solution.AddProjectReference(project3.Id, new(project3.Solution.Projects.Single(p => p.Name == "P2").Id));
var map = new Dictionary<Checksum, object>();
var assetProvider = new AssetProvider(
Checksum.Create(ImmutableArray.CreateRange(Guid.NewGuid().ToByteArray())), new SolutionAssetCache(), new SimpleAssetSource(workspace.Services.GetService<ISerializerService>(), map), remoteWorkspace.Services.GetService<ISerializerService>());
// syncing P3 should since project P2 as well because of the p2p ref
await solution.AppendAssetMapAsync(map, project3.Id, CancellationToken.None);
var project3Checksum = await solution.State.GetChecksumAsync(project3.Id, CancellationToken.None);
var project3SyncedSolution = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, project3Checksum, updatePrimaryBranch: false, workspaceVersion: -1, CancellationToken.None);
Assert.Equal(2, project3SyncedSolution.Projects.Count());
// if we then sync just P2, we should still have P2 and P3 from the prior sync
await solution.AppendAssetMapAsync(map, project2.Id, CancellationToken.None);
var project2Checksum = await solution.State.GetChecksumAsync(project2.Id, CancellationToken.None);
var project2SyncedSolution = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, project2Checksum, updatePrimaryBranch: false, workspaceVersion: -1, CancellationToken.None);
Assert.Equal(2, project2SyncedSolution.Projects.Count());
AssertEx.SetEqual(new[] { project2.Name, project3.Name }, project2SyncedSolution.Projects.Select(p => p.Name));
// if we then sync just P1, we should have 3 projects synved over now.
await solution.AppendAssetMapAsync(map, project1.Id, CancellationToken.None);
var project1Checksum = await solution.State.GetChecksumAsync(project1.Id, CancellationToken.None);
var project1SyncedSolution = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, project1Checksum, updatePrimaryBranch: false, workspaceVersion: -1, CancellationToken.None);
Assert.Equal(3, project1SyncedSolution.Projects.Count());
AssertEx.SetEqual(new[] { project1.Name, project2.Name, project3.Name }, project1SyncedSolution.Projects.Select(p => p.Name));
}
[Fact]
public async Task TestPartialProjectSync_GetDependentProjects3()
{
var code = @"class Test { void Method() { } }";
using var workspace = TestWorkspace.CreateCSharp(code);
using var remoteWorkspace = CreateRemoteWorkspace();
var solution = workspace.CurrentSolution;
var project1 = solution.Projects.Single();
var project2 = solution.AddProject("P2", "P2", LanguageNames.CSharp);
var project3 = project2.Solution.AddProject("P3", "P3", LanguageNames.CSharp);
solution = project3.Solution.AddProjectReference(project3.Id, new(project2.Id))
.AddProjectReference(project2.Id, new(project1.Id));
var map = new Dictionary<Checksum, object>();
var assetProvider = new AssetProvider(
Checksum.Create(ImmutableArray.CreateRange(Guid.NewGuid().ToByteArray())), new SolutionAssetCache(), new SimpleAssetSource(workspace.Services.GetService<ISerializerService>(), map), remoteWorkspace.Services.GetService<ISerializerService>());
// syncing project3 should since project2 and project1 as well because of the p2p ref
await solution.AppendAssetMapAsync(map, project3.Id, CancellationToken.None);
var project3Checksum = await solution.State.GetChecksumAsync(project3.Id, CancellationToken.None);
var project3SyncedSolution = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, project3Checksum, updatePrimaryBranch: false, workspaceVersion: -1, CancellationToken.None);
Assert.Equal(3, project3SyncedSolution.Projects.Count());
// syncing project2 should do nothing as everything is already synced
var project2Checksum = await solution.State.GetChecksumAsync(project2.Id, CancellationToken.None);
var project2SyncedSolution = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, project2Checksum, updatePrimaryBranch: false, workspaceVersion: -1, CancellationToken.None);
Assert.Equal(3, project2SyncedSolution.Projects.Count());
// syncing project1 should do nothing as everything is already synced
var project1Checksum = await solution.State.GetChecksumAsync(project1.Id, CancellationToken.None);
var project1SyncedSolution = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, project1Checksum, updatePrimaryBranch: false, workspaceVersion: -1, CancellationToken.None);
Assert.Equal(3, project1SyncedSolution.Projects.Count());
}
[Fact]
public async Task TestPartialProjectSync_GetDependentProjects4()
{
var code = @"class Test { void Method() { } }";
using var workspace = TestWorkspace.CreateCSharp(code);
using var remoteWorkspace = CreateRemoteWorkspace();
var solution = workspace.CurrentSolution;
var project1 = solution.Projects.Single();
var project2 = solution.AddProject("P2", "P2", LanguageNames.CSharp);
var project3 = project2.Solution.AddProject("P3", "P3", LanguageNames.CSharp);
solution = project3.Solution.AddProjectReference(project3.Id, new(project2.Id))
.AddProjectReference(project3.Id, new(project1.Id));
var map = new Dictionary<Checksum, object>();
var assetProvider = new AssetProvider(
Checksum.Create(ImmutableArray.CreateRange(Guid.NewGuid().ToByteArray())), new SolutionAssetCache(), new SimpleAssetSource(workspace.Services.GetService<ISerializerService>(), map), remoteWorkspace.Services.GetService<ISerializerService>());
// syncing project3 should since project2 and project1 as well because of the p2p ref
await solution.AppendAssetMapAsync(map, project3.Id, CancellationToken.None);
var project3Checksum = await solution.State.GetChecksumAsync(project3.Id, CancellationToken.None);
var project3SyncedSolution = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, project3Checksum, updatePrimaryBranch: false, workspaceVersion: -1, CancellationToken.None);
Assert.Equal(3, project3SyncedSolution.Projects.Count());
// Syncing project2 should do nothing as it's already synced
var project2Checksum = await solution.State.GetChecksumAsync(project2.Id, CancellationToken.None);
var project2SyncedSolution = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, project2Checksum, updatePrimaryBranch: false, workspaceVersion: -1, CancellationToken.None);
Assert.Equal(3, project2SyncedSolution.Projects.Count());
// Syncing project1 should do nothing as it's already synced
var project1Checksum = await solution.State.GetChecksumAsync(project1.Id, CancellationToken.None);
var project1SyncedSolution = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, project1Checksum, updatePrimaryBranch: false, workspaceVersion: -1, CancellationToken.None);
Assert.Equal(3, project1SyncedSolution.Projects.Count());
}
[Fact]
public async Task TestPartialProjectSync_Options1()
{
var code = @"class Test { void Method() { } }";
using var workspace = TestWorkspace.CreateCSharp(code);
using var remoteWorkspace = CreateRemoteWorkspace();
var solution = workspace.CurrentSolution;
var project1 = solution.Projects.Single();
var project2 = solution.AddProject("P2", "P2", LanguageNames.VisualBasic);
solution = project2.Solution;
var map = new Dictionary<Checksum, object>();
var assetProvider = new AssetProvider(
Checksum.Create(ImmutableArray.CreateRange(Guid.NewGuid().ToByteArray())), new SolutionAssetCache(), new SimpleAssetSource(workspace.Services.GetService<ISerializerService>(), map), remoteWorkspace.Services.GetService<ISerializerService>());
// Syncing over project1 should give us 1 set of options on the OOP side.
await solution.AppendAssetMapAsync(map, project1.Id, CancellationToken.None);
var project1Checksum = await solution.State.GetChecksumAsync(project1.Id, CancellationToken.None);
var project1SyncedSolution = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, project1Checksum, updatePrimaryBranch: false, workspaceVersion: -1, CancellationToken.None);
Assert.Equal(1, project1SyncedSolution.Projects.Count());
Assert.Equal(project1.Name, project1SyncedSolution.Projects.Single().Name);
// Syncing over project2 should now give two sets of options.
await solution.AppendAssetMapAsync(map, project2.Id, CancellationToken.None);
var project2Checksum = await solution.State.GetChecksumAsync(project2.Id, CancellationToken.None);
var project2SyncedSolution = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, project2Checksum, updatePrimaryBranch: false, workspaceVersion: -1, CancellationToken.None);
Assert.Equal(2, project2SyncedSolution.Projects.Count());
}
[Fact]
public async Task TestPartialProjectSync_ReferenceToNonExistentProject()
{
var code = @"class Test { void Method() { } }";
using var workspace = TestWorkspace.CreateCSharp(code);
using var remoteWorkspace = CreateRemoteWorkspace();
var solution = workspace.CurrentSolution;
var project1 = solution.Projects.Single();
// This reference a project that doesn't exist.
// Ensure that it's still fine to get the checksum for this project we have.
project1 = project1.AddProjectReference(new ProjectReference(ProjectId.CreateNewId()));
solution = project1.Solution;
var assetProvider = await GetAssetProviderAsync(workspace, remoteWorkspace, solution);
var project1Checksum = await solution.State.GetChecksumAsync(project1.Id, CancellationToken.None);
}
private static async Task VerifySolutionUpdate(string code, Func<Solution, Solution> newSolutionGetter)
{
using var workspace = TestWorkspace.CreateCSharp(code);
await VerifySolutionUpdate(workspace, newSolutionGetter);
}
private static async Task VerifySolutionUpdate(
TestWorkspace workspace,
Func<Solution, Solution> newSolutionGetter,
Action<Solution> oldSolutionValidator = null,
Action<Solution> newSolutionValidator = null)
{
var solution = workspace.CurrentSolution;
oldSolutionValidator?.Invoke(solution);
var map = new Dictionary<Checksum, object>();
using var remoteWorkspace = CreateRemoteWorkspace();
var assetProvider = await GetAssetProviderAsync(workspace, remoteWorkspace, solution, map);
var solutionChecksum = await solution.State.GetChecksumAsync(CancellationToken.None);
// update primary workspace
await remoteWorkspace.UpdatePrimaryBranchSolutionAsync(assetProvider, solutionChecksum, solution.WorkspaceVersion, CancellationToken.None);
var recoveredSolution = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, solutionChecksum, updatePrimaryBranch: false, workspaceVersion: -1, CancellationToken.None);
oldSolutionValidator?.Invoke(recoveredSolution);
Assert.Equal(WorkspaceKind.RemoteWorkspace, recoveredSolution.WorkspaceKind);
Assert.Equal(solutionChecksum, await recoveredSolution.State.GetChecksumAsync(CancellationToken.None));
// get new solution
var newSolution = newSolutionGetter(solution);
var newSolutionChecksum = await newSolution.State.GetChecksumAsync(CancellationToken.None);
await newSolution.AppendAssetMapAsync(map, CancellationToken.None);
// get solution without updating primary workspace
var recoveredNewSolution = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, newSolutionChecksum, updatePrimaryBranch: false, workspaceVersion: -1, CancellationToken.None);
Assert.Equal(newSolutionChecksum, await recoveredNewSolution.State.GetChecksumAsync(CancellationToken.None));
// do same once updating primary workspace
await remoteWorkspace.UpdatePrimaryBranchSolutionAsync(assetProvider, newSolutionChecksum, solution.WorkspaceVersion + 1, CancellationToken.None);
var third = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, newSolutionChecksum, updatePrimaryBranch: false, workspaceVersion: -1, CancellationToken.None);
Assert.Equal(newSolutionChecksum, await third.State.GetChecksumAsync(CancellationToken.None));
newSolutionValidator?.Invoke(recoveredNewSolution);
}
private static async Task<AssetProvider> GetAssetProviderAsync(Workspace workspace, RemoteWorkspace remoteWorkspace, Solution solution, Dictionary<Checksum, object> map = null)
{
// make sure checksum is calculated
await solution.State.GetChecksumAsync(CancellationToken.None);
map ??= new Dictionary<Checksum, object>();
await solution.AppendAssetMapAsync(map, CancellationToken.None);
var sessionId = Checksum.Create(ImmutableArray.CreateRange(Guid.NewGuid().ToByteArray()));
var storage = new SolutionAssetCache();
var assetSource = new SimpleAssetSource(workspace.Services.GetService<ISerializerService>(), map);
return new AssetProvider(sessionId, storage, assetSource, remoteWorkspace.Services.GetService<ISerializerService>());
}
private class TestAnalyzerProvider : IIncrementalAnalyzerProvider
{
public readonly TestAnalyzer Analyzer = new TestAnalyzer();
public IIncrementalAnalyzer CreateIncrementalAnalyzer(Workspace workspace)
{
return Analyzer;
}
public class TestAnalyzer : IncrementalAnalyzerBase
{
private TaskCompletionSource<bool> _source = new TaskCompletionSource<bool>();
public override Task AnalyzeDocumentAsync(Document document, SyntaxNode bodyOpt, InvocationReasons reasons, CancellationToken cancellationToken)
{
_source.SetResult(true);
return Task.CompletedTask;
}
public Task<bool> Called => _source.Task;
public void Reset()
{
_source = new TaskCompletionSource<bool>();
}
}
}
}
}
|