File: WorkspaceServiceTests\GlobalOptionServiceTests.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.Generic;
using System.Linq;
using Microsoft.CodeAnalysis.CodeStyle;
using Microsoft.CodeAnalysis.Formatting;
using Microsoft.CodeAnalysis.Host;
using Microsoft.CodeAnalysis.Options;
using Microsoft.CodeAnalysis.Test.Utilities;
using Roslyn.Test.Utilities;
using Roslyn.Utilities;
using Xunit;
 
namespace Microsoft.CodeAnalysis.UnitTests.WorkspaceServices
{
    [UseExportProvider]
    [Trait(Traits.Feature, Traits.Features.Workspace)]
    public class GlobalOptionServiceTests
    {
        private static IGlobalOptionService GetGlobalOptionService(HostWorkspaceServices services)
            => services.SolutionServices.ExportProvider.GetExportedValue<IGlobalOptionService>();
 
        private static ILegacyGlobalOptionService GetLegacyGlobalOptionService(HostWorkspaceServices services)
            => services.SolutionServices.ExportProvider.GetExportedValue<ILegacyGlobalOptionService>();
 
        [Fact]
        public void LegacyGlobalOptions_SetGet()
        {
            using var workspace = new AdhocWorkspace();
            var optionService = GetLegacyGlobalOptionService(workspace.Services);
 
            var optionKey = new OptionKey(new TestOption() { DefaultValue = 1 });
 
            Assert.Equal(1, optionService.GetExternallyDefinedOption(optionKey));
 
            optionService.SetOptions(
                [],
                [KeyValuePairUtil.Create(optionKey, (object?)2)]);
 
            Assert.Equal(2, optionService.GetExternallyDefinedOption(optionKey));
 
            optionService.SetOptions(
                [],
                [KeyValuePairUtil.Create(optionKey, (object?)3)]);
 
            Assert.Equal(3, optionService.GetExternallyDefinedOption(optionKey));
        }
 
        [Theory, CombinatorialData]
        public void ExternallyDefinedOption(bool subclass)
        {
            using var workspace1 = new AdhocWorkspace();
            using var workspace2 = new AdhocWorkspace();
            var optionService = GetLegacyGlobalOptionService(workspace1.Services);
            var optionSet = new SolutionOptionSet(optionService);
 
            var option = subclass ? (IOption)new TestOption<int>(defaultValue: 1) : new TestOption() { DefaultValue = 1 };
            var perLanguageOption = subclass ? (IOption)new PerLanguageTestOption<int>(defaultValue: 1) : new TestOption() { IsPerLanguage = true, DefaultValue = 1 };
 
            var optionKey = new OptionKey(option);
            var perLanguageOptionKey = new OptionKey(perLanguageOption, "lang");
 
            Assert.Equal(optionKey.Option.DefaultValue, optionSet.GetOption<int>(optionKey));
            Assert.Equal(perLanguageOptionKey.Option.DefaultValue, optionSet.GetOption<int>(perLanguageOptionKey));
 
            var newSet = optionSet.WithChangedOption(optionKey, 2).WithChangedOption(perLanguageOptionKey, 3);
            Assert.Equal(2, newSet.GetOption<int>(optionKey));
            Assert.Equal(3, newSet.GetOption<int>(perLanguageOptionKey));
 
            var newSolution1 = workspace1.CurrentSolution.WithOptions(newSet);
            Assert.Equal(2, newSolution1.Options.GetOption<int>(optionKey));
            Assert.Equal(3, newSolution1.Options.GetOption<int>(perLanguageOptionKey));
 
            // TryApplyChanges propagates the option to all workspaces:
            var oldSolution2 = workspace2.CurrentSolution;
            Assert.Equal(1, oldSolution2.Options.GetOption<int>(optionKey));
            Assert.Equal(1, oldSolution2.Options.GetOption<int>(perLanguageOptionKey));
 
            Assert.True(workspace1.TryApplyChanges(newSolution1));
 
            var newSolution2 = workspace2.CurrentSolution;
            Assert.Equal(2, newSolution2.Options.GetOption<int>(optionKey));
            Assert.Equal(3, newSolution2.Options.GetOption<int>(perLanguageOptionKey));
        }
 
