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

extern alias Abstraction;

using System;
using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;

using Abstraction::Microsoft.VisualStudio.TestPlatform.PlatformAbstractions;

using Microsoft.TestPlatform.VsTestConsole.TranslationLayer;
using Microsoft.TestPlatform.VsTestConsole.TranslationLayer.Interfaces;
using Microsoft.VisualStudio.TestPlatform.Client;
using Microsoft.VisualStudio.TestPlatform.Client.DesignMode;
using Microsoft.VisualStudio.TestPlatform.Client.RequestHelper;
using Microsoft.VisualStudio.TestPlatform.CoreUtilities.Helpers;
using Microsoft.VisualStudio.TestPlatform.CoreUtilities.Tracing;
using Microsoft.VisualStudio.TestPlatform.CoreUtilities.Tracing.Interfaces;
using Microsoft.VisualStudio.TestPlatform.Execution;
using Microsoft.VisualStudio.TestPlatform.ObjectModel;
using Microsoft.VisualStudio.TestPlatform.ObjectModel.Client;
using Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.Interfaces;
using Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.Payloads;
using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging;
using Microsoft.VisualStudio.TestPlatform.Utilities;
using Microsoft.VisualStudio.TestPlatform.Utilities.Helpers.Interfaces;
using Microsoft.VisualStudio.TestPlatform.VsTestConsole.TranslationLayer.Interfaces;

namespace Microsoft.VisualStudio.TestPlatform.CommandLine;

/// <summary>
/// The in-process wrapper.
/// </summary>
internal class InProcessVsTestConsoleWrapper : IVsTestConsoleWrapper
{
    // Must be in sync with the highest supported version in
    // src/Microsoft.TestPlatform.CrossPlatEngine/EventHandlers/TestRequestHandler.cs file.
    private readonly int _highestSupportedVersion = 6;

    private readonly ITranslationLayerRequestSender _requestSender;
    private readonly ITestPlatformEventSource _testPlatformEventSource;
    private readonly IEnvironmentVariableHelper _environmentVariableHelper;

    /// <summary>
    /// Creates a new instance of <see cref="InProcessVsTestConsoleWrapper"/>.
    /// </summary>
    /// 
    /// <param name="consoleParameters">The console parameters.</param>
    public InProcessVsTestConsoleWrapper(ConsoleParameters consoleParameters)
        : this(
              consoleParameters,
              environmentVariableHelper: new EnvironmentVariableHelper(),
              requestSender: new VsTestConsoleRequestSender(),
              testRequestManager: null,
              executor: new Executor(ConsoleOutput.Instance),
              testPlatformEventSource: TestPlatformEventSource.Instance,
              new())
    { }

    internal InProcessVsTestConsoleWrapper(
        ConsoleParameters consoleParameters,
        IEnvironmentVariableHelper environmentVariableHelper,
        ITranslationLayerRequestSender requestSender,
        ITestRequestManager? testRequestManager,
        Executor executor,
        ITestPlatformEventSource testPlatformEventSource,
        UiLanguageOverride languageOverride)
    {
        // Setting the culture specified by user here since there's no more vstest.console process
        // to set it for us. See vstest.console Main method for more info.
        languageOverride.SetCultureSpecifiedByUser();

        EqtTrace.Info("VsTestConsoleWrapper.StartSession: Starting VsTestConsoleWrapper session.");

        _environmentVariableHelper = environmentVariableHelper;
        _testPlatformEventSource = testPlatformEventSource;
        _testPlatformEventSource.TranslationLayerInitializeStart();

        // Start communication.
        _requestSender = requestSender;
        var port = _requestSender.InitializeCommunication();
        if (port <= 0)
        {
            // Close the sender as it failed to host server.
            _requestSender.Close();
            throw new TransationLayerException(Resources.Resources.ErrorHostingCommunicationChannel);
        }

        // Fill the parameters.
        consoleParameters.ParentProcessId =
#if NET
            Environment.ProcessId;
#else
            System.Diagnostics.Process.GetCurrentProcess().Id;
#endif
        consoleParameters.PortNumber = port;

        // Start vstest.console.
        // Running vstest.console in process means we inherit all environment variables from the
        // process we load the wrapper into. We do not want to alter this environment since that
        // would mean we may interfer with the way the host process works. However, certain
        // alterations are desired. The solution is to pass the environment variables we get via
        // the console parameters directly to the testhost process and make sure that at least the
        // testhost environment is predictable.
        IDictionary<string, string?> environmentVariableBaseline = new Dictionary<string, string?>();
        if (consoleParameters.InheritEnvironmentVariables)
        {
            // This is needed because GetEnvironmentVariables() returns a non-generic dictionary
            // and we need to convert it to a generic dictionary for our use-case.
            foreach (DictionaryEntry? entry in _environmentVariableHelper.GetEnvironmentVariables())
            {
                environmentVariableBaseline[entry?.Key.ToString()!] = entry?.Value?.ToString();
            }
        }

        foreach (var pair in consoleParameters.EnvironmentVariables)
        {
            environmentVariableBaseline[pair.Key] = pair.Value;
        }

        ProcessHelper.ExternalEnvironmentVariables = environmentVariableBaseline;

        string someExistingFile = typeof(InProcessVsTestConsoleWrapper).Assembly.Location;
        using var manager = new VsTestConsoleProcessManager(someExistingFile);
        var args = manager.BuildArguments(consoleParameters);
        // Skip vstest.console path, we are already running in process, so it would just end up
        // being understood as test dll to run. (it is present even though we don't provide
        // dotnet path, because it is a .dll file).
        args = args.Skip(1).ToArray();

        // We standup the client, and it will allocate port as normal that we will never use.
        // This is just to avoid "duplicating" all the setup logic that is done in argument
        // processors before client processor. It created design mode client, and stores it as
        // single instance.
        // We connect back to the client but never use that connection, it only serves as
        // "await" to make sure the design mode client is already initialized.
        Task.Run<int>(() => executor.Execute(args));
        WaitForConnection();

        // Set the test request manager here.
        TestRequestManager = testRequestManager;
        if (TestRequestManager is null)
        {
            TPDebug.Assert(
                DesignModeClient.Instance != null,
                "DesignModeClient.Instance is null");
            TestRequestManager = DesignModeClient.Instance.TestRequestManager;
        }

        _testPlatformEventSource.TranslationLayerInitializeStop();
    }

