From d63035b788840103f94c54eeba5df093ea1a2a8e Mon Sep 17 00:00:00 2001 From: GitHub Copilot Date: Mon, 27 Apr 2026 19:55:30 +0200 Subject: [PATCH] Convert ExitCodes from static class to enum Converts the internal ExitCodes static class with const int fields to an internal enum. This improves type safety, discoverability, and enables Enum.IsDefined checks for validating known MTP exit codes. This is safe because: - ExitCodes is internal and not in any PublicAPI.txt - const int values are inlined at compile time, so extensions compiled against the old class have zero runtime dependency on the ExitCodes type - All out-of-repo IVT consumers (MSTest.Engine, OpenTelemetry, etc.) do not reference ExitCodes at all - Source-embedded consumers always recompile from the same source Fixes #7807 --- .../RetryOrchestrator.cs | 8 ++-- .../TrxCompareTool.cs | 4 +- .../TrxReportEngine.cs | 4 +- .../Tasks/InvokeTestingPlatformTask.cs | 4 +- .../Helpers/ExitCodes.cs | 34 ++++++++--------- .../NonCooperativeParentProcessListener.cs | 4 +- .../Hosts/CommonTestHost.cs | 8 ++-- .../Hosts/ConsoleTestHost.cs | 4 +- .../Hosts/ServerTestHost.cs | 4 +- .../Hosts/TestHostBuilder.cs | 2 +- .../Hosts/TestHostControllersTestHost.cs | 8 ++-- .../Hosts/TestHostOchestratorHost.cs | 2 +- .../Hosts/ToolsTestHost.cs | 8 ++-- .../IPC/NamedPipeClient.cs | 2 +- .../Services/TestApplicationResult.cs | 16 ++++---- .../SdkTests.cs | 4 +- .../ConsoleTests.cs | 2 +- .../ExecutionRequestCompleteTests.cs | 2 +- .../Helpers/AcceptanceAssert.cs | 14 +++++++ .../MSBuildTests.GenerateEntryPoint.cs | 2 +- .../ServerMode/ServerTests.cs | 2 +- .../Services/TestApplicationResultTests.cs | 38 +++++++++---------- 22 files changed, 93 insertions(+), 83 deletions(-) diff --git a/src/Platform/Microsoft.Testing.Extensions.Retry/RetryOrchestrator.cs b/src/Platform/Microsoft.Testing.Extensions.Retry/RetryOrchestrator.cs index 3533f67fc6..42daf122c2 100644 --- a/src/Platform/Microsoft.Testing.Extensions.Retry/RetryOrchestrator.cs +++ b/src/Platform/Microsoft.Testing.Extensions.Retry/RetryOrchestrator.cs @@ -298,16 +298,16 @@ public async Task OrchestrateTestHostExecutionAsync(CancellationToken cance catch (OperationCanceledException) when (processExitedCancellationToken.IsCancellationRequested) { await outputDevice.DisplayAsync(this, new ErrorMessageOutputDeviceData(string.Format(CultureInfo.InvariantCulture, ExtensionResources.TestHostProcessExitedBeforeRetryCouldConnect, testHostProcess.ExitCode)), cancellationToken).ConfigureAwait(false); - return ExitCodes.GenericFailure; + return (int)ExitCodes.GenericFailure; } } await testHostProcess.WaitForExitAsync().ConfigureAwait(false); exitCodes.Add(testHostProcess.ExitCode); - if (testHostProcess.ExitCode != ExitCodes.Success) + if (testHostProcess.ExitCode != (int)ExitCodes.Success) { - if (testHostProcess.ExitCode != ExitCodes.AtLeastOneTestFailed) + if (testHostProcess.ExitCode != (int)ExitCodes.AtLeastOneTestFailed) { await outputDevice.DisplayAsync(this, new WarningMessageOutputDeviceData(string.Format(CultureInfo.InvariantCulture, ExtensionResources.TestSuiteFailedWithWrongExitCode, testHostProcess.ExitCode)), cancellationToken).ConfigureAwait(false); retryInterrupted = true; @@ -370,7 +370,7 @@ public async Task OrchestrateTestHostExecutionAsync(CancellationToken cance if (!thresholdPolicyKickedIn && !retryInterrupted) { - if (exitCodes[^1] != ExitCodes.Success) + if (exitCodes[^1] != (int)ExitCodes.Success) { await outputDevice.DisplayAsync(this, new ErrorMessageOutputDeviceData(string.Format(CultureInfo.InvariantCulture, ExtensionResources.TestSuiteFailedInAllAttempts, userMaxRetryCount + 1)), cancellationToken).ConfigureAwait(false); } diff --git a/src/Platform/Microsoft.Testing.Extensions.TrxReport/TrxCompareTool.cs b/src/Platform/Microsoft.Testing.Extensions.TrxReport/TrxCompareTool.cs index 5dbb643375..0f822e14fc 100644 --- a/src/Platform/Microsoft.Testing.Extensions.TrxReport/TrxCompareTool.cs +++ b/src/Platform/Microsoft.Testing.Extensions.TrxReport/TrxCompareTool.cs @@ -73,12 +73,12 @@ await _task.WhenAll( if (AreMatchingTrxFiles(baseLineResults, comparedResults, outputBuilder)) { await _outputDisplay.DisplayAsync(this, new TextOutputDeviceData(outputBuilder.ToString()), cancellationToken).ConfigureAwait(false); - return ExitCodes.Success; + return (int)ExitCodes.Success; } else { await _outputDisplay.DisplayAsync(this, new TextOutputDeviceData(outputBuilder.ToString()), cancellationToken).ConfigureAwait(false); - return ExitCodes.GenericFailure; + return (int)ExitCodes.GenericFailure; } } diff --git a/src/Platform/Microsoft.Testing.Extensions.TrxReport/TrxReportEngine.cs b/src/Platform/Microsoft.Testing.Extensions.TrxReport/TrxReportEngine.cs index 61ebb27157..da3785e56e 100644 --- a/src/Platform/Microsoft.Testing.Extensions.TrxReport/TrxReportEngine.cs +++ b/src/Platform/Microsoft.Testing.Extensions.TrxReport/TrxReportEngine.cs @@ -165,7 +165,7 @@ public TrxReportEngine( AddTestLists(testRun); bool hasFailedTests = summaryCounts.Failed > 0 || summaryCounts.Timedout > 0; - string trxOutcome = isTestHostCrashed || _exitCode != ExitCodes.Success || hasFailedTests ? "Failed" : "Completed"; + string trxOutcome = isTestHostCrashed || _exitCode != (int)ExitCodes.Success || hasFailedTests ? "Failed" : "Completed"; AddResultSummary(testRun, trxOutcome, runDeploymentRoot, testHostCrashInfo, _exitCode, summaryCounts, isTestHostCrashed); @@ -333,7 +333,7 @@ private void AddResultSummary(XElement testRun, string resultSummaryOutcome, str runInfo.Add(text); runInfos.Add(runInfo); } - else if (exitCode != ExitCodes.Success) + else if (exitCode != (int)ExitCodes.Success) { var runInfos = new XElement(NamespaceUri + "RunInfos"); resultSummary.Add(runInfos); diff --git a/src/Platform/Microsoft.Testing.Platform.MSBuild/Tasks/InvokeTestingPlatformTask.cs b/src/Platform/Microsoft.Testing.Platform.MSBuild/Tasks/InvokeTestingPlatformTask.cs index ba321f8e74..25c460b25e 100644 --- a/src/Platform/Microsoft.Testing.Platform.MSBuild/Tasks/InvokeTestingPlatformTask.cs +++ b/src/Platform/Microsoft.Testing.Platform.MSBuild/Tasks/InvokeTestingPlatformTask.cs @@ -433,14 +433,14 @@ protected override void LogToolCommand(string message) protected override bool HandleTaskExecutionErrors() { // This is an unexpected situation we simply print to the console the output and return false. - if (string.IsNullOrEmpty(_outputFileName) && ExitCode != ExitCodes.InvalidCommandLine) + if (string.IsNullOrEmpty(_outputFileName) && ExitCode != (int)ExitCodes.InvalidCommandLine) { Log.LogError(null, "run failed", null, TargetPath.ItemSpec.Trim(), 0, 0, 0, 0, Resources.MSBuildResources.TestFailedNoDetail, _output); } else { // If the output file name is null and the exit code is invalid command line we create a default one. - if (_outputFileName is null && ExitCode == ExitCodes.InvalidCommandLine) + if (_outputFileName is null && ExitCode == (int)ExitCodes.InvalidCommandLine) { _outputFileName = Path.Combine(Path.GetDirectoryName(TargetPath.ItemSpec.Trim())!, "TestResults"); _fileSystem.CreateDirectory(_outputFileName); diff --git a/src/Platform/Microsoft.Testing.Platform/Helpers/ExitCodes.cs b/src/Platform/Microsoft.Testing.Platform/Helpers/ExitCodes.cs index 070a33b6bf..5c35683594 100644 --- a/src/Platform/Microsoft.Testing.Platform/Helpers/ExitCodes.cs +++ b/src/Platform/Microsoft.Testing.Platform/Helpers/ExitCodes.cs @@ -9,25 +9,21 @@ namespace Microsoft.Testing.Platform.Helpers; /// We use positive exit codes for failure because POSIX/BASH exit codes are unsigned 8-bit integers. /// On POSIX systems the standard exit code is 0 for success and any number from 1 to 255 for anything else. /// -// TODO: Consider changing this to an enum, and rename to 'ExitCode' to follow enum naming convention. -// Being an enum makes it easier to do 'Enum.IsDefined' checks to validate if an exit code is a known MTP exit code. -// Note: Changing this to enum would be binary breaking for extensions built against MTP <= 2.1 that still consume this via IVT -// (those extensions reference the class directly from the MTP assembly, not via source embedding). [Embedded] -internal static class ExitCodes +internal enum ExitCodes { - public const int Success = 0; - public const int GenericFailure = 1; - public const int AtLeastOneTestFailed = 2; - public const int TestSessionAborted = 3; - public const int InvalidPlatformSetup = 4; - public const int InvalidCommandLine = 5; - // public const int FeatureNotImplemented = 6; - public const int TestHostProcessExitedNonGracefully = 7; - public const int ZeroTests = 8; - public const int MinimumExpectedTestsPolicyViolation = 9; - public const int TestAdapterTestSessionFailure = 10; - public const int DependentProcessExited = 11; - public const int IncompatibleProtocolVersion = 12; - public const int TestExecutionStoppedForMaxFailedTests = 13; + Success = 0, + GenericFailure = 1, + AtLeastOneTestFailed = 2, + TestSessionAborted = 3, + InvalidPlatformSetup = 4, + InvalidCommandLine = 5, + // FeatureNotImplemented = 6, + TestHostProcessExitedNonGracefully = 7, + ZeroTests = 8, + MinimumExpectedTestsPolicyViolation = 9, + TestAdapterTestSessionFailure = 10, + DependentProcessExited = 11, + IncompatibleProtocolVersion = 12, + TestExecutionStoppedForMaxFailedTests = 13, } diff --git a/src/Platform/Microsoft.Testing.Platform/Helpers/NonCooperativeParentProcessListener.cs b/src/Platform/Microsoft.Testing.Platform/Helpers/NonCooperativeParentProcessListener.cs index 01f094bffe..e2b95fbd16 100644 --- a/src/Platform/Microsoft.Testing.Platform/Helpers/NonCooperativeParentProcessListener.cs +++ b/src/Platform/Microsoft.Testing.Platform/Helpers/NonCooperativeParentProcessListener.cs @@ -35,11 +35,11 @@ private void SubscribeToParentProcess() { // If we fail the process is already gone, so we can just exit. // The first check is already done inside the command line parser. - _environment.Exit(ExitCodes.DependentProcessExited); + _environment.Exit((int)ExitCodes.DependentProcessExited); } } - private void ParentProcess_Exited(object? sender, EventArgs e) => _environment.Exit(ExitCodes.DependentProcessExited); + private void ParentProcess_Exited(object? sender, EventArgs e) => _environment.Exit((int)ExitCodes.DependentProcessExited); public void Dispose() => _parentProcess?.Dispose(); } diff --git a/src/Platform/Microsoft.Testing.Platform/Hosts/CommonTestHost.cs b/src/Platform/Microsoft.Testing.Platform/Hosts/CommonTestHost.cs index 6726d52e73..76e6d2ac19 100644 --- a/src/Platform/Microsoft.Testing.Platform/Hosts/CommonTestHost.cs +++ b/src/Platform/Microsoft.Testing.Platform/Hosts/CommonTestHost.cs @@ -30,7 +30,7 @@ public async Task RunAsync() { CancellationToken testApplicationCancellationToken = ServiceProvider.GetTestApplicationCancellationTokenSource().CancellationToken; - int exitCode = ExitCodes.GenericFailure; + int exitCode = (int)ExitCodes.GenericFailure; IPlatformOpenTelemetryService? platformOTelService = null; IPlatformActivity? activity = null; try @@ -45,7 +45,7 @@ public async Task RunAsync() if (testApplicationCancellationToken.IsCancellationRequested) { - exitCode = ExitCodes.TestSessionAborted; + exitCode = (int)ExitCodes.TestSessionAborted; } return exitCode; @@ -59,7 +59,7 @@ public async Task RunAsync() exitCode = isValidProtocol ? await RunTestAppAsync(platformOTelService, testApplicationCancellationToken).ConfigureAwait(false) - : ExitCodes.IncompatibleProtocolVersion; + : (int)ExitCodes.IncompatibleProtocolVersion; } finally { @@ -90,7 +90,7 @@ public async Task RunAsync() if (testApplicationCancellationToken.IsCancellationRequested) { - exitCode = ExitCodes.TestSessionAborted; + exitCode = (int)ExitCodes.TestSessionAborted; } return exitCode; diff --git a/src/Platform/Microsoft.Testing.Platform/Hosts/ConsoleTestHost.cs b/src/Platform/Microsoft.Testing.Platform/Hosts/ConsoleTestHost.cs index 4c52364b4e..53db9b6726 100644 --- a/src/Platform/Microsoft.Testing.Platform/Hosts/ConsoleTestHost.cs +++ b/src/Platform/Microsoft.Testing.Platform/Hosts/ConsoleTestHost.cs @@ -108,7 +108,7 @@ await ExecuteRequestAsync( { requestExecuteStop ??= _clock.UtcNow; - exitCode = ExitCodes.TestSessionAborted; + exitCode = (int)ExitCodes.TestSessionAborted; await _logger.LogInformationAsync("Test session canceled.").ConfigureAwait(false); } finally @@ -128,7 +128,7 @@ await ExecuteRequestAsync( { TelemetryProperties.RequestProperties.AdapterLoadStop, adapterLoadStop }, { TelemetryProperties.RequestProperties.RequestExecuteStart, requestExecuteStart }, { TelemetryProperties.RequestProperties.RequestExecuteStop, requestExecuteStop }, - { TelemetryProperties.HostProperties.ExitCodePropertyName, cancellationToken.IsCancellationRequested ? ExitCodes.TestSessionAborted : exitCode.ToString(CultureInfo.InvariantCulture) }, + { TelemetryProperties.HostProperties.ExitCodePropertyName, cancellationToken.IsCancellationRequested ? (int)ExitCodes.TestSessionAborted : exitCode.ToString(CultureInfo.InvariantCulture) }, }; if (statistics is not null) diff --git a/src/Platform/Microsoft.Testing.Platform/Hosts/ServerTestHost.cs b/src/Platform/Microsoft.Testing.Platform/Hosts/ServerTestHost.cs index 7731b8383e..3944911372 100644 --- a/src/Platform/Microsoft.Testing.Platform/Hosts/ServerTestHost.cs +++ b/src/Platform/Microsoft.Testing.Platform/Hosts/ServerTestHost.cs @@ -170,8 +170,8 @@ or InvalidOperationException // If the global cancellation is called together with the server closing one the server exited gracefully. return !cancellationToken.IsCancellationRequested && _serverClosingTokenSource.IsCancellationRequested - ? ExitCodes.Success - : ExitCodes.TestSessionAborted; + ? (int)ExitCodes.Success + : (int)ExitCodes.TestSessionAborted; } /// diff --git a/src/Platform/Microsoft.Testing.Platform/Hosts/TestHostBuilder.cs b/src/Platform/Microsoft.Testing.Platform/Hosts/TestHostBuilder.cs index 466be943f2..68e20c6451 100644 --- a/src/Platform/Microsoft.Testing.Platform/Hosts/TestHostBuilder.cs +++ b/src/Platform/Microsoft.Testing.Platform/Hosts/TestHostBuilder.cs @@ -261,7 +261,7 @@ public async Task BuildAsync( builderActivity?.SetTag(BuilderHostTypeOTelKey, nameof(InformativeCommandLineHost)); builderActivity?.Dispose(); - return new InformativeCommandLineHost(ExitCodes.InvalidCommandLine, serviceProvider); + return new InformativeCommandLineHost((int)ExitCodes.InvalidCommandLine, serviceProvider); } // Register as ICommandLineOptions. diff --git a/src/Platform/Microsoft.Testing.Platform/Hosts/TestHostControllersTestHost.cs b/src/Platform/Microsoft.Testing.Platform/Hosts/TestHostControllersTestHost.cs index 0103e1fefc..3257abd887 100644 --- a/src/Platform/Microsoft.Testing.Platform/Hosts/TestHostControllersTestHost.cs +++ b/src/Platform/Microsoft.Testing.Platform/Hosts/TestHostControllersTestHost.cs @@ -221,7 +221,7 @@ protected override async Task InternalRunAsync(CancellationToken cancellati await outputDevice.DisplayAsync(this, new ErrorMessageOutputDeviceData(displayErrorMessageBuilder.ToString()), cancellationToken).ConfigureAwait(false); await _logger.LogErrorAsync(logErrorMessageBuilder.ToString()).ConfigureAwait(false); - return ExitCodes.InvalidPlatformSetup; + return (int)ExitCodes.InvalidPlatformSetup; } foreach (EnvironmentVariable envVar in environmentVariables.GetAll()) @@ -346,17 +346,17 @@ protected override async Task InternalRunAsync(CancellationToken cancellati // If we have a process in the middle between the test host controller and the test host process we need to keep it into account. exitCode = testHostProcess.ExitCode; - if (exitCode == ExitCodes.Success && cancellationToken.IsCancellationRequested) + if (exitCode == (int)ExitCodes.Success && cancellationToken.IsCancellationRequested) { // In case of cancellation, only alter exit code if it was success. // If there is another exit code indicating another failure, we prefer it over the cancellation. - exitCode = ExitCodes.TestSessionAborted; + exitCode = (int)ExitCodes.TestSessionAborted; } else if (!testHostProcessInformation.HasExitedGracefully || _testHostExitCodeReceived != testHostProcess.ExitCode) { await outputDevice.DisplayAsync(this, new ErrorMessageOutputDeviceData(string.Format(CultureInfo.InvariantCulture, PlatformResources.TestProcessDidNotExitGracefullyErrorMessage, testHostProcess.ExitCode)), cancellationToken).ConfigureAwait(false); - exitCode = ExitCodes.TestHostProcessExitedNonGracefully; + exitCode = (int)ExitCodes.TestHostProcessExitedNonGracefully; } await _logger.LogInformationAsync($"TestHostControllersTestHost ended with exit code '{exitCode}' (real test host exit code '{testHostProcess.ExitCode}') in '{consoleRunStarted.Elapsed}'").ConfigureAwait(false); diff --git a/src/Platform/Microsoft.Testing.Platform/Hosts/TestHostOchestratorHost.cs b/src/Platform/Microsoft.Testing.Platform/Hosts/TestHostOchestratorHost.cs index 0b8dc8105b..45db019568 100644 --- a/src/Platform/Microsoft.Testing.Platform/Hosts/TestHostOchestratorHost.cs +++ b/src/Platform/Microsoft.Testing.Platform/Hosts/TestHostOchestratorHost.cs @@ -46,7 +46,7 @@ public async Task RunAsync() catch (OperationCanceledException) when (applicationCancellationToken.CancellationToken.IsCancellationRequested) { // We do nothing we're canceling - exitCode = ExitCodes.TestSessionAborted; + exitCode = (int)ExitCodes.TestSessionAborted; } return exitCode; diff --git a/src/Platform/Microsoft.Testing.Platform/Hosts/ToolsTestHost.cs b/src/Platform/Microsoft.Testing.Platform/Hosts/ToolsTestHost.cs index 1a664a7956..f087a9e50c 100644 --- a/src/Platform/Microsoft.Testing.Platform/Hosts/ToolsTestHost.cs +++ b/src/Platform/Microsoft.Testing.Platform/Hosts/ToolsTestHost.cs @@ -63,20 +63,20 @@ public async Task RunAsync() { await _outputDevice.DisplayAsync(this, new ErrorMessageOutputDeviceData(unknownOptionsError), cancellationToken).ConfigureAwait(false); console.WriteLine(); - return ExitCodes.InvalidCommandLine; + return (int)ExitCodes.InvalidCommandLine; } if (ExtensionArgumentArityAreInvalid(out string? arityErrors, tool)) { await _outputDevice.DisplayAsync(this, new ErrorMessageOutputDeviceData(arityErrors), cancellationToken).ConfigureAwait(false); - return ExitCodes.InvalidCommandLine; + return (int)ExitCodes.InvalidCommandLine; } ValidationResult optionsArgumentsValidationResult = await ValidateOptionsArgumentsAsync(tool).ConfigureAwait(false); if (!optionsArgumentsValidationResult.IsValid) { await _outputDevice.DisplayAsync(this, new ErrorMessageOutputDeviceData(optionsArgumentsValidationResult.ErrorMessage), cancellationToken).ConfigureAwait(false); - return ExitCodes.InvalidCommandLine; + return (int)ExitCodes.InvalidCommandLine; } return await tool.RunAsync(cancellationToken).ConfigureAwait(false); @@ -85,7 +85,7 @@ public async Task RunAsync() await _outputDevice.DisplayAsync(this, new ErrorMessageOutputDeviceData($"Tool '{toolNameToRun}' not found in the list of registered tools."), cancellationToken).ConfigureAwait(false); await _commandLineHandler.PrintHelpAsync(_outputDevice, null, cancellationToken).ConfigureAwait(false); - return ExitCodes.InvalidCommandLine; + return (int)ExitCodes.InvalidCommandLine; } private bool UnknownOptions([NotNullWhen(true)] out string? error, ITool tool) diff --git a/src/Platform/Microsoft.Testing.Platform/IPC/NamedPipeClient.cs b/src/Platform/Microsoft.Testing.Platform/IPC/NamedPipeClient.cs index 4f840d3235..0d60487de1 100644 --- a/src/Platform/Microsoft.Testing.Platform/IPC/NamedPipeClient.cs +++ b/src/Platform/Microsoft.Testing.Platform/IPC/NamedPipeClient.cs @@ -181,7 +181,7 @@ public async Task RequestReplyAsync(TRequest req // This is especially important for 'dotnet test', where the user can simply kill the dotnet.exe process themselves. // In that case, we want the MTP process to also die. // Exit code 1 indicates abnormal termination due to IPC connection loss. - _environment.Exit(ExitCodes.GenericFailure); + _environment.Exit((int)ExitCodes.GenericFailure); } // Reset the current chunk size diff --git a/src/Platform/Microsoft.Testing.Platform/Services/TestApplicationResult.cs b/src/Platform/Microsoft.Testing.Platform/Services/TestApplicationResult.cs index 80dbd77cc6..6cec2d677a 100644 --- a/src/Platform/Microsoft.Testing.Platform/Services/TestApplicationResult.cs +++ b/src/Platform/Microsoft.Testing.Platform/Services/TestApplicationResult.cs @@ -129,16 +129,16 @@ public Task ConsumeAsync(IDataProducer dataProducer, IData value, CancellationTo public int GetProcessExitCode() { - int exitCode = ExitCodes.Success; - exitCode = exitCode == ExitCodes.Success && _policiesService.IsMaxFailedTestsTriggered ? ExitCodes.TestExecutionStoppedForMaxFailedTests : exitCode; - exitCode = exitCode == ExitCodes.Success && _testAdapterTestSessionFailure ? ExitCodes.TestAdapterTestSessionFailure : exitCode; - exitCode = exitCode == ExitCodes.Success && _failedTestsCount > 0 ? ExitCodes.AtLeastOneTestFailed : exitCode; - exitCode = exitCode == ExitCodes.Success && _policiesService.IsAbortTriggered ? ExitCodes.TestSessionAborted : exitCode; - exitCode = exitCode == ExitCodes.Success && _totalRanTests == 0 ? ExitCodes.ZeroTests : exitCode; + int exitCode = (int)ExitCodes.Success; + exitCode = exitCode == (int)ExitCodes.Success && _policiesService.IsMaxFailedTestsTriggered ? (int)ExitCodes.TestExecutionStoppedForMaxFailedTests : exitCode; + exitCode = exitCode == (int)ExitCodes.Success && _testAdapterTestSessionFailure ? (int)ExitCodes.TestAdapterTestSessionFailure : exitCode; + exitCode = exitCode == (int)ExitCodes.Success && _failedTestsCount > 0 ? (int)ExitCodes.AtLeastOneTestFailed : exitCode; + exitCode = exitCode == (int)ExitCodes.Success && _policiesService.IsAbortTriggered ? (int)ExitCodes.TestSessionAborted : exitCode; + exitCode = exitCode == (int)ExitCodes.Success && _totalRanTests == 0 ? (int)ExitCodes.ZeroTests : exitCode; if (_commandLineOptions.TryGetOptionArgumentList(PlatformCommandLineProvider.MinimumExpectedTestsOptionKey, out string[]? argumentList)) { - exitCode = exitCode == ExitCodes.Success && _totalRanTests < int.Parse(argumentList[0], CultureInfo.InvariantCulture) ? ExitCodes.MinimumExpectedTestsPolicyViolation : exitCode; + exitCode = exitCode == (int)ExitCodes.Success && _totalRanTests < int.Parse(argumentList[0], CultureInfo.InvariantCulture) ? (int)ExitCodes.MinimumExpectedTestsPolicyViolation : exitCode; } // If the user has specified the IgnoreExitCode, then we don't want to return a non-zero exit code if the exit code matches the one specified. @@ -155,7 +155,7 @@ public int GetProcessExitCode() { if (exitCodeToIgnore.Split(';').Any(code => int.TryParse(code, out int parsedExitCode) && parsedExitCode == exitCode)) { - exitCode = ExitCodes.Success; + exitCode = (int)ExitCodes.Success; } } diff --git a/test/IntegrationTests/MSTest.Acceptance.IntegrationTests/SdkTests.cs b/test/IntegrationTests/MSTest.Acceptance.IntegrationTests/SdkTests.cs index ef56b1ce7f..c1e0ca4480 100644 --- a/test/IntegrationTests/MSTest.Acceptance.IntegrationTests/SdkTests.cs +++ b/test/IntegrationTests/MSTest.Acceptance.IntegrationTests/SdkTests.cs @@ -226,7 +226,7 @@ public async Task RunTests_With_MSTestRunner_Standalone_Selectively_Enabled_Exte testHostResult.AssertOutputContainsSummary(0, 1, 0); testHostResult = await testHost.ExecuteAsync(command: invalidCommandLineArg, cancellationToken: TestContext.CancellationToken); - Assert.AreEqual(ExitCodes.InvalidCommandLine, testHostResult.ExitCode); + Assert.AreEqual((int)ExitCodes.InvalidCommandLine, testHostResult.ExitCode); } } @@ -283,7 +283,7 @@ public async Task RunTests_With_MSTestRunner_Standalone_Enable_Default_Extension } else { - Assert.AreEqual(ExitCodes.InvalidCommandLine, testHostResult.ExitCode); + Assert.AreEqual((int)ExitCodes.InvalidCommandLine, testHostResult.ExitCode); } } } diff --git a/test/IntegrationTests/Microsoft.Testing.Platform.Acceptance.IntegrationTests/ConsoleTests.cs b/test/IntegrationTests/Microsoft.Testing.Platform.Acceptance.IntegrationTests/ConsoleTests.cs index 377578b012..ce89522f35 100644 --- a/test/IntegrationTests/Microsoft.Testing.Platform.Acceptance.IntegrationTests/ConsoleTests.cs +++ b/test/IntegrationTests/Microsoft.Testing.Platform.Acceptance.IntegrationTests/ConsoleTests.cs @@ -49,7 +49,7 @@ public async Task ProgressAndControllerOutputAreNotFullySynchronizedAcrossProces $"\"{testHost.FullName}\" --ignore-exit-code 8", cancellationToken: TestContext.CancellationToken); - Assert.AreEqual(ExitCodes.Success, exitCode); + Assert.AreEqual((int)ExitCodes.Success, exitCode); Assert.Contains("Slowest 10 tests", commandLine.StandardOutput); } diff --git a/test/IntegrationTests/Microsoft.Testing.Platform.Acceptance.IntegrationTests/ExecutionRequestCompleteTests.cs b/test/IntegrationTests/Microsoft.Testing.Platform.Acceptance.IntegrationTests/ExecutionRequestCompleteTests.cs index 62af57f232..68bd40ff52 100644 --- a/test/IntegrationTests/Microsoft.Testing.Platform.Acceptance.IntegrationTests/ExecutionRequestCompleteTests.cs +++ b/test/IntegrationTests/Microsoft.Testing.Platform.Acceptance.IntegrationTests/ExecutionRequestCompleteTests.cs @@ -16,7 +16,7 @@ public async Task Exec_Honor_Request_Complete(string tfm) var stopwatch = Stopwatch.StartNew(); TestHostResult testHostResult = await testHost.ExecuteAsync(cancellationToken: TestContext.CancellationToken); stopwatch.Stop(); - Assert.AreEqual(ExitCodes.Success, testHostResult.ExitCode); + Assert.AreEqual((int)ExitCodes.Success, testHostResult.ExitCode); Assert.IsGreaterThan(3, stopwatch.Elapsed.TotalSeconds); } diff --git a/test/IntegrationTests/Microsoft.Testing.Platform.Acceptance.IntegrationTests/Helpers/AcceptanceAssert.cs b/test/IntegrationTests/Microsoft.Testing.Platform.Acceptance.IntegrationTests/Helpers/AcceptanceAssert.cs index 88973c7c5d..173eab5be7 100644 --- a/test/IntegrationTests/Microsoft.Testing.Platform.Acceptance.IntegrationTests/Helpers/AcceptanceAssert.cs +++ b/test/IntegrationTests/Microsoft.Testing.Platform.Acceptance.IntegrationTests/Helpers/AcceptanceAssert.cs @@ -1,6 +1,8 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. +using Microsoft.Testing.Platform.Helpers; + namespace Microsoft.Testing.Platform.Acceptance.IntegrationTests.Helpers; internal static class AcceptanceAssert @@ -8,9 +10,15 @@ internal static class AcceptanceAssert public static void AssertExitCodeIs(this TestHostResult testHostResult, int exitCode, [CallerMemberName] string? callerMemberName = null, [CallerFilePath] string? callerFilePath = null, [CallerLineNumber] int callerLineNumber = 0) => Assert.AreEqual(exitCode, testHostResult.ExitCode, GenerateFailedAssertionMessage(testHostResult, callerMemberName: callerMemberName, callerFilePath: callerFilePath, callerLineNumber: callerLineNumber)); + public static void AssertExitCodeIs(this TestHostResult testHostResult, ExitCodes exitCode, [CallerMemberName] string? callerMemberName = null, [CallerFilePath] string? callerFilePath = null, [CallerLineNumber] int callerLineNumber = 0) + => AssertExitCodeIs(testHostResult, (int)exitCode, callerMemberName, callerFilePath, callerLineNumber); + public static void AssertExitCodeIsNot(this TestHostResult testHostResult, int exitCode, [CallerMemberName] string? callerMemberName = null, [CallerFilePath] string? callerFilePath = null, [CallerLineNumber] int callerLineNumber = 0) => Assert.AreNotEqual(exitCode, testHostResult.ExitCode, GenerateFailedAssertionMessage(testHostResult, callerMemberName: callerMemberName, callerFilePath: callerFilePath, callerLineNumber: callerLineNumber)); + public static void AssertExitCodeIsNot(this TestHostResult testHostResult, ExitCodes exitCode, [CallerMemberName] string? callerMemberName = null, [CallerFilePath] string? callerFilePath = null, [CallerLineNumber] int callerLineNumber = 0) + => AssertExitCodeIsNot(testHostResult, (int)exitCode, callerMemberName, callerFilePath, callerLineNumber); + /// /// Ensure that the output matches the given pattern. The pattern can use `*` to mean any character, it is internally replaced by `.*` and matched as regex. /// If you have lines in the pattern that are optional then you can output `###SKIP###` and the line in pattern will be skipped. This allows matching lines that are present only when some condition is met. @@ -90,9 +98,15 @@ public static void AssertOutputContains(this TestHostResult testHostResult, stri public static void AssertExitCodeIs(this DotnetMuxerResult testHostResult, int exitCode, [CallerMemberName] string? callerMemberName = null, [CallerFilePath] string? callerFilePath = null, [CallerLineNumber] int callerLineNumber = 0) => Assert.AreEqual(exitCode, testHostResult.ExitCode, GenerateFailedAssertionMessage(testHostResult, callerMemberName: callerMemberName, callerFilePath: callerFilePath, callerLineNumber: callerLineNumber)); + public static void AssertExitCodeIs(this DotnetMuxerResult testHostResult, ExitCodes exitCode, [CallerMemberName] string? callerMemberName = null, [CallerFilePath] string? callerFilePath = null, [CallerLineNumber] int callerLineNumber = 0) + => AssertExitCodeIs(testHostResult, (int)exitCode, callerMemberName, callerFilePath, callerLineNumber); + public static void AssertExitCodeIsNot(this DotnetMuxerResult testHostResult, int exitCode, [CallerMemberName] string? callerMemberName = null, [CallerFilePath] string? callerFilePath = null, [CallerLineNumber] int callerLineNumber = 0) => Assert.AreNotEqual(exitCode, testHostResult.ExitCode, GenerateFailedAssertionMessage(testHostResult, callerMemberName: callerMemberName, callerFilePath: callerFilePath, callerLineNumber: callerLineNumber)); + public static void AssertExitCodeIsNot(this DotnetMuxerResult testHostResult, ExitCodes exitCode, [CallerMemberName] string? callerMemberName = null, [CallerFilePath] string? callerFilePath = null, [CallerLineNumber] int callerLineNumber = 0) + => AssertExitCodeIsNot(testHostResult, (int)exitCode, callerMemberName, callerFilePath, callerLineNumber); + public static void AssertOutputContains(this DotnetMuxerResult dotnetMuxerResult, string value, [CallerMemberName] string? callerMemberName = null, [CallerFilePath] string? callerFilePath = null, [CallerLineNumber] int callerLineNumber = 0) => Assert.Contains(value, dotnetMuxerResult.StandardOutput, StringComparison.Ordinal, GenerateFailedAssertionMessage(dotnetMuxerResult, callerMemberName: callerMemberName, callerFilePath: callerFilePath, callerLineNumber: callerLineNumber)); diff --git a/test/IntegrationTests/Microsoft.Testing.Platform.Acceptance.IntegrationTests/MSBuildTests.GenerateEntryPoint.cs b/test/IntegrationTests/Microsoft.Testing.Platform.Acceptance.IntegrationTests/MSBuildTests.GenerateEntryPoint.cs index dfb13db03f..e0a149de36 100644 --- a/test/IntegrationTests/Microsoft.Testing.Platform.Acceptance.IntegrationTests/MSBuildTests.GenerateEntryPoint.cs +++ b/test/IntegrationTests/Microsoft.Testing.Platform.Acceptance.IntegrationTests/MSBuildTests.GenerateEntryPoint.cs @@ -171,7 +171,7 @@ private async Task GenerateAndVerifyLanguageSpecificEntryPointAsync(string asset testHost = TestInfrastructure.TestHost.LocateFrom(testAsset.TargetAssetPath, AssetName, tfm, rid: RID, verb: verb, buildConfiguration: compilationMode); testHostResult = await testHost.ExecuteAsync(cancellationToken: TestContext.CancellationToken); - Assert.AreEqual(ExitCodes.Success, testHostResult.ExitCode); + Assert.AreEqual((int)ExitCodes.Success, testHostResult.ExitCode); Assert.Contains("Passed!", testHostResult.StandardOutput); } diff --git a/test/UnitTests/Microsoft.Testing.Platform.UnitTests/ServerMode/ServerTests.cs b/test/UnitTests/Microsoft.Testing.Platform.UnitTests/ServerMode/ServerTests.cs index bfc8c49a4c..10e39690e2 100644 --- a/test/UnitTests/Microsoft.Testing.Platform.UnitTests/ServerMode/ServerTests.cs +++ b/test/UnitTests/Microsoft.Testing.Platform.UnitTests/ServerMode/ServerTests.cs @@ -48,7 +48,7 @@ public async Task ServerCanBeStartedAndAborted_TcpIp() ITestApplicationCancellationTokenSource stopService = testApplication.ServiceProvider.GetTestApplicationCancellationTokenSource(); stopService.Cancel(); - Assert.AreEqual(ExitCodes.TestSessionAborted, await serverTask); + Assert.AreEqual((int)ExitCodes.TestSessionAborted, await serverTask); } [TestMethod] diff --git a/test/UnitTests/Microsoft.Testing.Platform.UnitTests/Services/TestApplicationResultTests.cs b/test/UnitTests/Microsoft.Testing.Platform.UnitTests/Services/TestApplicationResultTests.cs index 918e2286a5..c7e4913d28 100644 --- a/test/UnitTests/Microsoft.Testing.Platform.UnitTests/Services/TestApplicationResultTests.cs +++ b/test/UnitTests/Microsoft.Testing.Platform.UnitTests/Services/TestApplicationResultTests.cs @@ -31,7 +31,7 @@ public async Task GetProcessExitCodeAsync_If_All_Skipped_Returns_ZeroTestsRan() Properties = new PropertyBag(SkippedTestNodeStateProperty.CachedInstance), }), CancellationToken.None); - Assert.AreEqual(ExitCodes.ZeroTests, _testApplicationResult.GetProcessExitCode()); + Assert.AreEqual((int)ExitCodes.ZeroTests, _testApplicationResult.GetProcessExitCode()); } [TestMethod] @@ -46,7 +46,7 @@ public async Task GetProcessExitCodeAsync_If_No_Tests_Ran_Returns_ZeroTestsRan() Properties = new PropertyBag(), }), CancellationToken.None); - Assert.AreEqual(ExitCodes.ZeroTests, _testApplicationResult.GetProcessExitCode()); + Assert.AreEqual((int)ExitCodes.ZeroTests, _testApplicationResult.GetProcessExitCode()); } [TestMethod] @@ -62,7 +62,7 @@ public async Task GetProcessExitCodeAsync_If_Failed_Tests_Returns_AtLeastOneTest Properties = new PropertyBag(testNodeStateProperty), }), CancellationToken.None); - Assert.AreEqual(ExitCodes.AtLeastOneTestFailed, _testApplicationResult.GetProcessExitCode()); + Assert.AreEqual((int)ExitCodes.AtLeastOneTestFailed, _testApplicationResult.GetProcessExitCode()); } [TestMethod] @@ -91,7 +91,7 @@ TestApplicationResult testApplicationResult Properties = new PropertyBag(), }), CancellationToken.None); - Assert.AreEqual(ExitCodes.TestSessionAborted, testApplicationResult.GetProcessExitCode()); + Assert.AreEqual((int)ExitCodes.TestSessionAborted, testApplicationResult.GetProcessExitCode()); } [TestMethod] @@ -107,7 +107,7 @@ public async Task GetProcessExitCodeAsync_If_TestAdapter_Returns_TestAdapterTest Properties = new PropertyBag(PassedTestNodeStateProperty.CachedInstance), }), CancellationToken.None); - Assert.AreEqual(ExitCodes.TestAdapterTestSessionFailure, _testApplicationResult.GetProcessExitCode()); + Assert.AreEqual((int)ExitCodes.TestAdapterTestSessionFailure, _testApplicationResult.GetProcessExitCode()); } [TestMethod] @@ -139,7 +139,7 @@ TestApplicationResult testApplicationResult Properties = new PropertyBag(InProgressTestNodeStateProperty.CachedInstance), }), CancellationToken.None); - Assert.AreEqual(ExitCodes.MinimumExpectedTestsPolicyViolation, testApplicationResult.GetProcessExitCode()); + Assert.AreEqual((int)ExitCodes.MinimumExpectedTestsPolicyViolation, testApplicationResult.GetProcessExitCode()); } [TestMethod] @@ -159,7 +159,7 @@ TestApplicationResult testApplicationResult DisplayName = "DisplayName", }), CancellationToken.None); - Assert.AreEqual(ExitCodes.ZeroTests, testApplicationResult.GetProcessExitCode()); + Assert.AreEqual((int)ExitCodes.ZeroTests, testApplicationResult.GetProcessExitCode()); } [TestMethod] @@ -180,20 +180,20 @@ TestApplicationResult testApplicationResult Properties = new PropertyBag(DiscoveredTestNodeStateProperty.CachedInstance), }), CancellationToken.None); - Assert.AreEqual(ExitCodes.Success, testApplicationResult.GetProcessExitCode()); + Assert.AreEqual((int)ExitCodes.Success, testApplicationResult.GetProcessExitCode()); } - [DataRow("8", ExitCodes.Success)] - [DataRow("8;2", ExitCodes.Success)] - [DataRow("8;", ExitCodes.Success)] - [DataRow("8;2;", ExitCodes.Success)] - [DataRow("5", ExitCodes.ZeroTests)] - [DataRow("5;7", ExitCodes.ZeroTests)] - [DataRow("5;", ExitCodes.ZeroTests)] - [DataRow("5;7;", ExitCodes.ZeroTests)] - [DataRow(";", ExitCodes.ZeroTests)] - [DataRow(null, ExitCodes.ZeroTests)] - [DataRow("", ExitCodes.ZeroTests)] + [DataRow("8", (int)ExitCodes.Success)] + [DataRow("8;2", (int)ExitCodes.Success)] + [DataRow("8;", (int)ExitCodes.Success)] + [DataRow("8;2;", (int)ExitCodes.Success)] + [DataRow("5", (int)ExitCodes.ZeroTests)] + [DataRow("5;7", (int)ExitCodes.ZeroTests)] + [DataRow("5;", (int)ExitCodes.ZeroTests)] + [DataRow("5;7;", (int)ExitCodes.ZeroTests)] + [DataRow(";", (int)ExitCodes.ZeroTests)] + [DataRow(null, (int)ExitCodes.ZeroTests)] + [DataRow("", (int)ExitCodes.ZeroTests)] [TestMethod] public void GetProcessExitCodeAsync_IgnoreExitCodes(string? argument, int expectedExitCode) {