diff --git a/src/VisualStudio/Core/Test.Next/Services/ServiceHubServicesTests.cs b/src/VisualStudio/Core/Test.Next/Services/ServiceHubServicesTests.cs index 33736957a313e7c55ff0d088125f08fd93e8309b..94364ff48d75b0315373c70bda515ab5b1b2880c 100644 --- a/src/VisualStudio/Core/Test.Next/Services/ServiceHubServicesTests.cs +++ b/src/VisualStudio/Core/Test.Next/Services/ServiceHubServicesTests.cs @@ -58,6 +58,22 @@ public async Task TestRemoteHostSynchronize() } } + [Fact, Trait(Traits.Feature, Traits.Features.RemoteHost)] + public async Task TestUnknownProject() + { + var workspace = new AdhocWorkspace(TestHostServices.CreateHostServices()); + var solution = workspace.CurrentSolution.AddProject("unknown", "unknown", NoCompilationConstants.LanguageName).Solution; + + var client = (InProcRemoteHostClient)(await InProcRemoteHostClient.CreateAsync(workspace, runCacheCleanup: false, cancellationToken: CancellationToken.None)); + + await UpdatePrimaryWorkspace(client, solution); + VerifyAssetStorage(client, solution); + + Assert.Equal( + await solution.State.GetChecksumAsync(CancellationToken.None), + await PrimaryWorkspace.Workspace.CurrentSolution.State.GetChecksumAsync(CancellationToken.None)); + } + [Fact, Trait(Traits.Feature, Traits.Features.RemoteHost)] public async Task TestRemoteHostSynchronizeIncrementalUpdate() { diff --git a/src/VisualStudio/Core/Test.Next/Services/SolutionServiceTests.cs b/src/VisualStudio/Core/Test.Next/Services/SolutionServiceTests.cs index 868040bc7f714fa3847b89415885b4a38b997171..f0e5343aea6efd0f4b7cd5179d80fb8f54107d86 100644 --- a/src/VisualStudio/Core/Test.Next/Services/SolutionServiceTests.cs +++ b/src/VisualStudio/Core/Test.Next/Services/SolutionServiceTests.cs @@ -1,5 +1,6 @@ // Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using System; using System.Collections.Generic; using System.Linq; using System.Threading; @@ -137,6 +138,27 @@ 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, Trait(Traits.Feature, Traits.Features.RemoteHost)] + public async Task TestUpdateProjectInfo() + { + var code = @"class Test { void Method() { } }"; + + await VerifySolutionUpdate(code, s => s.Projects.First().WithAssemblyName("test2").Solution); + } + + [Fact, Trait(Traits.Feature, Traits.Features.RemoteHost)] + public async Task TestUpdateDocumentInfo() + { + var code = @"class Test { void Method() { } }"; + + await VerifySolutionUpdate(code, s => s.WithDocumentFolders(s.Projects.First().Documents.First().Id, new[] { "test" })); + } + + private static async Task VerifySolutionUpdate(string code, Func newSolutionGetter) + { using (var workspace = await TestWorkspace.CreateCSharpAsync(code)) { var map = new Dictionary(); @@ -153,8 +175,8 @@ public async Task TestUpdatePrimaryWorkspace() Assert.Equal(solutionChecksum, await first.State.GetChecksumAsync(CancellationToken.None)); Assert.True(object.ReferenceEquals(PrimaryWorkspace.Workspace.PrimaryBranchId, first.BranchId)); - // change content - var newSolution = solution.WithDocumentText(solution.Projects.First().DocumentIds.First(), SourceText.From(code + " ")); + // get new solution + var newSolution = newSolutionGetter(solution); var newSolutionChecksum = await newSolution.State.GetChecksumAsync(CancellationToken.None); newSolution.AppendAssetMap(map); diff --git a/src/VisualStudio/Core/Test.Next/TestUtils.cs b/src/VisualStudio/Core/Test.Next/TestUtils.cs index 2889400b1d23b4876a65ab71a9eac5fa58e7ad84..10b11d9df59109cef667e57473a4031f918afa47 100644 --- a/src/VisualStudio/Core/Test.Next/TestUtils.cs +++ b/src/VisualStudio/Core/Test.Next/TestUtils.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Threading; using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Remote; using Microsoft.CodeAnalysis.Serialization; using Xunit; @@ -44,7 +45,11 @@ public static void AppendAssetMap(this Solution solution, Dictionary map) { ProjectStateChecksums projectChecksums; - Assert.True(project.State.TryGetStateChecksums(out projectChecksums)); + if (!project.State.TryGetStateChecksums(out projectChecksums)) + { + Assert.False(RemoteSupportedLanguages.Support(project.Language)); + return; + } projectChecksums.Find(project.State, Flatten(projectChecksums), map, CancellationToken.None); diff --git a/src/Workspaces/Core/Portable/Remote/RemoteSupportedLanguages.cs b/src/Workspaces/Core/Portable/Remote/RemoteSupportedLanguages.cs new file mode 100644 index 0000000000000000000000000000000000000000..15aac4ab3fae044afce6286d9c36bd72e3afd801 --- /dev/null +++ b/src/Workspaces/Core/Portable/Remote/RemoteSupportedLanguages.cs @@ -0,0 +1,13 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +namespace Microsoft.CodeAnalysis.Remote +{ + internal static class RemoteSupportedLanguages + { + public static bool Support(this string language) + { + return language == LanguageNames.CSharp || + language == LanguageNames.VisualBasic; + } + } +} diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState_Checksum.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState_Checksum.cs index 4a4bf336cbc3bca9f3477ff588fc584c73ef8408..9916f8cda55278dfddccaf2d992094c251750124 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState_Checksum.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState_Checksum.cs @@ -3,8 +3,9 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; -using Microsoft.CodeAnalysis.Serialization; using Microsoft.CodeAnalysis.Internal.Log; +using Microsoft.CodeAnalysis.Remote; +using Microsoft.CodeAnalysis.Serialization; namespace Microsoft.CodeAnalysis { @@ -31,7 +32,9 @@ private async Task ComputeChecksumsAsync(CancellationTok using (Logger.LogBlock(FunctionId.SolutionState_ComputeChecksumsAsync, FilePath, cancellationToken)) { // get states by id order to have deterministic checksum - var projectChecksumTasks = ProjectIds.Select(id => ProjectStates[id].GetChecksumAsync(cancellationToken)); + var projectChecksumTasks = ProjectIds.Select(id => ProjectStates[id]) + .Where(s => RemoteSupportedLanguages.Support(s.Language)) + .Select(s => s.GetChecksumAsync(cancellationToken)); var serializer = new Serializer(_solutionServices.Workspace); var infoChecksum = serializer.CreateChecksum(SolutionInfo.Attributes, cancellationToken); diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/StateChecksums.cs b/src/Workspaces/Core/Portable/Workspace/Solution/StateChecksums.cs index 8af29e4366c51170e4bb0025917b823a1f68dd11..498f11bc2637a02a4ab09dcd57ac0b5b72d2850e 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/StateChecksums.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/StateChecksums.cs @@ -3,7 +3,7 @@ using System.Collections.Generic; using System.Collections.Immutable; using System.Threading; -using Microsoft.CodeAnalysis.Shared.Extensions; +using Microsoft.CodeAnalysis.Remote; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.Serialization @@ -29,6 +29,7 @@ public SolutionStateChecksums(params object[] children) : base(nameof(SolutionSt CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); + // verify input Contract.ThrowIfFalse(state.TryGetStateChecksums(out var stateChecksum)); Contract.ThrowIfFalse(this == stateChecksum); @@ -51,8 +52,14 @@ public SolutionStateChecksums(params object[] children) : base(nameof(SolutionSt foreach (var kv in state.ProjectStates) { var projectState = kv.Value; + // solution state checksum can't be created without project state checksums created first - Contract.ThrowIfFalse(projectState.TryGetStateChecksums(out var projectStateChecksums)); + // check unsupported projects + if (!projectState.TryGetStateChecksums(out var projectStateChecksums)) + { + Contract.ThrowIfTrue(RemoteSupportedLanguages.Support(projectState.Language)); + continue; + } projectStateChecksums.Find(projectState, searchingChecksumsLeft, result, cancellationToken); if (searchingChecksumsLeft.Count == 0) diff --git a/src/Workspaces/Core/Portable/Workspaces.csproj b/src/Workspaces/Core/Portable/Workspaces.csproj index c320f86fa0080e41891ae2d928ab83ec4b78051d..dd11095bd6c4a87f49de99a509f49b8e2db7cda6 100644 --- a/src/Workspaces/Core/Portable/Workspaces.csproj +++ b/src/Workspaces/Core/Portable/Workspaces.csproj @@ -384,6 +384,7 @@ + diff --git a/src/Workspaces/Remote/Core/Services/SolutionCreator.cs b/src/Workspaces/Remote/Core/Services/SolutionCreator.cs index a6b5bcf4dc4019dabc6566b789d951a7df435a59..bd4ad4b64f87b6b82ff5a127ec50052b2644d56b 100644 --- a/src/Workspaces/Remote/Core/Services/SolutionCreator.cs +++ b/src/Workspaces/Remote/Core/Services/SolutionCreator.cs @@ -207,9 +207,9 @@ private async Task UpdateProjectInfoAsync(Project project, Checksum inf var newProjectInfo = await _assetService.GetAssetAsync(infoChecksum, _cancellationToken).ConfigureAwait(false); // there is no API to change these once project is created - Contract.ThrowIfFalse(project.State.ProjectInfo.Attributes.Id != newProjectInfo.Id); - Contract.ThrowIfFalse(project.State.ProjectInfo.Attributes.Language != newProjectInfo.Language); - Contract.ThrowIfFalse(project.State.ProjectInfo.Attributes.IsSubmission != newProjectInfo.IsSubmission); + Contract.ThrowIfFalse(project.State.ProjectInfo.Attributes.Id == newProjectInfo.Id); + Contract.ThrowIfFalse(project.State.ProjectInfo.Attributes.Language == newProjectInfo.Language); + Contract.ThrowIfFalse(project.State.ProjectInfo.Attributes.IsSubmission == newProjectInfo.IsSubmission); if (project.State.ProjectInfo.Attributes.Name != newProjectInfo.Name) { @@ -321,10 +321,10 @@ private async Task UpdateDocumentInfoAsync(Document document, Checksum var newDocumentInfo = await _assetService.GetAssetAsync(infoChecksum, _cancellationToken).ConfigureAwait(false); // there is no api to change these once document is created - Contract.ThrowIfFalse(document.State.Info.Attributes.Id != newDocumentInfo.Id); - Contract.ThrowIfFalse(document.State.Info.Attributes.Name != newDocumentInfo.Name); - Contract.ThrowIfFalse(document.State.Info.Attributes.FilePath != newDocumentInfo.FilePath); - Contract.ThrowIfFalse(document.State.Info.Attributes.IsGenerated != newDocumentInfo.IsGenerated); + Contract.ThrowIfFalse(document.State.Info.Attributes.Id == newDocumentInfo.Id); + Contract.ThrowIfFalse(document.State.Info.Attributes.Name == newDocumentInfo.Name); + Contract.ThrowIfFalse(document.State.Info.Attributes.FilePath == newDocumentInfo.FilePath); + Contract.ThrowIfFalse(document.State.Info.Attributes.IsGenerated == newDocumentInfo.IsGenerated); if (document.State.Info.Attributes.Folders != newDocumentInfo.Folders) { @@ -415,13 +415,15 @@ private async Task CreateProjectInfoAsync(Checksum projectChecksum) var projectSnapshot = await _assetService.GetAssetAsync(projectChecksum, _cancellationToken).ConfigureAwait(false); var projectInfo = await _assetService.GetAssetAsync(projectSnapshot.Info, _cancellationToken).ConfigureAwait(false); - if (!_baseSolution.Workspace.Services.IsSupported(projectInfo.Language)) + if (!RemoteSupportedLanguages.Support(projectInfo.Language)) { // only add project our workspace supports. // workspace doesn't allow creating project with unknown languages return null; } + Contract.ThrowIfFalse(_baseSolution.Workspace.Services.IsSupported(projectInfo.Language)); + var compilationOptions = await _assetService.GetAssetAsync(projectSnapshot.CompilationOptions, _cancellationToken).ConfigureAwait(false); var parseOptions = await _assetService.GetAssetAsync(projectSnapshot.ParseOptions, _cancellationToken).ConfigureAwait(false);