    internal ITestRequestManager? TestRequestManager { get; set; }

    /// <inheritdoc/>
    public void AbortTestRun()
    {
        try
        {
            TestRequestManager?.AbortTestRun();
        }
        catch (Exception ex)
        {
            EqtTrace.Error("InProcessVsTestConsoleWrapper.AbortTestRun: Exception occurred: " + ex);
        }
    }

    /// <inheritdoc/>
    public void CancelDiscovery()
    {
        try
        {
            TestRequestManager?.CancelDiscovery();
        }
        catch (Exception ex)
        {
            EqtTrace.Error("InProcessVsTestConsoleWrapper.CancelDiscovery: Exception occurred: " + ex);
        }
    }

    /// <inheritdoc/>
    public void CancelTestRun()
    {
        try
        {
            TestRequestManager?.CancelTestRun();
        }
        catch (Exception ex)
        {
            EqtTrace.Error("InProcessVsTestConsoleWrapper.CancelTestRun: Exception occurred: " + ex);
        }
    }

    /// <inheritdoc/>
    public void EndSession()
    {
        // Session means vstest.console process in the original api
        // we don't have a process to manage, we are in-process.
    }

    /// <inheritdoc/>
    public void StartSession()
    {
        // Session means vstest.console process in the original api
        // we don't have a process to manage, we are in-process.
    }

    /// <inheritdoc/>
    [Obsolete("This API is not final yet and is subject to changes.", false)]
    public ITestSession? StartTestSession(
        IList<string> sources,
        string? runSettings,
        ITestSessionEventsHandler eventsHandler)
    {
        return StartTestSession(sources, runSettings, options: null, eventsHandler);
    }

    /// <inheritdoc/>
    [Obsolete("This API is not final yet and is subject to changes.", false)]
    public ITestSession? StartTestSession(
        IList<string> sources,
        string? runSettings,
        TestPlatformOptions? options,
        ITestSessionEventsHandler eventsHandler)
    {
        return StartTestSession(
            sources,
            runSettings,
            options,
            eventsHandler,
            testHostLauncher: null);
    }

    /// <inheritdoc/>
    [Obsolete("This API is not final yet and is subject to changes.", false)]
    public ITestSession? StartTestSession(
        IList<string> sources,
        string? runSettings,
        TestPlatformOptions? options,
        ITestSessionEventsHandler eventsHandler,
        ITestHostLauncher? testHostLauncher)
    {
        _testPlatformEventSource.TranslationLayerStartTestSessionStart();

        var resetEvent = new ManualResetEvent(false);
        TestSessionInfo? testSessionInfo = null;

        try
        {
            TestRequestManager?.ResetOptions();
            var startTestSessionPayload = new StartTestSessionPayload()
            {
                Sources = sources,
                RunSettings = runSettings,
                HasCustomHostLauncher = testHostLauncher != null,
                IsDebuggingEnabled = testHostLauncher != null
                                     && testHostLauncher.IsDebug,
                TestPlatformOptions = options
            };

            var inProcessEventsHandler = new InProcessTestSessionEventsHandler(eventsHandler);
            inProcessEventsHandler.StartTestSessionCompleteEventHandler += (_, eventArgs) =>
            {
                testSessionInfo = eventArgs?.TestSessionInfo;
                resetEvent.Set();
            };

            TestRequestManager?.StartTestSession(
                startTestSessionPayload,
                (ITestHostLauncher3?)testHostLauncher,
                inProcessEventsHandler,
                new ProtocolConfig { Version = _highestSupportedVersion });

            var timeout = EnvironmentHelper.GetConnectionTimeout() * 1000;
            if (!resetEvent.WaitOne(timeout))
            {
                throw new TransationLayerException(
                    string.Format(
                        CultureInfo.CurrentCulture,
                        Resources.Resources.StartTestSessionTimedOut,
                        timeout));
            }

            inProcessEventsHandler.StartTestSessionCompleteEventHandler = null;
        }
        catch (Exception ex)
        {
            EqtTrace.Error("InProcessVsTestConsoleWrapper.StartTestSession: Exception occurred: " + ex);

            eventsHandler.HandleLogMessage(TestMessageLevel.Error, ex.ToString());
            eventsHandler.HandleStartTestSessionComplete(new());
        }

        _testPlatformEventSource.TranslationLayerStartTestSessionStop();

        return new TestSession(testSessionInfo, eventsHandler, this);
    }

