File: Execution\ExecutionManager.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 System.Linq;

using Microsoft.VisualStudio.TestPlatform.Common.ExtensionFramework;
using Microsoft.VisualStudio.TestPlatform.Common.Logging;
using Microsoft.VisualStudio.TestPlatform.Common.SettingsProvider;
using Microsoft.VisualStudio.TestPlatform.CommunicationUtilities;
using Microsoft.VisualStudio.TestPlatform.CoreUtilities.Tracing;
using Microsoft.VisualStudio.TestPlatform.CoreUtilities.Tracing.Interfaces;
using Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.DataCollection;
using Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.DataCollection.Interfaces;
using Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Utilities;
using Microsoft.VisualStudio.TestPlatform.ObjectModel;
using Microsoft.VisualStudio.TestPlatform.ObjectModel.Client;
using Microsoft.VisualStudio.TestPlatform.ObjectModel.Engine;
using Microsoft.VisualStudio.TestPlatform.ObjectModel.Engine.ClientProtocol;
using Microsoft.VisualStudio.TestPlatform.ObjectModel.Engine.TesthostProtocol;
using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging;
using Microsoft.VisualStudio.TestPlatform.ObjectModel.Utilities;

namespace Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Execution;

/// <summary>
/// Orchestrates test execution related functionality for the engine communicating with the test host process.
/// </summary>
public class ExecutionManager : IExecutionManager
{
    private readonly ITestPlatformEventSource _testPlatformEventSource;
    private readonly IRequestData _requestData;
    private readonly TestSessionMessageLogger? _sessionMessageLogger;
    private BaseRunTests? _activeTestRun;
    private ITestMessageEventHandler? _testMessageEventsHandler;

    /// <summary>
    /// Initializes a new instance of the <see cref="ExecutionManager"/> class.
    /// </summary>
    public ExecutionManager(IRequestData requestData)
        : this(TestPlatformEventSource.Instance, requestData)
    {
        _sessionMessageLogger = TestSessionMessageLogger.Instance;
        _sessionMessageLogger.TestRunMessage += TestSessionMessageHandler;
    }

    /// <summary>
    /// Initializes a new instance of the <see cref="ExecutionManager"/> class.
    /// </summary>
    /// <param name="testPlatformEventSource">Test platform event source.</param>
    /// <param name="requestData">Request data</param>
    protected ExecutionManager(ITestPlatformEventSource testPlatformEventSource, IRequestData requestData)
    {
        _testPlatformEventSource = testPlatformEventSource;
        _requestData = requestData ?? throw new ArgumentNullException(nameof(requestData));
    }

    #region IExecutionManager Implementation

    /// <summary>
    /// Initializes the execution manager.
    /// </summary>
    /// <param name="pathToAdditionalExtensions"> The path to additional extensions. </param>
    /// <param name="testMessageEventsHandler">Handler of test messages</param>
    public void Initialize(IEnumerable<string>? pathToAdditionalExtensions, ITestMessageEventHandler? testMessageEventsHandler)
    {
        // Clear the request data metrics left over from a potential previous run.
        _requestData.MetricsCollection?.Metrics?.Clear();

        _testMessageEventsHandler = testMessageEventsHandler;
        _testPlatformEventSource.AdapterSearchStart();

        if (pathToAdditionalExtensions != null && pathToAdditionalExtensions.Any())
        {
            // Start using these additional extensions
            TestPluginCache.Instance.DefaultExtensionPaths = pathToAdditionalExtensions;
        }

        LoadExtensions();

        //unsubscribe session logger
        if (_sessionMessageLogger is not null)
        {
            _sessionMessageLogger.TestRunMessage -= TestSessionMessageHandler;
        }

        _testPlatformEventSource.AdapterSearchStop();
    }

    /// <summary>
    /// Starts the test run
    /// </summary>
    /// <param name="adapterSourceMap"> The adapter Source Map.  </param>
    /// <param name="package">The user input test source(package) if it differ from actual test source otherwise null.</param>
    /// <param name="runSettings"> The run Settings.  </param>
    /// <param name="testExecutionContext"> The test Execution Context. </param>
    /// <param name="testCaseEventsHandler"> EventHandler for handling test cases level events from Engine. </param>
    /// <param name="runEventsHandler"> EventHandler for handling execution events from Engine.  </param>
    public void StartTestRun(
        Dictionary<string, IEnumerable<string>> adapterSourceMap,
        string? package,
        string? runSettings,
        TestExecutionContext testExecutionContext,
        ITestCaseEventsHandler? testCaseEventsHandler,
        IInternalTestRunEventsHandler runEventsHandler)
    {
        try
        {
            InitializeDataCollectors(runSettings, testCaseEventsHandler as ITestEventsPublisher, TestSourcesUtility.GetDefaultCodebasePath(adapterSourceMap!));

            _activeTestRun = new RunTestsWithSources(_requestData, adapterSourceMap, package, runSettings, testExecutionContext, testCaseEventsHandler, runEventsHandler);
            _activeTestRun.RunTests();
        }
        catch (Exception e)
        {
            runEventsHandler.HandleLogMessage(TestMessageLevel.Error, e.ToString());
            Abort(runEventsHandler);
        }
        finally
        {
            _activeTestRun = null;
        }
    }

