File: SettingsProvider\SettingsProviderExtensionManager.cs
Web Access
Project: src\src\vstest\src\Microsoft.TestPlatform.Common\Microsoft.TestPlatform.Common.csproj (Microsoft.VisualStudio.TestPlatform.Common)
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using System;
using System.Collections.Generic;
using System.Globalization;

using Microsoft.VisualStudio.TestPlatform.Common.ExtensionFramework;
using Microsoft.VisualStudio.TestPlatform.Common.ExtensionFramework.Utilities;
using Microsoft.VisualStudio.TestPlatform.Common.Interfaces;
using Microsoft.VisualStudio.TestPlatform.Common.Logging;
using Microsoft.VisualStudio.TestPlatform.ObjectModel;
using Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter;
using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging;

using CommonResources = Microsoft.VisualStudio.TestPlatform.Common.Resources.Resources;
using ObjectModelCommonResources = Microsoft.VisualStudio.TestPlatform.ObjectModel.Resources.CommonResources;

namespace Microsoft.VisualStudio.TestPlatform.Common.SettingsProvider;

/// <summary>
/// Manages the settings provider extensions.
/// </summary>
/// <remarks>
/// This is a non-shared instance because we want different settings provider instances to
/// be used for each run settings instance.
/// </remarks>
public class SettingsProviderExtensionManager
{
    private static SettingsProviderExtensionManager? s_settingsProviderExtensionManager;
    private static readonly object Synclock = new();

    /// <summary>
    /// The settings providers which are available.
    /// </summary>
    private readonly IEnumerable<LazyExtension<ISettingsProvider, ISettingsProviderCapabilities>> _settingsProviders;

    /// <summary>
    /// Used for logging errors.
    /// </summary>
    private readonly IMessageLogger _logger;

    /// <summary>
    /// Initializes with the settings providers.
    /// </summary>
    /// <remarks>
    /// The settings providers are imported as non-shared because we need different settings provider
    /// instances to be used for each run settings.
    /// </remarks>
    protected SettingsProviderExtensionManager(
        IEnumerable<LazyExtension<ISettingsProvider, ISettingsProviderCapabilities>> settingsProviders,
        IEnumerable<LazyExtension<ISettingsProvider, Dictionary<string, object>>> unfilteredSettingsProviders,
        IMessageLogger logger)
    {
        _settingsProviders = settingsProviders ?? throw new ArgumentNullException(nameof(settingsProviders));
        UnfilteredSettingsProviders = unfilteredSettingsProviders ?? throw new ArgumentNullException(nameof(unfilteredSettingsProviders));
        _logger = logger ?? throw new ArgumentNullException(nameof(logger));

        // Populate the map to avoid threading issues
        SettingsProvidersMap = new Dictionary<string, LazyExtension<ISettingsProvider, ISettingsProviderCapabilities>>();

        foreach (var settingsProvider in _settingsProviders)
        {
            var settingsName = settingsProvider.Metadata?.SettingsName;
            if (settingsName is null)
            {
                continue;
            }

            if (SettingsProvidersMap.ContainsKey(settingsName))
            {
                _logger.SendMessage(
                    TestMessageLevel.Error,
                    string.Format(
                        CultureInfo.CurrentCulture,
                        CommonResources.DuplicateSettingsName,
                        settingsName));
            }
            else
            {
                SettingsProvidersMap.Add(settingsName, settingsProvider);
            }
        }
    }

    /// <summary>
    /// Gets the Unfiltered list of settings providers.  Used for the /ListSettingsProviders command line argument.
    /// </summary>
    public IEnumerable<LazyExtension<ISettingsProvider, Dictionary<string, object>>> UnfilteredSettingsProviders { get; }

    /// <summary>
    /// Gets the map of settings name to settings provider.
    /// </summary>
    public Dictionary<string, LazyExtension<ISettingsProvider, ISettingsProviderCapabilities>> SettingsProvidersMap { get; }

    /// <summary>
    /// Creates an instance of the settings provider.
    /// </summary>
    /// <returns>Instance of the settings provider.</returns>
    public static SettingsProviderExtensionManager Create()
    {
        if (s_settingsProviderExtensionManager == null)
        {
            lock (Synclock)
            {
                if (s_settingsProviderExtensionManager == null)
                {

                    TestPluginManager.GetSpecificTestExtensions<TestSettingsProviderPluginInformation, ISettingsProvider, ISettingsProviderCapabilities, TestSettingsProviderMetadata>(
                            TestPlatformConstants.TestAdapterEndsWithPattern,
                            out var unfilteredTestExtensions,
                            out var testExtensions);

                    s_settingsProviderExtensionManager = new SettingsProviderExtensionManager(
                        testExtensions, unfilteredTestExtensions, TestSessionMessageLogger.Instance);
                }
            }
        }

        return s_settingsProviderExtensionManager;
    }

    /// <summary>
    /// Destroy the extension manager.
    /// </summary>
    public static void Destroy()
    {
        lock (Synclock)
        {
            s_settingsProviderExtensionManager = null;
        }
    }

    /// <summary>
    /// Load all the settings provider and fail on error
    /// </summary>
    /// <param name="shouldThrowOnError"> Indicates whether this method should throw on error. </param>
    public static void LoadAndInitializeAllExtensions(bool shouldThrowOnError)
    {
        var extensionManager = Create();

        try
        {
            foreach (var settingsProvider in extensionManager.SettingsProvidersMap)
            {
                // Note: - The below Verbose call should not be under IsVerboseEnabled check as we want to
                // call executor.Value even if logging is not enabled.
                EqtTrace.Verbose("SettingsProviderExtensionManager: Loading settings provider {0}", settingsProvider.Value.Value);
            }
        }
        catch (Exception ex)
        {
            EqtTrace.Error("SettingsProviderExtensionManager: LoadAndInitialize: Exception occurred while loading extensions {0}", ex);

            if (shouldThrowOnError)
            {
                throw;
            }
        }
    }

    /// <summary>
    /// Gets the settings with the provided name.
    /// </summary>
    /// <param name="settingsName">Name of the settings to get.</param>
    /// <returns>Settings provider with the provided name or null if one was not found.</returns>
    internal LazyExtension<ISettingsProvider, ISettingsProviderCapabilities>? GetSettingsProvider(string settingsName)
    {
        if (settingsName.IsNullOrWhiteSpace())
        {
            throw new ArgumentException(ObjectModelCommonResources.CannotBeNullOrEmpty, nameof(settingsName));
        }

        SettingsProvidersMap.TryGetValue(settingsName, out var settingsProvider);

        return settingsProvider;
    }

}

/// <summary>
/// Hold data about the Test settings provider.
/// </summary>
internal class TestSettingsProviderMetadata : ISettingsProviderCapabilities
{
    /// <summary>
    /// The constructor
    /// </summary>
    /// <param name="settingsName">Test settings name</param>
    public TestSettingsProviderMetadata(string settingsName)
    {
        SettingsName = settingsName;
    }

    /// <summary>
    /// Gets test settings name.
    /// </summary>
    public string SettingsName
    {
        get;
        private set;
    }
}