File: Tasks\VSTestTask.cs
Web Access
Project: src\src\vstest\src\Microsoft.TestPlatform.Build\Microsoft.TestPlatform.Build.csproj (Microsoft.TestPlatform.Build)
// 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.Diagnostics;
using System.Threading;

using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;
using Microsoft.TestPlatform.Build.Trace;

namespace Microsoft.TestPlatform.Build.Tasks;

public class VSTestTask : Task, ITestTask
{
    private int _activeProcessId;

    [Required]
    public ITaskItem? TestFileFullPath { get; set; }
    public string? VSTestSetting { get; set; }
    public ITaskItem[]? VSTestTestAdapterPath { get; set; }
    public string? VSTestFramework { get; set; }
    public string? VSTestPlatform { get; set; }
    public string? VSTestTestCaseFilter { get; set; }
    public string[]? VSTestLogger { get; set; }
    public bool VSTestListTests { get; set; }
    public string? VSTestDiag { get; set; }
    public string[]? VSTestCLIRunSettings { get; set; }
    [Required]
    public ITaskItem? VSTestConsolePath { get; set; }
    public ITaskItem? VSTestResultsDirectory { get; set; }
    public string? VSTestVerbosity { get; set; }
    public string[]? VSTestCollect { get; set; }
    public bool VSTestBlame { get; set; }
    public bool VSTestBlameCrash { get; set; }
    public string? VSTestBlameCrashDumpType { get; set; }
    public bool VSTestBlameCrashCollectAlways { get; set; }
    public bool VSTestBlameHang { get; set; }
    public string? VSTestBlameHangDumpType { get; set; }
    public string? VSTestBlameHangTimeout { get; set; }
    public ITaskItem? VSTestTraceDataCollectorDirectoryPath { get; set; }
    public bool VSTestNoLogo { get; set; }
    public string? VSTestArtifactsProcessingMode { get; set; }
    public string? VSTestSessionCorrelationId { get; set; }

    public override bool Execute()
    {
        var traceEnabledValue = Environment.GetEnvironmentVariable("VSTEST_BUILD_TRACE");
        Tracing.traceEnabled = string.Equals(traceEnabledValue, "1", StringComparison.OrdinalIgnoreCase);

        var debugEnabled = Environment.GetEnvironmentVariable("VSTEST_BUILD_DEBUG");
        if (string.Equals(debugEnabled, "1", StringComparison.Ordinal))
        {
            Console.WriteLine("Waiting for debugger attach...");

            var currentProcess = Process.GetCurrentProcess();
            Console.WriteLine($"Process Id: {currentProcess.Id}, Name: {currentProcess.ProcessName}");

            while (!Debugger.IsAttached)
            {
                Thread.Sleep(1000);
            }

            Debugger.Break();
        }

        // Avoid logging "Task returned false but did not log an error." on test failure, because we don't
        // write MSBuild error. https://github.com/dotnet/msbuild/blob/51a1071f8871e0c93afbaf1b2ac2c9e59c7b6491/src/Framework/IBuildEngine7.cs#L12
        var allowFailureWithoutError = BuildEngine.GetType().GetProperty("AllowFailureWithoutError");
        allowFailureWithoutError?.SetValue(BuildEngine, true);

        var processInfo = new ProcessStartInfo
        {
            FileName = TestTaskUtils.ResolveDotnetPath() ?? "dotnet",
            Arguments = TestTaskUtils.CreateCommandLineArguments(this),
            UseShellExecute = false,
        };

        if (!VSTestFramework.IsNullOrEmpty())
        {
            Console.WriteLine(Resources.Resources.TestRunningSummary, TestFileFullPath, VSTestFramework);
        }

        Tracing.Trace("VSTest: Starting vstest.console...");
        Tracing.Trace($"VSTest: Arguments: {processInfo.FileName} {processInfo.Arguments}");

        using var activeProcess = new Process { StartInfo = processInfo };
        activeProcess.Start();
        _activeProcessId = activeProcess.Id;

        activeProcess.WaitForExit();
        Tracing.Trace($"VSTest: Exit code: {activeProcess.ExitCode}");
        return activeProcess.ExitCode == 0;
    }

    public void Cancel()
    {
        Tracing.Trace("VSTest: Killing the process...");
        try
        {
            Process.GetProcessById(_activeProcessId).Kill();
        }
        catch (ArgumentException ex)
        {
            Tracing.Trace($"VSTest: Killing process throws ArgumentException with the following message {ex}. It may be that process is not running");
        }
    }
}