    /// <summary>
    /// Starts the test run with tests.
    /// </summary>
    /// <param name="tests"> The test list. </param>
    /// <param name="package">The user input test source(package) if it differ from actual test source otherwise null.</param>
    /// <param name="runSettings"> The run Settings.  </param>
    /// <param name="testExecutionContext"> The test Execution Context. </param>
    /// <param name="testCaseEventsHandler"> EventHandler for handling test cases level events from Engine. </param>
    /// <param name="runEventsHandler"> EventHandler for handling execution events from Engine. </param>
    public void StartTestRun(
        IEnumerable<TestCase> tests,
        string? package,
        string? runSettings,
        TestExecutionContext testExecutionContext,
        ITestCaseEventsHandler? testCaseEventsHandler,
        IInternalTestRunEventsHandler runEventsHandler)
    {
        try
        {
            InitializeDataCollectors(runSettings, testCaseEventsHandler as ITestEventsPublisher, TestSourcesUtility.GetDefaultCodebasePath(tests));

            _activeTestRun = new RunTestsWithTests(_requestData, tests, package, runSettings, testExecutionContext, testCaseEventsHandler, runEventsHandler);
            _activeTestRun.RunTests();
        }
        catch (Exception e)
        {
            runEventsHandler.HandleLogMessage(TestMessageLevel.Error, e.ToString());
            Abort(runEventsHandler);
        }
        finally
        {
            _activeTestRun = null;
        }
    }

    /// <summary>
    /// Cancel the test execution.
    /// </summary>
    public void Cancel(IInternalTestRunEventsHandler testRunEventsHandler)
    {
        if (_activeTestRun == null)
        {
            var testRunCompleteEventArgs = new TestRunCompleteEventArgs(null, true, false, null, null, null, TimeSpan.Zero);
            testRunEventsHandler.HandleTestRunComplete(testRunCompleteEventArgs, null, null, null);
        }
        else
        {
            _activeTestRun.Cancel();
        }
    }

    /// <summary>
    /// Aborts the test execution.
    /// </summary>
    public void Abort(IInternalTestRunEventsHandler testRunEventsHandler)
    {
        if (_activeTestRun == null)
        {
            var testRunCompleteEventArgs = new TestRunCompleteEventArgs(null, false, true, null, null, null, TimeSpan.Zero);
            testRunEventsHandler.HandleTestRunComplete(testRunCompleteEventArgs, null, null, null);
        }
        else
        {
            _activeTestRun.Abort();
        }
    }

    #endregion
    private static void LoadExtensions()
    {
        try
        {
            // Load the extensions on creation so that we dont have to spend time during first execution.
            EqtTrace.Verbose("TestExecutorService: Loading the extensions");

            TestExecutorExtensionManager.LoadAndInitializeAllExtensions(false);

            EqtTrace.Verbose("TestExecutorService: Loaded the executors");

            SettingsProviderExtensionManager.LoadAndInitializeAllExtensions(false);

            EqtTrace.Verbose("TestExecutorService: Loaded the settings providers");
            EqtTrace.Info("TestExecutorService: Loaded the extensions");
        }
        catch (Exception ex)
        {
            EqtTrace.Warning("TestExecutorWebService: Exception occurred while calling test connection. {0}", ex);
        }
    }

    /// <summary>
    /// Initializes out-proc and in-proc data collectors.
    /// </summary>
    private static void InitializeDataCollectors(string? runSettings, ITestEventsPublisher? testEventsPublisher, string? defaultCodeBase)
    {
        // Initialize out-proc data collectors if declared in run settings.
        if (DataCollectionTestCaseEventSender.Instance != null && XmlRunSettingsUtilities.IsDataCollectionEnabled(runSettings))
        {
            TPDebug.Assert(testEventsPublisher is not null, "testEventsPublisher is null");
            _ = new ProxyOutOfProcDataCollectionManager(DataCollectionTestCaseEventSender.Instance, testEventsPublisher);
        }

        // Initialize in-proc data collectors if declared in run settings.
        if (XmlRunSettingsUtilities.IsInProcDataCollectionEnabled(runSettings))
        {
            TPDebug.Assert(testEventsPublisher is not null, "testEventsPublisher is null");
            _ = new InProcDataCollectionExtensionManager(runSettings, testEventsPublisher, defaultCodeBase, TestPluginCache.Instance);
        }
    }

    private void TestSessionMessageHandler(object? sender, TestRunMessageEventArgs e)
    {
        if (_testMessageEventsHandler != null)
        {
            _testMessageEventsHandler.HandleLogMessage(e.Level, e.Message);
        }
        else
        {
            EqtTrace.Warning(
                "ExecutionManager: Could not pass the log message  '{0}' as the callback is null.",
                e.Message);
        }
    }

}