File: ExtensionDecorators\SerialTestRunDecorator.cs
Web Access
Project: src\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 System.Threading;
using System.Xml.Linq;

using Microsoft.VisualStudio.TestPlatform.ObjectModel;
using Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter;
using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging;

namespace Microsoft.VisualStudio.TestPlatform.Common.ExtensionDecorators;

internal class SerialTestRunDecorator : ITestExecutor, ITestExecutor2, IDisposable
{
    private readonly SemaphoreSlim _runSequentialEvent = new(1);

    public ITestExecutor OriginalTestExecutor { get; }

    public SerialTestRunDecorator(ITestExecutor originalTestExecutor)
    {
        OriginalTestExecutor = originalTestExecutor;
    }

    public void RunTests(IEnumerable<TestCase>? tests, IRunContext? runContext, IFrameworkHandle? frameworkHandle)
    {
        if (IsSerialTestRunEnabled(runContext))
        {
            EqtTrace.Info("SerializeTestRunDecorator.RunTests: Test cases will run sequentially");
            if (tests is null)
            {
                return;
            }

            foreach (TestCase testToRun in tests)
            {
                _runSequentialEvent.Wait();
                OriginalTestExecutor.RunTests(new List<TestCase> { testToRun }, runContext, new SerializeTestRunDecoratorFrameworkHandle(frameworkHandle!, _runSequentialEvent));
            }
        }
        else
        {
            OriginalTestExecutor.RunTests(tests, runContext, frameworkHandle);
        }
    }

    public void RunTests(IEnumerable<string>? sources, IRunContext? runContext, IFrameworkHandle? frameworkHandle)
    {
        if (IsSerialTestRunEnabled(runContext))
        {
            EqtTrace.Error("<ForceOneTestAtTimePerTestHost>true</ForceOneTestAtTimePerTestHost> is not supported for sources test run.");
            frameworkHandle?.SendMessage(TestMessageLevel.Error, Resources.Resources.SerialTestRunInvalidScenario);
        }
        else
        {
            OriginalTestExecutor.RunTests(sources, runContext, frameworkHandle);
        }
    }

    public bool ShouldAttachToTestHost(IEnumerable<string>? sources, IRunContext runContext)
    {
        if (OriginalTestExecutor is ITestExecutor2 executor)
        {
            return executor.ShouldAttachToTestHost(sources, 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 true;
    }

    public bool ShouldAttachToTestHost(IEnumerable<TestCase>? tests, IRunContext runContext)
    {
        if (OriginalTestExecutor is ITestExecutor2 executor)
        {
            return executor.ShouldAttachToTestHost(tests, 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 true;
    }

    public void Cancel()
        => OriginalTestExecutor.Cancel();

    private static bool IsSerialTestRunEnabled(IRunContext? runContext)
    {
        if (runContext is null || runContext.RunSettings is null || runContext.RunSettings.SettingsXml is null)
        {
            return false;
        }

        XElement runSettings = XElement.Parse(runContext.RunSettings.SettingsXml);
        XElement? forceOneTestAtTimePerTestHost = runSettings.Element("RunConfiguration")?.Element("ForceOneTestAtTimePerTestHost");
        return forceOneTestAtTimePerTestHost is not null && bool.TryParse(forceOneTestAtTimePerTestHost.Value, out bool enabled) && enabled;
    }

    public void Dispose()
        => _runSequentialEvent.Dispose();
}

internal class SerializeTestRunDecoratorFrameworkHandle : IFrameworkHandle
{
    private readonly IFrameworkHandle _frameworkHandle;
    private readonly SemaphoreSlim _testEnd;

    public SerializeTestRunDecoratorFrameworkHandle(IFrameworkHandle frameworkHandle, SemaphoreSlim testEnd)
    {
        _frameworkHandle = frameworkHandle;
        _testEnd = testEnd;
    }

    [Obsolete("This property has no effect", error: false)]
public bool EnableShutdownAfterTestRun { get => _frameworkHandle.EnableShutdownAfterTestRun; set => _frameworkHandle.EnableShutdownAfterTestRun = value; }

    public int LaunchProcessWithDebuggerAttached(string filePath, string? workingDirectory, string? arguments, IDictionary<string, string?>? environmentVariables)
        => _frameworkHandle.LaunchProcessWithDebuggerAttached(filePath, workingDirectory, arguments, environmentVariables);

    public void RecordAttachments(IList<AttachmentSet> attachmentSets)
        => _frameworkHandle.RecordAttachments(attachmentSets);

    public void RecordEnd(TestCase testCase, TestOutcome outcome)
    {
        _frameworkHandle.RecordEnd(testCase, outcome);
        _testEnd.Release();
    }

    public void RecordResult(TestResult testResult)
        => _frameworkHandle.RecordResult(testResult);

    public void RecordStart(TestCase testCase)
        => _frameworkHandle.RecordStart(testCase);

    public void SendMessage(TestMessageLevel testMessageLevel, string message)
        => _frameworkHandle.SendMessage(testMessageLevel, message);
}