    /// <inheritdoc/>
    [Obsolete("This API is not final yet and is subject to changes.", false)]
    public bool StopTestSession(
        TestSessionInfo? testSessionInfo,
        ITestSessionEventsHandler eventsHandler)
    {
        return StopTestSession(testSessionInfo, options: null, eventsHandler);
    }

    /// <inheritdoc/>
    [Obsolete("This API is not final yet and is subject to changes.", false)]
    public bool StopTestSession(
        TestSessionInfo? testSessionInfo,
        TestPlatformOptions? options,
        ITestSessionEventsHandler eventsHandler)
    {
        _testPlatformEventSource.TranslationLayerStopTestSessionStart();

        var isStopped = false;
        var resetEvent = new ManualResetEvent(false);

        try
        {
            TestRequestManager?.ResetOptions();
            var stopTestSessionPayload = new StopTestSessionPayload()
            {
                TestSessionInfo = testSessionInfo,
                CollectMetrics = options?.CollectMetrics ?? false
            };

            var inProcessEventsHandler = new InProcessTestSessionEventsHandler(eventsHandler);
            inProcessEventsHandler.StopTestSessionCompleteEventHandler += (_, eventArgs) =>
            {
                isStopped = (eventArgs?.IsStopped == true);
                resetEvent.Set();
            };

            TestRequestManager?.StopTestSession(
                stopTestSessionPayload,
                inProcessEventsHandler,
                new ProtocolConfig { Version = _highestSupportedVersion });

            var timeout = EnvironmentHelper.GetConnectionTimeout() * 1000;
            if (!resetEvent.WaitOne(timeout))
            {
                throw new TransationLayerException(
                    string.Format(
                        CultureInfo.CurrentCulture,
                        Resources.Resources.StopTestSessionTimedOut,
                        timeout));
            }

            inProcessEventsHandler.StopTestSessionCompleteEventHandler = null;
        }
        catch (Exception ex)
        {
            EqtTrace.Error("InProcessVsTestConsoleWrapper.StopTestSession: Exception occurred: " + ex);

            eventsHandler.HandleLogMessage(TestMessageLevel.Error, ex.ToString());
            eventsHandler.HandleStopTestSessionComplete(new());
        }

        _testPlatformEventSource.TranslationLayerStopTestSessionStop();

        return isStopped;
    }

    /// <inheritdoc/>
    public void InitializeExtensions(IEnumerable<string> pathToAdditionalExtensions)
    {
        try
        {
            TestRequestManager?.InitializeExtensions(
                pathToAdditionalExtensions,
                skipExtensionFilters: true);
        }
        catch (Exception ex)
        {
            EqtTrace.Error("InProcessVsTestConsoleWrapper.InitializeExtensions: Exception occurred: " + ex);
        }
    }

    /// <inheritdoc/>
    public void DiscoverTests(
        IEnumerable<string> sources,
        string? discoverySettings,
        ITestDiscoveryEventsHandler discoveryEventsHandler)
    {
        DiscoverTests(
            sources,
            discoverySettings,
            options: null,
            new DiscoveryEventsHandleConverter(discoveryEventsHandler));
    }

    /// <inheritdoc/>
    public void DiscoverTests(
        IEnumerable<string> sources,
        string? discoverySettings,
        TestPlatformOptions? options,
        ITestDiscoveryEventsHandler2 discoveryEventsHandler)
    {
        DiscoverTests(
            sources,
            discoverySettings,
            options,
            testSessionInfo: null,
            discoveryEventsHandler);
    }

    /// <inheritdoc/>
    public void DiscoverTests(
        IEnumerable<string> sources,
        string? discoverySettings,
        TestPlatformOptions? options,
        TestSessionInfo? testSessionInfo,
        ITestDiscoveryEventsHandler2 discoveryEventsHandler)
    {
        _testPlatformEventSource.TranslationLayerDiscoveryStart();

        try
        {
            TestRequestManager?.ResetOptions();
            var discoveryRequestPayload = new DiscoveryRequestPayload()
            {
                Sources = sources,
                RunSettings = discoverySettings,
                TestPlatformOptions = options,
                TestSessionInfo = testSessionInfo
            };

            TestRequestManager?.DiscoverTests(
                discoveryRequestPayload,
                new DiscoveryHandlerToEventsRegistrarAdapter(discoveryEventsHandler),
                new ProtocolConfig { Version = _highestSupportedVersion });
        }
        catch (Exception ex)
        {
            EqtTrace.Error("InProcessVsTestConsoleWrapper.DiscoverTests: Exception occurred: " + ex);

            discoveryEventsHandler.HandleLogMessage(TestMessageLevel.Error, ex.ToString());
            var errorDiscoveryComplete = new DiscoveryCompleteEventArgs
            {
                IsAborted = true,
                TotalCount = -1,
            };
            discoveryEventsHandler.HandleDiscoveryComplete(
                errorDiscoveryComplete,
                lastChunk: null);
        }

        _testPlatformEventSource.TranslationLayerDiscoveryStop();
    }

    /// <inheritdoc/>
    public void RunTests(
        IEnumerable<string> sources,
        string? runSettings,
        ITestRunEventsHandler testRunEventsHandler)
    {
        RunTests(
            sources,
            runSettings,
            options: null,
            testRunEventsHandler);
    }