        [Fact]
        public void InternallyDefinedOption()
        {
            using var workspace1 = new AdhocWorkspace();
            using var workspace2 = new AdhocWorkspace();
            var optionService = GetLegacyGlobalOptionService(workspace1.Services);
            var optionSet = new SolutionOptionSet(optionService);
 
            var perLanguageOptionKey = new OptionKey(FormattingOptions.NewLine, "lang");
 
            Assert.Equal(perLanguageOptionKey.Option.DefaultValue, optionSet.GetOption<string>(perLanguageOptionKey));
 
            var newSet = optionSet.WithChangedOption(perLanguageOptionKey, "EOLN");
            Assert.Equal("EOLN", newSet.GetOption<string>(perLanguageOptionKey));
 
            var newSolution1 = workspace1.CurrentSolution.WithOptions(newSet);
            Assert.Equal("EOLN", newSolution1.Options.GetOption<string>(perLanguageOptionKey));
 
            // TryApplyChanges propagates the option to all workspaces:
            var oldSolution2 = workspace2.CurrentSolution;
            Assert.Equal(perLanguageOptionKey.Option.DefaultValue, oldSolution2.Options.GetOption<string>(perLanguageOptionKey));
 
            Assert.True(workspace1.TryApplyChanges(newSolution1));
 
            Assert.Equal("EOLN", workspace1.CurrentSolution.Options.GetOption<string>(perLanguageOptionKey));
            Assert.Equal("EOLN", workspace2.CurrentSolution.Options.GetOption<string>(perLanguageOptionKey));
 
            // Update global option directly (after the value is cached in the above current solution snapshots).
            // Doing so does NOT update current solutions.
            optionService.GlobalOptions.SetGlobalOption(FormattingOptions2.NewLine, "lang", "NEW_LINE");
 
            Assert.Equal("EOLN", workspace1.CurrentSolution.Options.GetOption<string>(perLanguageOptionKey));
            Assert.Equal("EOLN", workspace2.CurrentSolution.Options.GetOption<string>(perLanguageOptionKey));
 
            // Update global option indirectly via legacy service updates current solutions.
            optionService.SetOptions(
                [KeyValuePairUtil.Create(new OptionKey2(FormattingOptions2.NewLine, "lang"), (object?)"NEW_LINE")],
                []);
 
            Assert.Equal("NEW_LINE", workspace1.CurrentSolution.Options.GetOption<string>(perLanguageOptionKey));
            Assert.Equal("NEW_LINE", workspace2.CurrentSolution.Options.GetOption<string>(perLanguageOptionKey));
 
            // Set the option directly again and trigger workspace update:
            optionService.GlobalOptions.SetGlobalOption(FormattingOptions2.NewLine, "lang", "NEW_LINE2");
 
            Assert.Equal("NEW_LINE", workspace1.CurrentSolution.Options.GetOption<string>(perLanguageOptionKey));
            Assert.Equal("NEW_LINE", workspace2.CurrentSolution.Options.GetOption<string>(perLanguageOptionKey));
 
            optionService.UpdateRegisteredWorkspaces();
 
            Assert.Equal("NEW_LINE2", workspace1.CurrentSolution.Options.GetOption<string>(perLanguageOptionKey));
            Assert.Equal("NEW_LINE2", workspace2.CurrentSolution.Options.GetOption<string>(perLanguageOptionKey));
        }
 
        [Fact]
        public void OptionPerLanguageOption()
        {
            using var workspace = new AdhocWorkspace();
            var optionService = GetLegacyGlobalOptionService(workspace.Services);
            var optionSet = new SolutionOptionSet(optionService);
 
            var optionvalid = new PerLanguageOption<bool>("Test Feature", "Test Name", false);
            Assert.False(optionSet.GetOption(optionvalid, "CS"));
        }
 
