File: Hosting\TestRunTimeProviderManager.cs
Web Access
Project: 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 Microsoft.VisualStudio.TestPlatform.Common.Logging;
using Microsoft.VisualStudio.TestPlatform.ObjectModel.Host;
using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging;

namespace Microsoft.VisualStudio.TestPlatform.Common.Hosting;

/// <summary>
/// Responsible for managing TestRuntimeProviderManager extensions
/// </summary>
public class TestRuntimeProviderManager : ITestRuntimeProviderManager
{
    private static TestRuntimeProviderManager? s_testHostManager;

    private readonly TestRuntimeExtensionManager _testHostExtensionManager;

    /// <summary>
    /// Initializes a new instance of the <see cref="TestRuntimeProviderManager"/> class.
    /// Default constructor.
    /// </summary>
    /// <param name="sessionLogger">
    /// The session Logger.
    /// </param>
    protected TestRuntimeProviderManager(IMessageLogger sessionLogger)
    {
        _testHostExtensionManager = TestRuntimeExtensionManager.Create(sessionLogger);
    }

    /// <summary>
    /// Gets the instance of TestRuntimeProviderManager
    /// </summary>
    public static TestRuntimeProviderManager Instance
        => s_testHostManager ??= new TestRuntimeProviderManager(TestSessionMessageLogger.Instance);

    public ITestRuntimeProvider? GetTestHostManagerByUri(string hostUri)
    {
        var host = _testHostExtensionManager.TryGetTestExtension(hostUri);
        return host?.Value;
    }

    Type? ITestRuntimeProviderManager.GetSourceAwareRuntimeProviderType(string? runConfiguration, string source)
    {
        // Consult only the source-aware providers (the first-refusal pass), mirroring the first loop of
        // GetTestHostManagerByRunConfiguration, but for a single source and without instantiating anything.
        // Callers use the returned type purely as a grouping discriminator, so a source claimed by a
        // source-aware provider is scheduled separately from a generic (source-blind) source.
        var sources = new List<string> { source };
        foreach (var testExtension in _testHostExtensionManager.TestExtensions)
        {
            if (testExtension.Value is ISourceAwareTestRuntimeProvider sourceAware
                && sourceAware.CanExecuteCurrentRunConfiguration(runConfiguration, sources))
            {
                return testExtension.Value.GetType();
            }
        }

        return null;
    }

    public virtual ITestRuntimeProvider? GetTestHostManagerByRunConfiguration(string? runConfiguration, List<string>? sources)
    {
        // First pass: give source-aware providers first refusal. These providers (e.g. the
        // Microsoft.Testing.Platform provider) can inspect the actual sources to decide whether they own the
        // run, so they must be consulted before the generic, source-blind providers that match only by target
        // framework. This gives the more specific provider priority without any global ordering scheme, and
        // without relying on the generic providers to decline.
        if (sources is not null && sources.Count > 0)
        {
            foreach (var testExtension in _testHostExtensionManager.TestExtensions)
            {
                if (testExtension.Value is ISourceAwareTestRuntimeProvider sourceAware
                    && sourceAware.CanExecuteCurrentRunConfiguration(runConfiguration, sources))
                {
                    // We are creating a new instance of ITestRuntimeProvider so that each POM gets its own object of ITestRuntimeProvider.
                    return (ITestRuntimeProvider?)Activator.CreateInstance(testExtension.Value.GetType());
                }
            }
        }

        // Second pass: the legacy, source-blind resolution based purely on the run configuration.
        // Source-aware providers already had their (source-based) first refusal above, so exclude them here.
        // Re-consulting them would be redundant, and — more importantly — a provider that declined by source
        // must not be re-admitted by matching only the target framework. Enforcing the exclusion in the manager
        // keeps the "first refusal" contract here, instead of relying on every source-aware provider to remember
        // to return false when asked the source-blind question.
        foreach (var testExtension in _testHostExtensionManager.TestExtensions)
        {
            if (testExtension.Value is ISourceAwareTestRuntimeProvider)
            {
                continue;
            }

            if (testExtension.Value.CanExecuteCurrentRunConfiguration(runConfiguration))
            {
                // we are creating a new Instance of ITestRuntimeProvider so that each POM gets it's own object of ITestRuntimeProvider
                return (ITestRuntimeProvider?)Activator.CreateInstance(testExtension.Value.GetType());
            }
        }

        return null;
    }

}