    /// <inheritdoc/>
    public void RunTests(
        IEnumerable<string> sources,
        string? runSettings,
        TestPlatformOptions? options,
        ITestRunEventsHandler testRunEventsHandler)
    {
        RunTests(
            sources,
            runSettings,
            options,
            testSessionInfo: null,
            testRunEventsHandler);
    }

    /// <inheritdoc/>
    public void RunTests(
        IEnumerable<string> sources,
        string? runSettings,
        TestPlatformOptions? options,
        TestSessionInfo? testSessionInfo,
        ITestRunEventsHandler testRunEventsHandler)
    {
        var sourceList = sources.ToList();
        _testPlatformEventSource.TranslationLayerExecutionStart(
            0,
            0,
            sourceList.Count,
            runSettings ?? string.Empty);

        try
        {
            TestRequestManager?.ResetOptions();

            var testRunPayload = new TestRunRequestPayload
            {
                Sources = sourceList,
                RunSettings = runSettings,
                TestPlatformOptions = options,
                TestSessionInfo = testSessionInfo
            };

            TestRequestManager?.RunTests(
                testRunPayload,
                customTestHostLauncher: null,
                new RunHandlerToEventsRegistrarAdapter(testRunEventsHandler),
                new ProtocolConfig { Version = _highestSupportedVersion });
        }
        catch (Exception ex)
        {
            EqtTrace.Error("InProcessVsTestConsoleWrapper.RunTests: Exception occurred: " + ex);
            var testRunCompleteArgs = new TestRunCompleteEventArgs(null, false, true, ex, null, null, TimeSpan.MinValue);

            testRunEventsHandler.HandleLogMessage(TestMessageLevel.Error, ex.ToString());
            testRunEventsHandler.HandleTestRunComplete(testRunCompleteArgs, null, null, null);
        }

        _testPlatformEventSource.TranslationLayerExecutionStop();
    }

    /// <inheritdoc/>
    public void RunTests(
        IEnumerable<string> sources,
        string? runSettings,
        TestPlatformOptions? options,
        TestSessionInfo? testSessionInfo,
        ITestRunEventsHandler testRunEventsHandler,
        ITelemetryEventsHandler telemetryEventsHandler)
    {
        RunTests(
            sources,
            runSettings,
            options,
            testSessionInfo,
            testRunEventsHandler);
    }

    /// <inheritdoc/>
    public void RunTests(
        IEnumerable<TestCase> testCases,
        string? runSettings,
        ITestRunEventsHandler testRunEventsHandler)
    {
        RunTests(
            testCases,
            runSettings,
            options: null,
            testRunEventsHandler);
    }

    /// <inheritdoc/>
    public void RunTests(
        IEnumerable<TestCase> testCases,
        string? runSettings,
        TestPlatformOptions? options,
        ITestRunEventsHandler testRunEventsHandler)
    {
        RunTests(
            testCases,
            runSettings,
            options,
            testSessionInfo: null,
            testRunEventsHandler);
    }

    /// <inheritdoc/>
    public void RunTests(
        IEnumerable<TestCase> testCases,
        string? runSettings,
        TestPlatformOptions? options,
        TestSessionInfo? testSessionInfo,
        ITestRunEventsHandler testRunEventsHandler)
    {
        var testCaseList = testCases.ToList();
        _testPlatformEventSource.TranslationLayerExecutionStart(
            0,
            0,
            testCaseList.Count,
            runSettings ?? string.Empty);

        try
        {
            TestRequestManager?.ResetOptions();

            var testRunPayload = new TestRunRequestPayload
            {
                TestCases = testCaseList,
                RunSettings = runSettings,
                TestPlatformOptions = options,
                TestSessionInfo = testSessionInfo
            };

            TestRequestManager?.RunTests(
                testRunPayload,
                customTestHostLauncher: null,
                new RunHandlerToEventsRegistrarAdapter(testRunEventsHandler),
                new ProtocolConfig { Version = _highestSupportedVersion });
        }
        catch (Exception ex)
        {
            EqtTrace.Error("InProcessVsTestConsoleWrapper.RunTests: Exception occurred: " + ex);
            var testRunCompleteArgs = new TestRunCompleteEventArgs(null, false, true, ex, null, null, TimeSpan.MinValue);

            testRunEventsHandler.HandleLogMessage(TestMessageLevel.Error, ex.ToString());
            testRunEventsHandler.HandleTestRunComplete(testRunCompleteArgs, null, null, null);
        }

        _testPlatformEventSource.TranslationLayerExecutionStop();
    }

    /// <inheritdoc/>
    public void RunTests(
        IEnumerable<TestCase> testCases,
        string? runSettings,
        TestPlatformOptions? options,
        TestSessionInfo? testSessionInfo,
        ITestRunEventsHandler testRunEventsHandler,
        ITelemetryEventsHandler telemetryEventsHandler)
    {
        RunTests(
            testCases,
            runSettings,
            options,
            testSessionInfo,
            testRunEventsHandler);
    }

    /// <inheritdoc/>
    public void RunTestsWithCustomTestHost(
        IEnumerable<string> sources,
        string? runSettings,
        ITestRunEventsHandler testRunEventsHandler,
        ITestHostLauncher? customTestHostLauncher)
    {
        RunTestsWithCustomTestHost(
            sources,
            runSettings,
            options: null,
            testRunEventsHandler,
            customTestHostLauncher);
    }

