Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduce --exit-on-process-exit #2434

Merged
merged 11 commits into from
Feb 23, 2024
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// 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.Diagnostics;
using System.Globalization;

using Microsoft.Testing.Platform.Extensions;
Expand Down Expand Up @@ -31,6 +32,7 @@ internal sealed class PlatformCommandLineProvider : ICommandLineOptionsProvider
public const string IgnoreExitCodeOptionKey = "ignore-exit-code";
public const string MinimumExpectedTestsOptionKey = "minimum-expected-tests";
public const string TestHostControllerPIDOptionKey = "internal-testhostcontroller-pid";
public const string ExitOnProcessExitOptionKey = "exit-on-process-exit";

private static readonly CommandLineOption MinimumExpectedTests = new(MinimumExpectedTestsOptionKey, "Specifies the minimum number of tests that are expected to run.", ArgumentArity.ZeroOrOne, false, isBuiltIn: true);

Expand All @@ -48,6 +50,7 @@ internal sealed class PlatformCommandLineProvider : ICommandLineOptionsProvider
MinimumExpectedTests,
new(DiscoverTestsOptionKey, PlatformResources.PlatformCommandLineDiscoverTestsOptionDescription, ArgumentArity.Zero, false, isBuiltIn: true),
new(IgnoreExitCodeOptionKey, PlatformResources.PlatformCommandLineIgnoreExitCodeOptionDescription, ArgumentArity.ExactlyOne, false, isBuiltIn: true),
new(ExitOnProcessExitOptionKey, PlatformResources.PlatformCommandLineExitOnProcessExit, ArgumentArity.ExactlyOne, false, isBuiltIn: true),

// Hidden options
new(ServerOptionKey, PlatformResources.PlatformCommandLineServerOptionDescription, ArgumentArity.Zero, true, isBuiltIn: true),
Expand Down Expand Up @@ -134,6 +137,11 @@ public Task<ValidationResult> ValidateOptionArgumentsAsync(CommandLineOption com
return ValidationResult.InvalidTask(PlatformResources.PlatformCommandLineClientHostOptionSingleArgument);
}

if (commandOption.Name == ExitOnProcessExitOptionKey && (arguments.Length != 1 || !int.TryParse(arguments[0], out int _)))
{
return ValidationResult.InvalidTask(string.Format(CultureInfo.InvariantCulture, PlatformResources.PlatformCommandLineExitOnProcessExitSingleArgument, ExitOnProcessExitOptionKey));
}

// Now validate the minimum expected tests option
return IsMinimumExpectedTestsOptionValidAsync(commandOption, arguments);
}
Expand Down Expand Up @@ -177,6 +185,24 @@ public Task<ValidationResult> ValidateCommandLineOptionsAsync(ICommandLineOption
return ValidationResult.InvalidTask(PlatformResources.PlatformCommandLineMinimumExpectedTestsIncompatibleDiscoverTests);
}

if (commandLineOptions.IsOptionSet(ExitOnProcessExitOptionKey))
{
commandLineOptions.TryGetOptionArgumentList(ExitOnProcessExitOptionKey, out string[]? pid);
ArgumentGuard.IsNotNull(pid);
RoslynDebug.Assert(pid.Length == 1);
int parentProcessPid = int.Parse(pid[0], CultureInfo.InvariantCulture);
try
{
// We let the api to do the validity check before to go down the subscription path.
// If we don't fail here but we fail below means that the parent process is not there anymore and we can take it as exited.
Process.GetProcessById(parentProcessPid);
}
catch (Exception ex)
{
return ValidationResult.InvalidTask(string.Format(CultureInfo.InvariantCulture, PlatformResources.PlatformCommandLineExitOnProcessExitInvalidParentProcess, parentProcessPid, ex));
}
}

// Validation succeeded
return ValidationResult.ValidTask;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ internal static class ExitCodes
public const int ZeroTests = 8;
public const int MinimumExpectedTestsPolicyViolation = 9;
public const int TestAdapterTestSessionFailure = 10;
public const int DependentProcessExited = 11;