        [Fact]
        public void GettingOptionReturnsOption()
        {
            using var workspace = new AdhocWorkspace();
            var optionService = GetLegacyGlobalOptionService(workspace.Services);
            var optionSet = new SolutionOptionSet(optionService);
            var option = new Option<bool>("Test Feature", "Test Name", false);
            Assert.False(optionSet.GetOption(option));
        }
 
        [Fact]
        public void GlobalOptions()
        {
            using var workspace = new AdhocWorkspace();
            var globalOptions = GetGlobalOptionService(workspace.Services);
            var option1 = new Option2<int>("test_option1", defaultValue: 1);
            var option2 = new Option2<int>("test_option2", defaultValue: 2);
            var option3 = new Option2<int>("test_option3", defaultValue: 3);
 
            var events = new List<OptionChangedEventArgs>();
 
            var handler = new WeakEventHandler<OptionChangedEventArgs>((_, _, e) => events.Add(e));
            globalOptions.AddOptionChangedHandler(this, handler);
 
            var values = globalOptions.GetOptions([new OptionKey2(option1), new OptionKey2(option2)]);
            Assert.Equal(1, values[0]);
            Assert.Equal(2, values[1]);
 
            globalOptions.SetGlobalOptions(
            [
                KeyValuePairUtil.Create(new OptionKey2(option1), (object?)5),
                KeyValuePairUtil.Create(new OptionKey2(option2), (object?)6),
                KeyValuePairUtil.Create(new OptionKey2(option3), (object?)3),
            ]);
 
            AssertEx.Equal(
            [
                "test_option1=5",
                "test_option2=6",
            ], events.Single().ChangedOptions.Select(e => $"{e.key.Option.Definition.ConfigName}={e.newValue}"));
 
            values = globalOptions.GetOptions([new OptionKey2(option1), new OptionKey2(option2), new OptionKey2(option3)]);
            Assert.Equal(5, values[0]);
            Assert.Equal(6, values[1]);
            Assert.Equal(3, values[2]);
 
            Assert.Equal(5, globalOptions.GetOption(option1));
            Assert.Equal(6, globalOptions.GetOption(option2));
            Assert.Equal(3, globalOptions.GetOption(option3));
 
            globalOptions.RemoveOptionChangedHandler(this, handler);
        }
 
        [Fact]
        public void GettingOptionWithChangedOption()
        {
            using var workspace = new AdhocWorkspace();
            var optionService = GetLegacyGlobalOptionService(workspace.Services);
            OptionSet optionSet = new SolutionOptionSet(optionService);
            var option = new Option<bool>("Test Feature", "Test Name", false);
            var key = new OptionKey(option);
            Assert.False(optionSet.GetOption(option));
            optionSet = optionSet.WithChangedOption(key, true);
            Assert.True((bool?)optionSet.GetOption(key));
        }
 
        [Fact]
        public void GettingOptionWithoutChangedOption()
        {
            using var workspace = new AdhocWorkspace();
            var optionSet = new SolutionOptionSet(GetLegacyGlobalOptionService(workspace.Services));
 
            var optionFalse = new Option<bool>("Test Feature", "Test Name", false);
            Assert.False(optionSet.GetOption(optionFalse));
 
            var optionTrue = new Option<bool>("Test Feature", "Test Name", true);
            Assert.True(optionSet.GetOption(optionTrue));
 
            var falseKey = new OptionKey(optionFalse);
            Assert.False((bool?)optionSet.GetOption(falseKey));
 
            var trueKey = new OptionKey(optionTrue);
            Assert.True((bool?)optionSet.GetOption(trueKey));
        }
 
        [Fact]
        public void SetKnownOptions()
        {
            using var workspace = new AdhocWorkspace();
            var optionService = GetLegacyGlobalOptionService(workspace.Services);
            var optionSet = new SolutionOptionSet(optionService);
 
            var option = new Option<bool>("Test Feature", "Test Name", defaultValue: true);
            var optionKey = new OptionKey(option);
            var newOptionSet = optionSet.WithChangedOption(optionKey, false);
            var changedOptions = ((SolutionOptionSet)newOptionSet).GetChangedOptions();
            optionService.SetOptions(changedOptions.internallyDefined, changedOptions.externallyDefined);
            var isOptionSet = (bool?)new SolutionOptionSet(optionService).GetOption(optionKey);
            Assert.False(isOptionSet);
        }
 