    /// <inheritdoc/>
    public void RunTestsWithCustomTestHost(
        IEnumerable<string> sources,
        string? runSettings,
        TestPlatformOptions? options,
        ITestRunEventsHandler testRunEventsHandler,
        ITestHostLauncher? customTestHostLauncher)
    {
        RunTestsWithCustomTestHost(
            sources,
            runSettings,
            options,
            testSessionInfo: null,
            testRunEventsHandler,
            customTestHostLauncher);
    }

    /// <inheritdoc/>
    public void RunTestsWithCustomTestHost(
        IEnumerable<string> sources,
        string? runSettings,
        TestPlatformOptions? options,
        TestSessionInfo? testSessionInfo,
        ITestRunEventsHandler testRunEventsHandler,
        ITestHostLauncher? customTestHostLauncher)
    {
        var sourceList = sources.ToList();
        _testPlatformEventSource.TranslationLayerExecutionStart(
            1,
            sourceList.Count,
            0,
            runSettings ?? string.Empty);

        try
        {
            TestRequestManager?.ResetOptions();

            // We must avoid re-launching the test host if the test run payload already
            // contains test session info. Test session info being present is an indicative
            // of an already running test host spawned by a start test session call.
            var customLauncher =
                testSessionInfo is null
                    ? customTestHostLauncher
                    : null;

            var testRunPayload = new TestRunRequestPayload
            {
                Sources = sourceList,
                RunSettings = runSettings,
                DebuggingEnabled = customLauncher?.IsDebug == true,
                TestPlatformOptions = options,
                TestSessionInfo = testSessionInfo
            };

            // TODO: this will need to be via an adapter instead. Because we will get a "live"
            // implementation rather than our own implementation that we normally have in design
            // mode client. It probably will even throw on this cast.
            var upcastCustomLauncher = (ITestHostLauncher3?)customLauncher;

            TestRequestManager?.RunTests(
                testRunPayload,
                upcastCustomLauncher,
                new RunHandlerToEventsRegistrarAdapter(testRunEventsHandler),
                new ProtocolConfig { Version = _highestSupportedVersion });
        }
        catch (Exception ex)
        {
            EqtTrace.Error("InProcessVsTestConsoleWrapper.RunTestsWithCustomTestHost: Exception occurred: " + ex);
            var testRunCompleteArgs = new TestRunCompleteEventArgs(null, false, true, ex, null, null, TimeSpan.MinValue);

            testRunEventsHandler.HandleLogMessage(TestMessageLevel.Error, ex.ToString());
            testRunEventsHandler.HandleTestRunComplete(testRunCompleteArgs, null, null, null);
        }

        _testPlatformEventSource.TranslationLayerExecutionStop();
    }

    /// <inheritdoc/>
    public void RunTestsWithCustomTestHost(
        IEnumerable<string> sources,
        string? runSettings,
        TestPlatformOptions? options,
        TestSessionInfo? testSessionInfo,
        ITestRunEventsHandler testRunEventsHandler,
        ITelemetryEventsHandler telemetryEventsHandler,
        ITestHostLauncher? customTestHostLauncher)
    {
        RunTestsWithCustomTestHost(
            sources,
            runSettings,
            options,
            testSessionInfo,
            testRunEventsHandler,
            customTestHostLauncher);
    }

    /// <inheritdoc/>
    public void RunTestsWithCustomTestHost(
        IEnumerable<TestCase> testCases,
        string? runSettings,
        ITestRunEventsHandler testRunEventsHandler,
        ITestHostLauncher? customTestHostLauncher)
    {
        RunTestsWithCustomTestHost(
            testCases,
            runSettings,
            options: null,
            testRunEventsHandler,
            customTestHostLauncher);
    }

    /// <inheritdoc/>
    public void RunTestsWithCustomTestHost(
        IEnumerable<TestCase> testCases,
        string? runSettings,
        TestPlatformOptions? options,
        ITestRunEventsHandler testRunEventsHandler,
        ITestHostLauncher? customTestHostLauncher)
    {
        RunTestsWithCustomTestHost(
            testCases,
            runSettings,
            options,
            testSessionInfo: null,
            testRunEventsHandler,
            customTestHostLauncher);
    }

