File: SolutionTests\TryApplyChangesTests.cs
Web Access
Project: src\src\Workspaces\CoreTest\Microsoft.CodeAnalysis.Workspaces.UnitTests.csproj (Microsoft.CodeAnalysis.Workspaces.UnitTests)
// 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.
 
using System;
using System.Collections.Immutable;
using System.Linq;
using Microsoft.CodeAnalysis.Test.Utilities;
using Microsoft.CodeAnalysis.Text;
using Roslyn.Utilities;
using Xunit;
 
namespace Microsoft.CodeAnalysis.UnitTests
{
    [UseExportProvider]
    public class TryApplyChangesTests
    {
        private class CustomizedCanApplyWorkspace : Workspace
        {
            private readonly ImmutableArray<ApplyChangesKind> _allowedKinds;
            private readonly Func<ParseOptions, ParseOptions, bool>? _canApplyParseOptions;
            private readonly Func<CompilationOptions, CompilationOptions, bool>? _canApplyCompilationOptions;
 
            public CustomizedCanApplyWorkspace(params ApplyChangesKind[] allowedKinds)
                : this(allowedKinds, canApplyParseOptions: null)
            {
            }
 
            public CustomizedCanApplyWorkspace(ApplyChangesKind[] allowedKinds,
                Func<ParseOptions, ParseOptions, bool>? canApplyParseOptions = null,
                Func<CompilationOptions, CompilationOptions, bool>? canApplyCompilationOptions = null)
                : base(Host.Mef.MefHostServices.DefaultHost, workspaceKind: nameof(CustomizedCanApplyWorkspace))
            {
                _allowedKinds = [.. allowedKinds];
                _canApplyParseOptions = canApplyParseOptions;
                _canApplyCompilationOptions = canApplyCompilationOptions;
 
                // Add a C# project automatically so each test has something to try mutating
                OnProjectAdded(ProjectInfo.Create(ProjectId.CreateNewId(), VersionStamp.Default, "TestProject", "TestProject", LanguageNames.CSharp));
            }
 
            public override bool CanApplyChange(ApplyChangesKind feature)
            {
                return _allowedKinds.Contains(feature);
            }
 
            public override bool CanApplyParseOptionChange(ParseOptions oldOptions, ParseOptions newOptions, Project project)
            {
                if (_canApplyParseOptions != null)
                {
                    return _canApplyParseOptions(oldOptions, newOptions);
                }
 
                return base.CanApplyParseOptionChange(oldOptions, newOptions, project);
            }
 
            public override bool CanApplyCompilationOptionChange(CompilationOptions oldOptions, CompilationOptions newOptions, Project project)
            {
                if (_canApplyCompilationOptions != null)
                {
                    return _canApplyCompilationOptions(oldOptions, newOptions);
                }
 
                return base.CanApplyCompilationOptionChange(oldOptions, newOptions, project);
            }
        }
 
        [Fact]
        public void TryApplyWorksIfAllowingAnyCompilationOption()
        {
            // If we simply support the main change kind, then any type of change should be supported and we should not get called
            // to the other method
            using var workspace = new CustomizedCanApplyWorkspace(
                allowedKinds: [ApplyChangesKind.ChangeCompilationOptions],
                canApplyCompilationOptions: (_, __) => throw new Exception("This should not have been called."));
 
            var project = workspace.CurrentSolution.Projects.Single();
 
            Assert.True(workspace.TryApplyChanges(project.WithCompilationOptions(project.CompilationOptions!.WithMainTypeName("Test")).Solution));
        }
 
        [Fact]
        public void TryApplyWorksSpecificChangeIsAllowedForCompilationOption()
        {
            // If we don't support the main change kind, then the other method should be called
            using var workspace = new CustomizedCanApplyWorkspace(
                allowedKinds: [],
                canApplyCompilationOptions: (_, newCompilationOptions) => newCompilationOptions.MainTypeName == "Test");
 
            var project = workspace.CurrentSolution.Projects.Single();
 
            Assert.True(workspace.TryApplyChanges(project.WithCompilationOptions(project.CompilationOptions!.WithMainTypeName("Test")).Solution));
        }
 
        [Fact]
        public void TryApplyWorksThrowsIfChangeIsDisallowedForCompilationOption()
        {
            // If we don't support the main change kind, then the other method should be called
            using var workspace = new CustomizedCanApplyWorkspace(
                allowedKinds: [],
                canApplyCompilationOptions: (_, newCompilationOptions) => newCompilationOptions.MainTypeName == "Expected");
 
            var project = workspace.CurrentSolution.Projects.Single();
 
            var exception = Assert.Throws<NotSupportedException>(
                () => workspace.TryApplyChanges(project.WithCompilationOptions(project.CompilationOptions!.WithMainTypeName("WrongThing")).Solution));
 
            Assert.Equal(WorkspacesResources.Changing_compilation_options_is_not_supported, exception.Message);
        }
 
        [Fact]
        public void TryApplyWorksIfAllowingAnyParseOption()
        {
            // If we simply support the main change kind, then any type of change should be supported and we should not get called
            // to the other method
            using var workspace = new CustomizedCanApplyWorkspace(
                allowedKinds: [ApplyChangesKind.ChangeParseOptions],
                canApplyParseOptions: (_, __) => throw new Exception("This should not have been called."));
 
            var project = workspace.CurrentSolution.Projects.Single();
 
            Assert.True(workspace.TryApplyChanges(
                project.WithParseOptions(
                    project.ParseOptions!.WithFeatures([KeyValuePairUtil.Create("Feature", "")])).Solution));
        }
 
        [Fact]
        public void TryApplyWorksSpecificChangeIsAllowedForParseOption()
        {
            // If we don't support the main change kind, then the other method should be called
            using var workspace = new CustomizedCanApplyWorkspace(
                allowedKinds: [],
                canApplyParseOptions: (_, newParseOptions) => newParseOptions.Features["Feature"] == "ExpectedValue");
 
            var project = workspace.CurrentSolution.Projects.Single();
 
            Assert.True(
                workspace.TryApplyChanges(
                    project.WithParseOptions(project.ParseOptions!.WithFeatures([KeyValuePairUtil.Create("Feature", "ExpectedValue")])).Solution));
        }
 
        [Fact]
        public void TryApplyWorksThrowsIfChangeIsDisallowedForParseOption()
        {
            // If we don't support the main change kind, then the other method should be called
            using var workspace = new CustomizedCanApplyWorkspace(
                allowedKinds: [],
                canApplyParseOptions: (_, newParseOptions) => newParseOptions.Features["Feature"] == "ExpectedValue");
 
            var project = workspace.CurrentSolution.Projects.Single();
 
            var exception = Assert.Throws<NotSupportedException>(
                () => workspace.TryApplyChanges(
                    project.WithParseOptions(project.ParseOptions!.WithFeatures([KeyValuePairUtil.Create("Feature", "WrongThing")])).Solution));
 
            Assert.Equal(WorkspacesResources.Changing_parse_options_is_not_supported, exception.Message);
        }
 
        [Fact]
        public void TryApplyWorksWhenAddingEditorConfigWithoutSupportingCompilationOptionsChanging()
        {
            using var workspace = new CustomizedCanApplyWorkspace(allowedKinds: ApplyChangesKind.AddAnalyzerConfigDocument);
 
            var project = workspace.CurrentSolution.Projects.Single();
 
            Assert.True(workspace.TryApplyChanges(project.AddAnalyzerConfigDocument(".editorconfig", SourceText.From("")).Project.Solution));
        }
    }
}