public static string StringifyExitCode(int exitCode) => exitCode switch
{
Expand All @@ -36,6 +37,7 @@ internal static class ExitCodes
ZeroTests => nameof(ZeroTests),
MinimumExpectedTestsPolicyViolation => nameof(MinimumExpectedTestsPolicyViolation),
TestAdapterTestSessionFailure => nameof(TestAdapterTestSessionFailure),
DependentProcessExited => nameof(DependentProcessExited),
_ => exitCode.ToString(CultureInfo.InvariantCulture),
};
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// 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.Diagnostics;
using System.Globalization;

using Microsoft.Testing.Platform.CommandLine;

namespace Microsoft.Testing.Platform.Helpers;

internal class NonCooperativeParentProcessListener : IDisposable
{
private readonly ICommandLineOptions _commandLineOptions;
private readonly IEnvironment _environment;
private Process? _parentProcess;

public NonCooperativeParentProcessListener(ICommandLineOptions commandLineOptions, IEnvironment environment)
{
_commandLineOptions = commandLineOptions;
_environment = environment;
SubscribeToParentProcess();
}

private void SubscribeToParentProcess()
{
_commandLineOptions.TryGetOptionArgumentList(PlatformCommandLineProvider.ExitOnProcessExitOptionKey, out string[]? pid);
ArgumentGuard.IsNotNull(pid);
RoslynDebug.Assert(pid.Length == 1);

try
{
_parentProcess = Process.GetProcessById(int.Parse(pid[0], CultureInfo.InvariantCulture));
_parentProcess.EnableRaisingEvents = true;
_parentProcess.Exited += ParentProcess_Exited;
}
catch (ArgumentException)
{
// 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);
return;
}
}

private void ParentProcess_Exited(object? sender, EventArgs e) => _environment.Exit(ExitCodes.DependentProcessExited);

public void Dispose() => _parentProcess?.Dispose();
}
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,15 @@ public async Task<ITestHost> BuildAsync(
serviceProvider.TryAddService(telemetryService);
AddApplicationMetadata(serviceProvider, builderMetrics);

// Subscribe to the process if the option is set.
if (commandLineOptions.IsOptionSet(PlatformCommandLineProvider.ExitOnProcessExitOptionKey))
{
NonCooperativeParentProcessListener nonCooperativeParentProcessListener = new(commandLineOptions, environment);

// Add to the service provider for cleanup.
serviceProvider.AddService(nonCooperativeParentProcessListener);
}

// ============= SETUP COMMON SERVICE USED IN ALL MODES END ===============//

// ============= SELECT AND RUN THE ACTUAL MODE ===============//
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,21 @@ This package provides the core platform and the .NET implementation of the proto
</EmbeddedResource>
</ItemGroup>

<!-- NuGet package layout -->
<!-- NuGet folders https://learn.microsoft.com/nuget/create-packages/creating-a-package#from-a-convention-based-working-directory -->
<ItemGroup>
<Content Include="buildMultiTargeting/**">
<Pack>true</Pack>
<PackagePath>buildMultiTargeting</PackagePath>
</Content>
<TfmSpecificPackageFile Include="buildTransitive/**">
<PackagePath>buildTransitive/$(TargetFramework)</PackagePath>
</TfmSpecificPackageFile>
<TfmSpecificPackageFile Include="build/**">
<PackagePath>build/$(TargetFramework)</PackagePath>
</TfmSpecificPackageFile>
</ItemGroup>

<Target Name="MoveNuGetPackage" AfterTargets="Pack" Condition=" '$(DoNotShipTestingPlatformPackage)' == 'true' ">
<ItemGroup>
<MicrosoftTestingPlatformNuGetPackage Include="$(ArtifactsNonShippingPackagesDir)Microsoft.Testing.Platform.*.nupkg" />
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -489,4 +489,14 @@ Read more about Microsoft Testing Platform telemetry: https://aka.ms/testingplat
<data name="PlatformCommandLineIgnoreExitCodeOptionDescription" xml:space="preserve">
<value>Do not report non successful exit value for specific exit codes (e.g. '--ignore-exit-code 8;9' ignore exit code 8 and 9 and will return 0 in these case)</value>
</data>
</root>
<data name="PlatformCommandLineExitOnProcessExit" xml:space="preserve">
<value>Close the test process if specific process exits. Process PID must be provided.</value>
</data>
<data name="PlatformCommandLineExitOnProcessExitSingleArgument" xml:space="preserve">
<value>'--{0}' expects a single int PID argument</value>
</data>
<data name="PlatformCommandLineExitOnProcessExitInvalidParentProcess" xml:space="preserve">
<value>Invalid process pid {0}
{1}</value>
</data>
</root>
Original file line number Diff line number Diff line change
Expand Up @@ -324,6 +324,23 @@
<target state="translated">Umožňuje zobrazit seznam dostupných testů.</target>
<note />
</trans-unit>
<trans-unit id="PlatformCommandLineExitOnProcessExit">
<source>Close the test process if specific process exits. Process PID must be provided.</source>
<target state="new">Close the test process if specific process exits. Process PID must be provided.</target>
<note />
</trans-unit>
<trans-unit id="PlatformCommandLineExitOnProcessExitInvalidParentProcess">
<source>Invalid process pid {0}
{1}</source>
<target state="new">Invalid process pid {0}
{1}</target>
<note />
</trans-unit>
<trans-unit id="PlatformCommandLineExitOnProcessExitSingleArgument">
<source>'--{0}' expects a single int PID argument</source>
<target state="new">'--{0}' expects a single int PID argument</target>
<note />
</trans-unit>
<trans-unit id="PlatformCommandLineHelpOptionDescription">
<source>Show the command line help.</source>
<target state="translated">Umožňuje zobrazit nápovědu příkazového řádku.</target>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -324,6 +324,23 @@
<target state="translated">Listen Sie verfügbare Tests auf.</target>
<note />
</trans-unit>
<trans-unit id="PlatformCommandLineExitOnProcessExit">
<source>Close the test process if specific process exits. Process PID must be provided.</source>
<target state="new">Close the test process if specific process exits. Process PID must be provided.</target>
<note />
</trans-unit>
<trans-unit id="PlatformCommandLineExitOnProcessExitInvalidParentProcess">
<source>Invalid process pid {0}
{1}</source>
<target state="new">Invalid process pid {0}
{1}</target>
<note />
</trans-unit>
<trans-unit id="PlatformCommandLineExitOnProcessExitSingleArgument">
<source>'--{0}' expects a single int PID argument</source>
<target state="new">'--{0}' expects a single int PID argument</target>
<note />
</trans-unit>
<trans-unit id="PlatformCommandLineHelpOptionDescription">
<source>Show the command line help.</source>
<target state="translated">Zeigen Sie die Hilfe zur Befehlszeile an.</target>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -324,6 +324,23 @@
<target state="translated">Enumere las pruebas disponibles.</target>
<note />
</trans-unit>
<trans-unit id="PlatformCommandLineExitOnProcessExit">
<source>Close the test process if specific process exits. Process PID must be provided.</source>
<target state="new">Close the test process if specific process exits. Process PID must be provided.</target>
<note />
</trans-unit>
<trans-unit id="PlatformCommandLineExitOnProcessExitInvalidParentProcess">
<source>Invalid process pid {0}
{1}</source>
<target state="new">Invalid process pid {0}
{1}</target>
<note />
</trans-unit>
<trans-unit id="PlatformCommandLineExitOnProcessExitSingleArgument">
<source>'--{0}' expects a single int PID argument</source>
<target state="new">'--{0}' expects a single int PID argument</target>
<note />
</trans-unit>
<trans-unit id="PlatformCommandLineHelpOptionDescription">
<source>Show the command line help.</source>
<target state="translated">Muestre la ayuda de la línea de comandos.</target>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -324,6 +324,23 @@
<target state="translated">Répertorier les tests disponibles.</target>
<note />
</trans-unit>
<trans-unit id="PlatformCommandLineExitOnProcessExit">
<source>Close the test process if specific process exits. Process PID must be provided.</source>
<target state="new">Close the test process if specific process exits. Process PID must be provided.</target>
<note />
</trans-unit>
<trans-unit id="PlatformCommandLineExitOnProcessExitInvalidParentProcess">
<source>Invalid process pid {0}
{1}</source>
<target state="new">Invalid process pid {0}
{1}</target>
<note />
</trans-unit>
<trans-unit id="PlatformCommandLineExitOnProcessExitSingleArgument">
<source>'--{0}' expects a single int PID argument</source>
<target state="new">'--{0}' expects a single int PID argument</target>
<note />
</trans-unit>
<trans-unit id="PlatformCommandLineHelpOptionDescription">
<source>Show the command line help.</source>
<target state="translated">Afficher l’aide de la ligne de commande.</target>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -324,6 +324,23 @@
<target state="translated">Elenca i test disponibili.</target>
<note />
</trans-unit>
<trans-unit id="PlatformCommandLineExitOnProcessExit">
<source>Close the test process if specific process exits. Process PID must be provided.</source>
<target state="new">Close the test process if specific process exits. Process PID must be provided.</target>
<note />
</trans-unit>
<trans-unit id="PlatformCommandLineExitOnProcessExitInvalidParentProcess">
<source>Invalid process pid {0}
{1}</source>
<target state="new">Invalid process pid {0}
{1}</target>
<note />
</trans-unit>
<trans-unit id="PlatformCommandLineExitOnProcessExitSingleArgument">
<source>'--{0}' expects a single int PID argument</source>
<target state="new">'--{0}' expects a single int PID argument</target>
<note />
</trans-unit>
<trans-unit id="PlatformCommandLineHelpOptionDescription">
<source>Show the command line help.</source>
<target state="translated">Mostra la Guida della riga di comando.</target>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -324,6 +324,23 @@
<target state="translated">使用可能なテストを一覧表示します。</target>
<note />
</trans-unit>
<trans-unit id="PlatformCommandLineExitOnProcessExit">
<source>Close the test process if specific process exits. Process PID must be provided.</source>
<target state="new">Close the test process if specific process exits. Process PID must be provided.</target>
<note />
</trans-unit>
<trans-unit id="PlatformCommandLineExitOnProcessExitInvalidParentProcess">
<source>Invalid process pid {0}
{1}</source>
<target state="new">Invalid process pid {0}
{1}</target>
<note />
</trans-unit>
<trans-unit id="PlatformCommandLineExitOnProcessExitSingleArgument">
<source>'--{0}' expects a single int PID argument</source>
<target state="new">'--{0}' expects a single int PID argument</target>
<note />
</trans-unit>
<trans-unit id="PlatformCommandLineHelpOptionDescription">
<source>Show the command line help.</source>
<target state="translated">コマンド ラインヘルプを表示します。</target>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -324,6 +324,23 @@
<target state="translated">사용 가능한 테스트를 나열합니다.</target>
<note />
</trans-unit>
<trans-unit id="PlatformCommandLineExitOnProcessExit">
<source>Close the test process if specific process exits. Process PID must be provided.</source>
<target state="new">Close the test process if specific process exits. Process PID must be provided.</target>
<note />
</trans-unit>
<trans-unit id="PlatformCommandLineExitOnProcessExitInvalidParentProcess">
<source>Invalid process pid {0}
{1}</source>
<target state="new">Invalid process pid {0}
{1}</target>
<note />
</trans-unit>
<trans-unit id="PlatformCommandLineExitOnProcessExitSingleArgument">
<source>'--{0}' expects a single int PID argument</source>
<target state="new">'--{0}' expects a single int PID argument</target>
<note />
</trans-unit>
<trans-unit id="PlatformCommandLineHelpOptionDescription">
<source>Show the command line help.</source>
<target state="translated">명령줄 도움말을 표시합니다.</target>
Expand Down
Loading
Loading