    /// <inheritdoc/>
    public void RunTestsWithCustomTestHost(
        IEnumerable<TestCase> testCases,
        string? runSettings,
        TestPlatformOptions? options,
        TestSessionInfo? testSessionInfo,
        ITestRunEventsHandler testRunEventsHandler,
        ITestHostLauncher? customTestHostLauncher)
    {
        var testCaseList = testCases.ToList();
        _testPlatformEventSource.TranslationLayerExecutionStart(
            1,
            0,
            testCaseList.Count,
            runSettings ?? string.Empty);

        try
        {
            TestRequestManager?.ResetOptions();

            // We must avoid re-launching the test host if the test run payload already
            // contains test session info. Test session info being present is an indicative
            // of an already running test host spawned by a start test session call.
            var customLauncher =
                testSessionInfo is null
                    ? customTestHostLauncher
                    : null;

            var testRunPayload = new TestRunRequestPayload
            {
                TestCases = testCaseList,
                RunSettings = runSettings,
                DebuggingEnabled = customLauncher?.IsDebug == true,
                TestPlatformOptions = options,
                TestSessionInfo = testSessionInfo
            };

            // TODO: this will need to be via an adapter instead. Because we will get a "live"
            // implementation rather than our own implementation that we normally have in design
            // mode client. It probably will even throw on this cast.
            var upcastCustomLauncher = (ITestHostLauncher3?)customLauncher;

            TestRequestManager?.RunTests(
                testRunPayload,
                upcastCustomLauncher,
                new RunHandlerToEventsRegistrarAdapter(testRunEventsHandler),
                new ProtocolConfig { Version = _highestSupportedVersion });
        }
        catch (Exception ex)
        {
            EqtTrace.Error("InProcessVsTestConsoleWrapper.RunTestsWithCustomTestHost: Exception occurred: " + ex);
            var testRunCompleteArgs = new TestRunCompleteEventArgs(null, false, true, ex, null, null, TimeSpan.MinValue);

            testRunEventsHandler.HandleLogMessage(TestMessageLevel.Error, ex.ToString());
            testRunEventsHandler.HandleTestRunComplete(testRunCompleteArgs, null, null, null);
        }

        _testPlatformEventSource.TranslationLayerExecutionStop();
    }

    /// <inheritdoc/>
    public void RunTestsWithCustomTestHost(
        IEnumerable<TestCase> testCases,
        string? runSettings,
        TestPlatformOptions? options,
        TestSessionInfo? testSessionInfo,
        ITestRunEventsHandler testRunEventsHandler,
        ITelemetryEventsHandler telemetryEventsHandler,
        ITestHostLauncher? customTestHostLauncher)
    {
        RunTestsWithCustomTestHost(
            testCases,
            runSettings,
            options,
            testSessionInfo,
            testRunEventsHandler,
            customTestHostLauncher);
    }

    #region Async, not implemented
    /// <inheritdoc/>
    public async Task DiscoverTestsAsync(
        IEnumerable<string> sources,
        string? discoverySettings,
        ITestDiscoveryEventsHandler discoveryEventsHandler)
    {
        await DiscoverTestsAsync(
                sources,
                discoverySettings,
                options: null,
                new DiscoveryEventsHandleConverter(discoveryEventsHandler))
            .ConfigureAwait(false);
    }

    /// <inheritdoc/>
    public async Task DiscoverTestsAsync(
        IEnumerable<string> sources,
        string? discoverySettings,
        TestPlatformOptions? options,
        ITestDiscoveryEventsHandler2 discoveryEventsHandler)
    {
        await DiscoverTestsAsync(
                sources,
                discoverySettings,
                options,
                testSessionInfo: null,
                discoveryEventsHandler)
            .ConfigureAwait(false);
    }

    /// <inheritdoc/>
    public Task DiscoverTestsAsync(
        IEnumerable<string> sources,
        string? discoverySettings,
        TestPlatformOptions? options,
        TestSessionInfo? testSessionInfo,
        ITestDiscoveryEventsHandler2 discoveryEventsHandler)
    {
        throw new NotImplementedException();
    }

    /// <inheritdoc/>
    public Task InitializeExtensionsAsync(IEnumerable<string> pathToAdditionalExtensions)
    {
        throw new NotImplementedException();
    }

    /// <inheritdoc/>
    public async Task ProcessTestRunAttachmentsAsync(
        IEnumerable<AttachmentSet> attachments,
        string? processingSettings,
        bool isLastBatch,
        bool collectMetrics,
        ITestRunAttachmentsProcessingEventsHandler eventsHandler,
        CancellationToken cancellationToken)
    {
        await ProcessTestRunAttachmentsAsync(
                attachments,
                invokedDataCollectors: null,
                processingSettings,
                isLastBatch,
                collectMetrics,
                eventsHandler,
                cancellationToken)
            .ConfigureAwait(false);
    }

    /// <inheritdoc/>
    public async Task ProcessTestRunAttachmentsAsync(
        IEnumerable<AttachmentSet> attachments,
        IEnumerable<InvokedDataCollector>? invokedDataCollectors,
        string? processingSettings,
        bool isLastBatch,
        bool collectMetrics,
        ITestRunAttachmentsProcessingEventsHandler eventsHandler,
        CancellationToken cancellationToken)
    {
        _testPlatformEventSource.TranslationLayerTestRunAttachmentsProcessingStart();

        try
        {
            var attachmentProcessingPayload = new TestRunAttachmentsProcessingPayload
            {
                Attachments = attachments,
                InvokedDataCollectors = invokedDataCollectors,
                RunSettings = processingSettings,
                CollectMetrics = collectMetrics
            };

            using (cancellationToken.Register(() =>
                TestRequestManager?.CancelTestRunAttachmentsProcessing()))
            {
                // Awaiting the attachment processing task here. The implementation of the
                // underlying operation guarantees the event handler is called when processing
                // is complete, so when awaiting ends, the results have already been passed to
                // the caller via the event handler. No need for further synchronization.
                //
                // NOTE: We're passing in CancellationToken.None and that is by design, *DO NOT*
                // attempt to optimize this code by passing in the cancellation token registered
                // above. Passing in the said token would result in potentially leaving the caller
                // hanging when the token is signaled before even starting the test run attachment
                // processing. In this scenario, the Task.Run should not even run in the first
                // place, and as such the event handler that signals processing is complete will
                // not be triggered anymore.
                await Task.Run(() =>
                        TestRequestManager?.ProcessTestRunAttachments(
                            attachmentProcessingPayload,
                            new InProcessTestRunAttachmentsProcessingEventsHandler(eventsHandler),
                            new ProtocolConfig { Version = _highestSupportedVersion }),
                        CancellationToken.None)
                    .ConfigureAwait(false);
            }
        }
        catch (Exception ex)
        {
            EqtTrace.Error("InProcessVsTestConsoleWrapper.ProcessTestRunAttachmentsAsync: Exception occurred: " + ex);

            var attachmentsProcessingArgs = new TestRunAttachmentsProcessingCompleteEventArgs(
                isCanceled: cancellationToken.IsCancellationRequested,
                ex);

            eventsHandler.HandleLogMessage(TestMessageLevel.Error, ex.ToString());
            eventsHandler.HandleTestRunAttachmentsProcessingComplete(attachmentsProcessingArgs, lastChunk: null);
        }

        _testPlatformEventSource.TranslationLayerTestRunAttachmentsProcessingStop();
    }