        [Fact]
        public void OptionSetIsImmutable()
        {
            using var workspace = new AdhocWorkspace();
            var optionService = GetLegacyGlobalOptionService(workspace.Services);
            var optionSet = new SolutionOptionSet(optionService);
 
            var option = new Option<bool>("Test Feature", "Test Name", defaultValue: true);
            var optionKey = new OptionKey(option);
            var newOptionSet = optionSet.WithChangedOption(optionKey, false);
            Assert.NotSame(optionSet, newOptionSet);
            Assert.NotEqual(optionSet, newOptionSet);
        }
 
        [Fact]
        public void TestPerLanguageCodeStyleOptions()
        {
            using var workspace = new AdhocWorkspace();
            var perLanguageOption2 = new PerLanguageOption2<CodeStyleOption2<bool>>("test", new CodeStyleOption2<bool>(false, NotificationOption2.Warning)).WithPublicOption("test", "test");
            var perLanguageOption = perLanguageOption2.ToPublicOption();
            var newValueCodeStyleOption2 = new CodeStyleOption2<bool>(!perLanguageOption2.DefaultValue.Value, perLanguageOption2.DefaultValue.Notification);
            var newValueCodeStyleOption = (CodeStyleOption<bool>)newValueCodeStyleOption2!;
 
            TestPublicOption(workspace, perLanguageOption, language: LanguageNames.CSharp, newValueCodeStyleOption);
            TestInternalOption(workspace, perLanguageOption2, language: LanguageNames.CSharp, newValueCodeStyleOption2);
 
            var optionService = GetLegacyGlobalOptionService(workspace.Services);
            var originalOptionSet = new SolutionOptionSet(optionService);
        }
 
        [Fact]
        public void TestLanguageSpecificCodeStyleOptions()
        {
            using var workspace = new AdhocWorkspace();
            var option2 = new Option2<CodeStyleOption2<bool>>("test", new CodeStyleOption2<bool>(false, NotificationOption2.Warning)).WithPublicOption("test", "test");
            var option = option2.ToPublicOption();
            var newValueCodeStyleOption2 = new CodeStyleOption2<bool>(!option2.DefaultValue.Value, option2.DefaultValue.Notification);
            var newValueCodeStyleOption = (CodeStyleOption<bool>)newValueCodeStyleOption2!;
 
            TestPublicOption(workspace, option, language: null, newValueCodeStyleOption);
            TestInternalOption(workspace, option2, language: null, newValueCodeStyleOption2);
        }
 
        private static void TestPublicOption(Workspace workspace, IPublicOption option, string? language, CodeStyleOption<bool> newPublicValue)
        {
            var optionService = GetLegacyGlobalOptionService(workspace.Services);
            var originalOptionSet = new SolutionOptionSet(optionService);
 
            var optionKey = new OptionKey(option, language);
 
            //  1. WithChangedOption(OptionKey), GetOption(OptionKey)/GetOption<T>(OptionKey)
            var newOptionSet = originalOptionSet.WithChangedOption(optionKey, newPublicValue);
            Assert.Equal(newPublicValue, newOptionSet.GetOption(optionKey));
            Assert.IsType<CodeStyleOption<bool>>(newOptionSet.GetOption(optionKey));
 
            // Verify "T GetOption<T>(OptionKey)" works for T being a public code style option type.
            Assert.Equal(newPublicValue, newOptionSet.GetOption<CodeStyleOption<bool>>(optionKey));
        }
 
        private static void TestInternalOption(Workspace workspace, IOption2 option, string? language, CodeStyleOption2<bool> newValue)
        {
            var optionService = GetLegacyGlobalOptionService(workspace.Services);
            var optionKey = new OptionKey2(option, language);
 
            optionService.SetOptions([new KeyValuePair<OptionKey2, object?>(optionKey, newValue)], []);
            Assert.Equal(newValue, optionService.GlobalOptions.GetOption<CodeStyleOption2<bool>>(optionKey));
        }
    }
}