File: Execution\RunTestsWithTests.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;
using System.Collections.Generic;

using Microsoft.VisualStudio.TestPlatform.Common.ExtensionFramework.Utilities;
using Microsoft.VisualStudio.TestPlatform.Common.Interfaces;
using Microsoft.VisualStudio.TestPlatform.CoreUtilities.Tracing;
using Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Adapter;
using Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Utilities;
using Microsoft.VisualStudio.TestPlatform.ObjectModel;
using Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter;
using Microsoft.VisualStudio.TestPlatform.ObjectModel.Client;
using Microsoft.VisualStudio.TestPlatform.ObjectModel.Engine;
using Microsoft.VisualStudio.TestPlatform.ObjectModel.Engine.ClientProtocol;

namespace Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Execution;

internal class RunTestsWithTests : BaseRunTests
{
    private readonly IEnumerable<TestCase> _testCases;
    private readonly ITestCaseEventsHandler? _testCaseEventsHandler;

    private Dictionary<Tuple<Uri, string>, List<TestCase>>? _executorUriVsTestList;

    public RunTestsWithTests(IRequestData requestData, IEnumerable<TestCase> testCases, string? package, string? runSettings, TestExecutionContext testExecutionContext, ITestCaseEventsHandler? testCaseEventsHandler, IInternalTestRunEventsHandler testRunEventsHandler)
        : this(requestData, testCases, package, runSettings, testExecutionContext, testCaseEventsHandler, testRunEventsHandler, null)
    {
    }

    /// <summary>
    /// Used for unit testing only.
    /// </summary>
    /// <param name="testCases"></param>
    /// <param name="package">The user input test source(package) if it differ from actual test source otherwise null.</param>
    /// <param name="requestData"></param>
    /// <param name="runSettings"></param>
    /// <param name="testExecutionContext"></param>
    /// <param name="testCaseEventsHandler"></param>
    /// <param name="testRunEventsHandler"></param>
    /// <param name="executorUriVsTestList"></param>
    internal RunTestsWithTests(IRequestData requestData, IEnumerable<TestCase> testCases, string? package, string? runSettings, TestExecutionContext testExecutionContext, ITestCaseEventsHandler? testCaseEventsHandler, IInternalTestRunEventsHandler testRunEventsHandler, Dictionary<Tuple<Uri, string>, List<TestCase>>? executorUriVsTestList)
        : base(requestData, package, runSettings, testExecutionContext, testCaseEventsHandler, testRunEventsHandler, TestPlatformEventSource.Instance)
    {
        _testCases = testCases;
        _executorUriVsTestList = executorUriVsTestList;
        _testCaseEventsHandler = testCaseEventsHandler;
    }

    protected override void BeforeRaisingTestRunComplete(bool exceptionsHitDuringRunTests)
    {
        // Do Nothing.
    }

    protected override IEnumerable<Tuple<Uri, string>> GetExecutorUriExtensionMap(IFrameworkHandle testExecutorFrameworkHandle, RunContext runContext)
    {
        _executorUriVsTestList = GetExecutorVsTestCaseList(_testCases);

        TPDebug.Assert(TestExecutionContext.TestCaseFilter == null, "TestCaseFilter should be null for specific tests.");
        runContext.FilterExpressionWrapper = null;

        return _executorUriVsTestList.Keys;
    }

    protected override void InvokeExecutor(
        LazyExtension<ITestExecutor, ITestExecutorCapabilities> executor,
        Tuple<Uri, string> executorUri,
        RunContext? runContext,
        IFrameworkHandle? frameworkHandle)
    {
        executor?.Value.RunTests(_executorUriVsTestList?[executorUri], runContext, frameworkHandle);
    }

    /// <inheritdoc />
    protected override bool ShouldAttachDebuggerToTestHost(
        LazyExtension<ITestExecutor, ITestExecutorCapabilities> executor,
        Tuple<Uri, string> executorUri,
        RunContext runContext)
    {
        // If the adapter doesn't implement the new test executor interface we should attach to
        // the default test host by default to preserve old behavior.
        return executor?.Value is not ITestExecutor2 convertedExecutor
               || convertedExecutor.ShouldAttachToTestHost(_executorUriVsTestList?[executorUri], runContext);
    }

    /// <summary>
    /// Sends Session-End event on in-proc datacollectors
    /// </summary>
    protected override void SendSessionEnd()
    {
        _testCaseEventsHandler?.SendSessionEnd();
    }

    /// <summary>
    /// Sends Session-Start event on in-proc datacollectors
    /// </summary>
    protected override void SendSessionStart()
    {
        // Send session start with test sources in property bag for session start event args.
        if (_testCaseEventsHandler == null)
        {
            return;
        }

        var properties = new Dictionary<string, object?>
        {
            { "TestSources", TestSourcesUtility.GetSources(_testCases)! }
        };

        _testCaseEventsHandler.SendSessionStart(properties);
    }

    /// <summary>
    /// Returns the executor Vs TestCase list
    /// </summary>
    private static Dictionary<Tuple<Uri, string>, List<TestCase>> GetExecutorVsTestCaseList(IEnumerable<TestCase> tests)
    {
        var result = new Dictionary<Tuple<Uri, string>, List<TestCase>>();
        foreach (var test in tests)
        {

            // TODO: Fill this in with the right extension value.
            var executorUriExtensionTuple = new Tuple<Uri, string>(
                test.ExecutorUri,
                ObjectModel.Constants.UnspecifiedAdapterPath);

            if (result.TryGetValue(executorUriExtensionTuple, out List<TestCase>? testList))
            {
                testList.Add(test);
            }
            else
            {
                testList = [test];
                result.Add(executorUriExtensionTuple, testList);
            }
        }

        return result;
    }
}