    /// <inheritdoc/>
    public async Task RunTestsAsync(
        IEnumerable<string> sources,
        string? runSettings,
        ITestRunEventsHandler testRunEventsHandler)
    {
        await RunTestsAsync(
                sources,
                runSettings,
                options: null,
                testRunEventsHandler)
            .ConfigureAwait(false);
    }

    /// <inheritdoc/>
    public async Task RunTestsAsync(
        IEnumerable<string> sources,
        string? runSettings,
        TestPlatformOptions? options,
        ITestRunEventsHandler testRunEventsHandler)
    {
        await RunTestsAsync(
                sources,
                runSettings,
                options,
                testSessionInfo: null,
                testRunEventsHandler)
            .ConfigureAwait(false);
    }

    /// <inheritdoc/>
    public Task RunTestsAsync(
        IEnumerable<string> sources,
        string? runSettings,
        TestPlatformOptions? options,
        TestSessionInfo? testSessionInfo,
        ITestRunEventsHandler testRunEventsHandler)
    {
        throw new NotImplementedException();
    }

    /// <inheritdoc/>
    public Task RunTestsAsync(
        IEnumerable<string> sources,
        string? runSettings,
        TestPlatformOptions? options,
        TestSessionInfo? testSessionInfo,
        ITestRunEventsHandler testRunEventsHandler,
        ITelemetryEventsHandler telemetryEventsHandler)
    {
        throw new NotImplementedException();
    }

    /// <inheritdoc/>
    public async Task RunTestsAsync(
        IEnumerable<TestCase> testCases,
        string? runSettings,
        ITestRunEventsHandler testRunEventsHandler)
    {
        await RunTestsAsync(
                testCases,
                runSettings,
                options: null,
                testRunEventsHandler)
            .ConfigureAwait(false);
    }

    /// <inheritdoc/>
    public async Task RunTestsAsync(
        IEnumerable<TestCase> testCases,
        string? runSettings,
        TestPlatformOptions? options,
        ITestRunEventsHandler testRunEventsHandler)
    {
        await RunTestsAsync(
                testCases,
                runSettings,
                options,
                testSessionInfo: null,
                testRunEventsHandler)
            .ConfigureAwait(false);
    }

    /// <inheritdoc/>
    public Task RunTestsAsync(
        IEnumerable<TestCase> testCases,
        string? runSettings,
        TestPlatformOptions? options,
        TestSessionInfo? testSessionInfo,
        ITestRunEventsHandler testRunEventsHandler)
    {
        throw new NotImplementedException();
    }

    /// <inheritdoc/>
    public Task RunTestsAsync(
        IEnumerable<TestCase> testCases,
        string? runSettings,
        TestPlatformOptions? options,
        TestSessionInfo? testSessionInfo,
        ITestRunEventsHandler testRunEventsHandler,
        ITelemetryEventsHandler telemetryEventsHandler)
    {
        throw new NotImplementedException();
    }

    /// <inheritdoc/>
    public async Task RunTestsWithCustomTestHostAsync(
        IEnumerable<string> sources,
        string? runSettings,
        ITestRunEventsHandler testRunEventsHandler,
        ITestHostLauncher customTestHostLauncher)
    {
        await RunTestsWithCustomTestHostAsync(
                sources,
                runSettings,
                options: null,
                testRunEventsHandler,
                customTestHostLauncher)
            .ConfigureAwait(false);
    }

    /// <inheritdoc/>
    public async Task RunTestsWithCustomTestHostAsync(
        IEnumerable<string> sources,
        string? runSettings,
        TestPlatformOptions? options,
        ITestRunEventsHandler testRunEventsHandler,
        ITestHostLauncher customTestHostLauncher)
    {
        await RunTestsWithCustomTestHostAsync(
                sources,
                runSettings,
                options,
                testSessionInfo: null,
                testRunEventsHandler,
                customTestHostLauncher)
            .ConfigureAwait(false);
    }

