File: Options\VisualStudioUnifiedSettingsOptionPersisterTests.cs
Web Access
Project: src\src\VisualStudio\Core\Test.Next\Roslyn.VisualStudio.Next.UnitTests.csproj (Roslyn.VisualStudio.Next.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.Threading.Tasks;
using Microsoft.CodeAnalysis.Formatting;
using Microsoft.CodeAnalysis.Options;
using Microsoft.VisualStudio.LanguageServices.Options;
using Microsoft.VisualStudio.Utilities.UnifiedSettings;
using Xunit;
 
namespace Microsoft.CodeAnalysis.UnitTests;
 
public class VisualStudioUnifiedSettingsOptionPersisterTests
{
    private sealed class MockUnifiedSettingsManager : ISettingsManager
    {
        public Func<string, Type, SettingRetrieval<object>>? GetValueImpl
        {
            set => _settingsReader.GetValueImpl = value;
        }
 
        public Action<string, object?>? SetValueImpl
        {
            set => _settingsWriter.SetValueImpl = value;
        }
 
        private readonly MockSettingsReader _settingsReader = new();
        private readonly MockSettingsWriter _settingsWriter = new();
 
        public ISettingsReader GetReader() => _settingsReader;
 
        public ISettingsWriter GetWriter(string callerName) => _settingsWriter;
 
        public ISettingsWriter GetWriter(string callerName, Guid eventSource) => _settingsWriter;
    }
 
    private sealed class MockSettingsReader : ISettingsReader
    {
        public Func<string, Type, SettingRetrieval<object>>? GetValueImpl;
 
        public SettingRetrieval<IReadOnlyList<T>> GetArray<T>(string moniker, SettingReadOptions readOptions = SettingReadOptions.RequireValidation) where T : notnull
        {
            throw new NotImplementedException();
        }
 
        public IReadOnlyList<T> GetArrayOrThrow<T>(string moniker) where T : notnull
        {
            throw new NotImplementedException();
        }
 
        public SettingRetrieval<T> GetValue<T>(string moniker, SettingReadOptions readOptions = SettingReadOptions.RequireValidation) where T : notnull
        {
            var retrieval = GetValueImpl!(moniker, null!);
            return new SettingRetrieval<T>(retrieval.Outcome, retrieval.Message, retrieval.Value is T ? (T)retrieval.Value : default);
        }
 
        public SettingRetrieval<object> GetValue(string moniker, Type targetType, SettingReadOptions readOptions = SettingReadOptions.RequireValidation)
        {
            throw new NotImplementedException();
        }
 
        public T GetValueOrThrow<T>(string moniker) where T : notnull
        {
            throw new NotImplementedException();
        }
 
        public object GetValueOrThrow(string moniker, Type targetType)
        {
            throw new NotImplementedException();
        }
 
        public IDisposable SubscribeToChanges(Action<SettingsUpdate> handler, params string[] monikerPatterns) => null!;
    }
 
    private sealed class MockSettingsWriter : ISettingsWriter
    {
        internal Action<string, object?>? SetValueImpl;
 
        public SettingCommitResult Commit(string changeDescription)
        {
            throw new NotImplementedException();
        }
 
        public SettingChangeResult EnqueueArrayChange<T>(string moniker, IReadOnlyList<T> value) where T : notnull
        {
            throw new NotImplementedException();
        }
 
        public SettingChangeResult EnqueueChange<T>(string moniker, T value) where T : notnull
        {
            SetValueImpl!(moniker, value);
            return new SettingChangeResult(SettingChangeOutcome.PendingCommit, true, null);
        }
 
        public SettingChangeResult EnqueueChange<T>(string moniker, T value, SettingWriteOptions options) where T : notnull
        {
            throw new NotImplementedException();
        }
 
        public SettingRetrieval<IReadOnlyList<T>> GetArray<T>(string moniker, SettingReadOptions readOptions = SettingReadOptions.RequireValidation) where T : notnull
        {
            throw new NotImplementedException();
        }
 
        public IReadOnlyList<T> GetArrayOrThrow<T>(string moniker) where T : notnull
        {
            throw new NotImplementedException();
        }
 
        public SettingRetrieval<T> GetValue<T>(string moniker, SettingReadOptions readOptions = SettingReadOptions.RequireValidation) where T : notnull
        {
            throw new NotImplementedException();
        }
 
        public SettingRetrieval<object> GetValue(string moniker, Type targetType, SettingReadOptions readOptions = SettingReadOptions.RequireValidation)
        {
            throw new NotImplementedException();
        }
 
        public T GetValueOrThrow<T>(string moniker) where T : notnull
        {
            throw new NotImplementedException();
        }
 
        public object GetValueOrThrow(string moniker, Type targetType)
        {
            throw new NotImplementedException();
        }
 
        public SettingCommitResult RequestCommit(string changeDescription)
        {
            return new SettingCommitResult(SettingCommitOutcome.Success, null);
        }
 
        public IDisposable SubscribeToChanges(Action<SettingsUpdate> handler, params string[] monikerPatterns)
        {
            throw new NotImplementedException();
        }
    }
 
    [Theory]
    [InlineData("indent_size", 2, "2")]
    [InlineData("indent_size", 4, "4")]
    [InlineData("indent_style", true, "tab")]
    [InlineData("indent_style", false, "space")]
    [InlineData("tab_width", 2, "2")]
    [InlineData("tab_width", 4, "4")]
    [InlineData("smart_indent", FormattingOptions2.IndentStyle.None, "none")]
    [InlineData("smart_indent", FormattingOptions2.IndentStyle.Block, "block")]
    [InlineData("smart_indent", FormattingOptions2.IndentStyle.Smart, "smart")]
    public async Task ValidateSettingManagerSettings(
        string key,
        object value,
        string finalValue)
    {
        var mockManager = new MockUnifiedSettingsManager();
 
        var persister = new VisualStudioUnifiedSettingsOptionPersister(
            (_, _) => { },
            mockManager);
 
        // read
        var manager = (VisualStudioOptionStorage.UnifiedSettingsManagerStorage)VisualStudioOptionStorage.Storages[key];
        IPerLanguageValuedOption option = key switch
        {
            "indent_size" => FormattingOptions2.IndentationSize,
            "indent_style" => FormattingOptions2.UseTabs,
            "tab_width" => FormattingOptions2.TabSize,
            "smart_indent" => FormattingOptions2.SmartIndent,
            _ => throw ExceptionUtilities.UnexpectedValue(key),
        };
 
        var optionKey = new OptionKey2(option, LanguageNames.CSharp);
 
        // Ensure that if the settings manager doesn't find the item, we are resilient.
        mockManager.GetValueImpl = (_, _) => new SettingRetrieval<object>(SettingRetrievalOutcome.NotRegistered, null, null);
        Assert.False(manager.TryFetch(persister, optionKey, out _));
 
        // Ensure that if the settings manager finds and return nothing, we are resilient.
        mockManager.GetValueImpl = (_, _) => new SettingRetrieval<object>(SettingRetrievalOutcome.Success, null, null);
        Assert.False(manager.TryFetch(persister, optionKey, out _));
 
        // Ensure that if the settings manager finds and return an unexpected type, we are resilient.
        mockManager.GetValueImpl = (_, _) => new SettingRetrieval<object>(SettingRetrievalOutcome.Success, null, true);
        Assert.False(manager.TryFetch(persister, optionKey, out _));
 
        // Ensure that We don't try to get when doing a store.  Also ensure that the value stored is the appropriate
        // string value of the original value passed in.
        mockManager.GetValueImpl = null;
        mockManager.SetValueImpl = (name, storedValue) =>
        {
            Assert.Equal(finalValue, storedValue);
        };
        await manager.PersistAsync(persister, optionKey, value);
 
        // Attempt to read the value back.  If the settings manager gets it back properly, ensure that we can read it
        // into the actual in-memory (non-string) value we expect.
        mockManager.SetValueImpl = null;
        mockManager.GetValueImpl = (name, type) => new SettingRetrieval<object>(SettingRetrievalOutcome.Success, null, finalValue);
        Assert.True(manager.TryFetch(persister, optionKey, out var fetchedValue));
        Assert.Equal(value, fetchedValue);
    }
}