File: Client\TestRunCriteriaExtensions.cs
Web Access
Project: src\src\vstest\src\Microsoft.TestPlatform.CrossPlatEngine\Microsoft.TestPlatform.CrossPlatEngine.csproj (Microsoft.TestPlatform.CrossPlatEngine)
// 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.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;

using Microsoft.VisualStudio.TestPlatform.CommunicationUtilities.ObjectModel;
using Microsoft.VisualStudio.TestPlatform.ObjectModel.Client;
using Microsoft.VisualStudio.TestPlatform.ObjectModel.Engine.ClientProtocol;
using Microsoft.VisualStudio.TestPlatform.ObjectModel.Host;

namespace Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Client;

internal static class TestRunCriteriaExtensions
{
    public static TestRunCriteriaWithSources CreateTestRunCriteriaForSources(this TestRunCriteria testRunCriteria, ITestRuntimeProvider? testRuntimeProvider, string? runSettings, TestExecutionContext executionContext, IEnumerable<string>? inputPackages)
    {
        TPDebug.Assert(testRunCriteria.AdapterSourceMap is not null, "testRunCriteria.AdapterSourceMap is null");
        if (TryCheckTestSourceDifferFromPackage(testRuntimeProvider, inputPackages, out IEnumerable<string>? actualTestSources))
        {
            UpdateTestSources(actualTestSources, testRunCriteria.AdapterSourceMap);
        }
        else
        {
            inputPackages = null;
        }

        return new TestRunCriteriaWithSources(testRunCriteria.AdapterSourceMap, inputPackages?.FirstOrDefault(), runSettings, executionContext);
    }

    public static TestRunCriteriaWithTests CreateTestRunCriteriaForTests(this TestRunCriteria testRunCriteria, ITestRuntimeProvider? testRuntimeProvider,
        string? runSettings, TestExecutionContext executionContext, IEnumerable<string>? inputPackages)
    {
        TPDebug.Assert(testRunCriteria.Tests is not null, "testRunCriteria.Tests is null");
        if (TryCheckTestSourceDifferFromPackage(testRuntimeProvider, inputPackages, out IEnumerable<string>? actualTestSources))
        {
            // In UWP scenario TestCase object contains the package as source, which is not actual test source for adapters,
            // so update test case before sending them.
            // We are limiting that a testhost will always run for a single package, A package can contain multiple sources
            foreach (var tc in testRunCriteria.Tests)
            {
                tc.Source = actualTestSources.FirstOrDefault()!;
            }
        }
        else
        {
            inputPackages = null;
        }

        return new TestRunCriteriaWithTests(testRunCriteria.Tests, inputPackages?.FirstOrDefault(), runSettings, executionContext);
    }

    private static bool TryCheckTestSourceDifferFromPackage(ITestRuntimeProvider? testRuntimeProvider,
        IEnumerable<string>? inputPackages, [NotNullWhen(true)] out IEnumerable<string>? actualTestSources)
    {
        actualTestSources = testRuntimeProvider?.GetTestSources(inputPackages!);

        if (inputPackages is null || actualTestSources is null)
        {
            return false;
        }

        // For netcore/fullclr both packages and sources are same thing,
        // For UWP the actual source(exe) differs from input source(.appxrecipe) which we call package.
        // So in such models we check if they differ, then we pass this info to test host to update TestCase source with package info,
        // since this is needed by IDE's to map a TestCase to project.
        return inputPackages.Except(actualTestSources).Any();
    }

    /// <summary>
    /// Update the AdapterSourceMap
    /// </summary>
    /// <param name="sources">actual test sources</param>
    /// <param name="adapterSourceMap">Adapter Source Map</param>
    private static void UpdateTestSources(IEnumerable<string> sources, Dictionary<string, IEnumerable<string>> adapterSourceMap)
    {
        adapterSourceMap.Clear();
        adapterSourceMap.Add(ObjectModel.Constants.UnspecifiedAdapterPath, sources);
    }
}