    /// <inheritdoc/>
    public Task RunTestsWithCustomTestHostAsync(
        IEnumerable<string> sources,
        string? runSettings,
        TestPlatformOptions? options,
        TestSessionInfo? testSessionInfo,
        ITestRunEventsHandler testRunEventsHandler,
        ITestHostLauncher customTestHostLauncher)
    {
        throw new NotImplementedException();
    }

    /// <inheritdoc/>
    public Task RunTestsWithCustomTestHostAsync(
        IEnumerable<string> sources,
        string? runSettings,
        TestPlatformOptions? options,
        TestSessionInfo? testSessionInfo,
        ITestRunEventsHandler testRunEventsHandler,
        ITelemetryEventsHandler telemetryEventsHandler,
        ITestHostLauncher customTestHostLauncher)
    {
        throw new NotImplementedException();
    }

    /// <inheritdoc/>
    public async Task RunTestsWithCustomTestHostAsync(
        IEnumerable<TestCase> testCases,
        string? runSettings,
        ITestRunEventsHandler testRunEventsHandler,
        ITestHostLauncher customTestHostLauncher)
    {
        await RunTestsWithCustomTestHostAsync(
                testCases,
                runSettings,
                options: null,
                testRunEventsHandler,
                customTestHostLauncher)
            .ConfigureAwait(false);
    }

    /// <inheritdoc/>
    public async Task RunTestsWithCustomTestHostAsync(
        IEnumerable<TestCase> testCases,
        string? runSettings,
        TestPlatformOptions? options,
        ITestRunEventsHandler testRunEventsHandler,
        ITestHostLauncher customTestHostLauncher)
    {
        await RunTestsWithCustomTestHostAsync(
                testCases,
                runSettings,
                options,
                testSessionInfo: null,
                testRunEventsHandler,
                customTestHostLauncher)
            .ConfigureAwait(false);
    }

    /// <inheritdoc/>
    public Task RunTestsWithCustomTestHostAsync(
        IEnumerable<TestCase> testCases,
        string? runSettings,
        TestPlatformOptions? options,
        TestSessionInfo? testSessionInfo,
        ITestRunEventsHandler testRunEventsHandler,
        ITestHostLauncher customTestHostLauncher)
    {
        throw new NotImplementedException();
    }

    /// <inheritdoc/>
    public Task RunTestsWithCustomTestHostAsync(
        IEnumerable<TestCase> testCases,
        string? runSettings,
        TestPlatformOptions? options,
        TestSessionInfo? testSessionInfo,
        ITestRunEventsHandler testRunEventsHandler,
        ITelemetryEventsHandler telemetryEventsHandler,
        ITestHostLauncher customTestHostLauncher)
    {
        throw new NotImplementedException();
    }

    /// <inheritdoc/>
    public Task StartSessionAsync()
    {
        throw new NotImplementedException();
    }

    /// <inheritdoc/>
    [Obsolete("This API is not final yet and is subject to changes.", false)]
    public async Task<ITestSession?> StartTestSessionAsync(
        IList<string> sources,
        string? runSettings,
        ITestSessionEventsHandler eventsHandler)
    {
        return await StartTestSessionAsync(
                sources,
                runSettings,
                options: null,
                eventsHandler)
            .ConfigureAwait(false);
    }

    /// <inheritdoc/>
    [Obsolete("This API is not final yet and is subject to changes.", false)]
    public async Task<ITestSession?> StartTestSessionAsync(
        IList<string> sources,
        string? runSettings,
        TestPlatformOptions? options,
        ITestSessionEventsHandler eventsHandler)
    {
        return await StartTestSessionAsync(
                sources,
                runSettings,
                options,
                eventsHandler,
                testHostLauncher: null)
            .ConfigureAwait(false);
    }

    /// <inheritdoc/>
    [Obsolete("This API is not final yet and is subject to changes.", false)]
    public Task<ITestSession?> StartTestSessionAsync(
        IList<string> sources,
        string? runSettings,
        TestPlatformOptions? options,
        ITestSessionEventsHandler eventsHandler,
        ITestHostLauncher? testHostLauncher)
    {
        throw new NotImplementedException();
    }

    /// <inheritdoc/>
    [Obsolete("This API is not final yet and is subject to changes.", false)]
    public async Task<bool> StopTestSessionAsync(
        TestSessionInfo? testSessionInfo,
        ITestSessionEventsHandler eventsHandler)
    {
        return await StopTestSessionAsync(
                testSessionInfo,
                options: null,
                eventsHandler)
            .ConfigureAwait(false);
    }

    /// <inheritdoc/>
    [Obsolete("This API is not final yet and is subject to changes.", false)]
    public Task<bool> StopTestSessionAsync(
        TestSessionInfo? testSessionInfo,
        TestPlatformOptions? options,
        ITestSessionEventsHandler eventsHandler)
    {
        throw new NotImplementedException();
    }
    #endregion

    private bool WaitForConnection()
    {
        EqtTrace.Info("InProcessVsTestConsoleWrapper.WaitForConnection: Waiting for connection to command line runner.");

        var timeout = EnvironmentHelper.GetConnectionTimeout() * 1000;
        if (!_requestSender.WaitForRequestHandlerConnection(timeout))
        {
            throw new TransationLayerException(
                string.Format(
                    CultureInfo.CurrentCulture,
                    Resources.Resources.RequestHandlerConnectionTimedOut,
                    timeout));
        }

        _testPlatformEventSource.TranslationLayerInitializeStop();
        return true;
    }
}