Skip to content
Draft
Show file tree
Hide file tree
Changes from all 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
Expand Up @@ -2,6 +2,7 @@
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using Microsoft.Testing.Extensions.Diagnostics.Resources;
using Microsoft.Testing.Platform;
using Microsoft.Testing.Platform.CommandLine;
using Microsoft.Testing.Platform.Extensions.Messages;
using Microsoft.Testing.Platform.Extensions.OutputDevice;
Expand All @@ -16,7 +17,7 @@
{
private readonly ICommandLineOptions _commandLineOptions;
private readonly IMessageBus _messageBus;
private readonly IOutputDevice _outputDisplay;

Check failure on line 20 in src/Platform/Microsoft.Testing.Extensions.CrashDump/CrashDumpProcessLifetimeHandler.cs

View check run for this annotation

Azure Pipelines / microsoft.testfx (Build MacOS Release)

src/Platform/Microsoft.Testing.Extensions.CrashDump/CrashDumpProcessLifetimeHandler.cs#L20

src/Platform/Microsoft.Testing.Extensions.CrashDump/CrashDumpProcessLifetimeHandler.cs(20,36): error IDE0052: (NETCORE_ENGINEERING_TELEMETRY=Build) Private member 'CrashDumpProcessLifetimeHandler._outputDisplay' can be removed as the value assigned to it is never read (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0052)

Check failure on line 20 in src/Platform/Microsoft.Testing.Extensions.CrashDump/CrashDumpProcessLifetimeHandler.cs

View check run for this annotation

Azure Pipelines / microsoft.testfx (Build MacOS Release)

src/Platform/Microsoft.Testing.Extensions.CrashDump/CrashDumpProcessLifetimeHandler.cs#L20

src/Platform/Microsoft.Testing.Extensions.CrashDump/CrashDumpProcessLifetimeHandler.cs(20,36): error IDE0052: (NETCORE_ENGINEERING_TELEMETRY=Build) Private member 'CrashDumpProcessLifetimeHandler._outputDisplay' can be removed as the value assigned to it is never read (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0052)

Check failure on line 20 in src/Platform/Microsoft.Testing.Extensions.CrashDump/CrashDumpProcessLifetimeHandler.cs

View check run for this annotation

Azure Pipelines / microsoft.testfx (Build MacOS Release)

src/Platform/Microsoft.Testing.Extensions.CrashDump/CrashDumpProcessLifetimeHandler.cs#L20

src/Platform/Microsoft.Testing.Extensions.CrashDump/CrashDumpProcessLifetimeHandler.cs(20,36): error IDE0052: (NETCORE_ENGINEERING_TELEMETRY=Build) Private member 'CrashDumpProcessLifetimeHandler._outputDisplay' can be removed as the value assigned to it is never read (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0052)

Check failure on line 20 in src/Platform/Microsoft.Testing.Extensions.CrashDump/CrashDumpProcessLifetimeHandler.cs

View check run for this annotation

Azure Pipelines / microsoft.testfx (Build MacOS Debug)

src/Platform/Microsoft.Testing.Extensions.CrashDump/CrashDumpProcessLifetimeHandler.cs#L20

src/Platform/Microsoft.Testing.Extensions.CrashDump/CrashDumpProcessLifetimeHandler.cs(20,36): error IDE0052: (NETCORE_ENGINEERING_TELEMETRY=Build) Private member 'CrashDumpProcessLifetimeHandler._outputDisplay' can be removed as the value assigned to it is never read (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0052)

Check failure on line 20 in src/Platform/Microsoft.Testing.Extensions.CrashDump/CrashDumpProcessLifetimeHandler.cs

View check run for this annotation

Azure Pipelines / microsoft.testfx (Build MacOS Debug)

src/Platform/Microsoft.Testing.Extensions.CrashDump/CrashDumpProcessLifetimeHandler.cs#L20

src/Platform/Microsoft.Testing.Extensions.CrashDump/CrashDumpProcessLifetimeHandler.cs(20,36): error IDE0052: (NETCORE_ENGINEERING_TELEMETRY=Build) Private member 'CrashDumpProcessLifetimeHandler._outputDisplay' can be removed as the value assigned to it is never read (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0052)

Check failure on line 20 in src/Platform/Microsoft.Testing.Extensions.CrashDump/CrashDumpProcessLifetimeHandler.cs

View check run for this annotation

Azure Pipelines / microsoft.testfx (Build MacOS Debug)

src/Platform/Microsoft.Testing.Extensions.CrashDump/CrashDumpProcessLifetimeHandler.cs#L20

src/Platform/Microsoft.Testing.Extensions.CrashDump/CrashDumpProcessLifetimeHandler.cs(20,36): error IDE0052: (NETCORE_ENGINEERING_TELEMETRY=Build) Private member 'CrashDumpProcessLifetimeHandler._outputDisplay' can be removed as the value assigned to it is never read (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0052)

Check failure on line 20 in src/Platform/Microsoft.Testing.Extensions.CrashDump/CrashDumpProcessLifetimeHandler.cs

View check run for this annotation

Azure Pipelines / microsoft.testfx (Build Linux Release)

src/Platform/Microsoft.Testing.Extensions.CrashDump/CrashDumpProcessLifetimeHandler.cs#L20

src/Platform/Microsoft.Testing.Extensions.CrashDump/CrashDumpProcessLifetimeHandler.cs(20,36): error IDE0052: (NETCORE_ENGINEERING_TELEMETRY=Build) Private member 'CrashDumpProcessLifetimeHandler._outputDisplay' can be removed as the value assigned to it is never read (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0052)

Check failure on line 20 in src/Platform/Microsoft.Testing.Extensions.CrashDump/CrashDumpProcessLifetimeHandler.cs

View check run for this annotation

Azure Pipelines / microsoft.testfx (Build Linux Release)

src/Platform/Microsoft.Testing.Extensions.CrashDump/CrashDumpProcessLifetimeHandler.cs#L20

src/Platform/Microsoft.Testing.Extensions.CrashDump/CrashDumpProcessLifetimeHandler.cs(20,36): error IDE0052: (NETCORE_ENGINEERING_TELEMETRY=Build) Private member 'CrashDumpProcessLifetimeHandler._outputDisplay' can be removed as the value assigned to it is never read (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0052)

Check failure on line 20 in src/Platform/Microsoft.Testing.Extensions.CrashDump/CrashDumpProcessLifetimeHandler.cs

View check run for this annotation

Azure Pipelines / microsoft.testfx (Build Linux Release)

src/Platform/Microsoft.Testing.Extensions.CrashDump/CrashDumpProcessLifetimeHandler.cs#L20

src/Platform/Microsoft.Testing.Extensions.CrashDump/CrashDumpProcessLifetimeHandler.cs(20,36): error IDE0052: (NETCORE_ENGINEERING_TELEMETRY=Build) Private member 'CrashDumpProcessLifetimeHandler._outputDisplay' can be removed as the value assigned to it is never read (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0052)

Check failure on line 20 in src/Platform/Microsoft.Testing.Extensions.CrashDump/CrashDumpProcessLifetimeHandler.cs

View check run for this annotation

Azure Pipelines / microsoft.testfx (Build Linux Debug)

src/Platform/Microsoft.Testing.Extensions.CrashDump/CrashDumpProcessLifetimeHandler.cs#L20

src/Platform/Microsoft.Testing.Extensions.CrashDump/CrashDumpProcessLifetimeHandler.cs(20,36): error IDE0052: (NETCORE_ENGINEERING_TELEMETRY=Build) Private member 'CrashDumpProcessLifetimeHandler._outputDisplay' can be removed as the value assigned to it is never read (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0052)

Check failure on line 20 in src/Platform/Microsoft.Testing.Extensions.CrashDump/CrashDumpProcessLifetimeHandler.cs

View check run for this annotation

Azure Pipelines / microsoft.testfx

src/Platform/Microsoft.Testing.Extensions.CrashDump/CrashDumpProcessLifetimeHandler.cs#L20

src/Platform/Microsoft.Testing.Extensions.CrashDump/CrashDumpProcessLifetimeHandler.cs(20,36): error IDE0052: (NETCORE_ENGINEERING_TELEMETRY=Build) Private member 'CrashDumpProcessLifetimeHandler._outputDisplay' can be removed as the value assigned to it is never read (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0052)

Check failure on line 20 in src/Platform/Microsoft.Testing.Extensions.CrashDump/CrashDumpProcessLifetimeHandler.cs

View check run for this annotation

Azure Pipelines / microsoft.testfx

src/Platform/Microsoft.Testing.Extensions.CrashDump/CrashDumpProcessLifetimeHandler.cs#L20

src/Platform/Microsoft.Testing.Extensions.CrashDump/CrashDumpProcessLifetimeHandler.cs(20,36): error IDE0052: (NETCORE_ENGINEERING_TELEMETRY=Build) Private member 'CrashDumpProcessLifetimeHandler._outputDisplay' can be removed as the value assigned to it is never read (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0052)

Check failure on line 20 in src/Platform/Microsoft.Testing.Extensions.CrashDump/CrashDumpProcessLifetimeHandler.cs

View check run for this annotation

Azure Pipelines / microsoft.testfx

src/Platform/Microsoft.Testing.Extensions.CrashDump/CrashDumpProcessLifetimeHandler.cs#L20

src/Platform/Microsoft.Testing.Extensions.CrashDump/CrashDumpProcessLifetimeHandler.cs(20,36): error IDE0052: (NETCORE_ENGINEERING_TELEMETRY=Build) Private member 'CrashDumpProcessLifetimeHandler._outputDisplay' can be removed as the value assigned to it is never read (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0052)
private readonly CrashDumpConfiguration _netCoreCrashDumpGeneratorConfiguration;

public CrashDumpProcessLifetimeHandler(
Expand Down Expand Up @@ -56,27 +57,26 @@
public async Task OnTestHostProcessExitedAsync(ITestHostProcessInformation testHostProcessInformation, CancellationToken cancellation)
{
if (cancellation.IsCancellationRequested
|| testHostProcessInformation.HasExitedGracefully
|| (AppDomain.CurrentDomain.GetData("ProcessKilledByHangDump") is string processKilledByHangDump && processKilledByHangDump == "true"))
{
return;
}

ApplicationStateGuard.Ensure(_netCoreCrashDumpGeneratorConfiguration.DumpFileNamePattern is not null);
await _outputDisplay.DisplayAsync(this, new ErrorMessageOutputDeviceData(string.Format(CultureInfo.InvariantCulture, CrashDumpResources.CrashDumpProcessCrashedDumpFileCreated, testHostProcessInformation.PID)), cancellation).ConfigureAwait(false);

string expectedDumpFile = _netCoreCrashDumpGeneratorConfiguration.DumpFileNamePattern.Replace("%p", testHostProcessInformation.PID.ToString(CultureInfo.InvariantCulture));
if (File.Exists(expectedDumpFile))
string? dumpDirectory = Path.GetDirectoryName(expectedDumpFile);
if (RoslynString.IsNullOrEmpty(dumpDirectory) || !Directory.Exists(dumpDirectory))
{
await _messageBus.PublishAsync(this, new FileArtifact(new FileInfo(expectedDumpFile), CrashDumpResources.CrashDumpArtifactDisplayName, CrashDumpResources.CrashDumpArtifactDescription)).ConfigureAwait(false);
return;
}
else

// Collect all dump files in the directory to capture crashes from child processes
// We check for any dumps in the directory, even if the main process exited gracefully,
// because child processes might have crashed
foreach (string dumpFile in Directory.GetFiles(dumpDirectory, "*.dmp"))
{
await _outputDisplay.DisplayAsync(this, new ErrorMessageOutputDeviceData(string.Format(CultureInfo.InvariantCulture, CrashDumpResources.CannotFindExpectedCrashDumpFile, expectedDumpFile)), cancellation).ConfigureAwait(false);
foreach (string dumpFile in Directory.GetFiles(Path.GetDirectoryName(expectedDumpFile)!, "*.dmp"))
{
await _messageBus.PublishAsync(this, new FileArtifact(new FileInfo(dumpFile), CrashDumpResources.CrashDumpDisplayName, CrashDumpResources.CrashDumpArtifactDescription)).ConfigureAwait(false);
}
await _messageBus.PublishAsync(this, new FileArtifact(new FileInfo(dumpFile), CrashDumpResources.CrashDumpArtifactDisplayName, CrashDumpResources.CrashDumpArtifactDescription)).ConfigureAwait(false);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,24 @@ public async Task CrashDump_InvalidFormat_ShouldFail()
testHostResult.AssertOutputContains("Option '--crashdump-type' has invalid arguments: 'invalid' is not a valid dump type. Valid options are 'Mini', 'Heap', 'Triage' and 'Full'");
}

[TestMethod]
public async Task CrashDump_WithChildProcess_CollectsMultipleDumps()
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
{
// TODO: Investigate failures on macos
return;
}

string resultDirectory = Path.Combine(AssetFixture.TargetAssetPath, Guid.NewGuid().ToString("N"));
var testHost = TestInfrastructure.TestHost.LocateFrom(AssetFixture.TargetAssetPath, "CrashDumpWithChild", TargetFrameworks.NetCurrent);
TestHostResult testHostResult = await testHost.ExecuteAsync($"--crashdump --results-directory {resultDirectory}", cancellationToken: TestContext.CancellationToken);
testHostResult.AssertExitCodeIs(ExitCodes.TestHostProcessExitedNonGracefully);

string[] dumpFiles = Directory.GetFiles(resultDirectory, "*.dmp", SearchOption.AllDirectories);
Assert.IsGreaterThanOrEqualTo(2, dumpFiles.Length, $"Expected at least 2 dump files (parent and child), but found {dumpFiles.Length}. Dumps: {string.Join(", ", dumpFiles.Select(Path.GetFileName))}\n{testHostResult}");
}

public sealed class TestAssetFixture() : TestAssetFixtureBase(AcceptanceFixture.NuGetGlobalPackagesFolder)
{
private const string AssetName = "CrashDumpFixture";
Expand All @@ -82,6 +100,11 @@ public sealed class TestAssetFixture() : TestAssetFixtureBase(AcceptanceFixture.
Sources
.PatchTargetFrameworks(TargetFrameworks.All)
.PatchCodeWithReplace("$MicrosoftTestingPlatformVersion$", MicrosoftTestingPlatformVersion));

yield return ("CrashDumpWithChildFixture", "CrashDumpWithChildFixture",
SourcesWithChild
.PatchTargetFrameworks(TargetFrameworks.All)
.PatchCodeWithReplace("$MicrosoftTestingPlatformVersion$", MicrosoftTestingPlatformVersion));
}

public string TargetAssetPath => GetAssetPath(AssetName);
Expand Down Expand Up @@ -152,6 +175,113 @@ public Task ExecuteRequestAsync(ExecuteRequestContext context)
return Task.CompletedTask;
}
}
""";

private const string SourcesWithChild = """
#file CrashDumpWithChild.csproj
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>$TargetFrameworks$</TargetFrameworks>
<OutputType>Exe</OutputType>
<UseAppHost>true</UseAppHost>
<Nullable>enable</Nullable>
<LangVersion>preview</LangVersion>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Testing.Extensions.CrashDump" Version="$MicrosoftTestingPlatformVersion$" />
</ItemGroup>
</Project>

#file Program.cs
using System;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Globalization;
using Microsoft.Testing.Platform;
using Microsoft.Testing.Platform.Extensions.TestFramework;
using Microsoft.Testing.Platform.Builder;
using Microsoft.Testing.Platform.Capabilities.TestFramework;
using Microsoft.Testing.Extensions;
using Microsoft.Testing.Platform.Messages;
using Microsoft.Testing.Platform.Requests;
using Microsoft.Testing.Platform.Services;

public class Startup
{
public static async Task<int> Main(string[] args)
{
Process self = Process.GetCurrentProcess();
string path = self.MainModule!.FileName!;

// Handle child process execution
if (args.Length > 0 && args[0] == "--child")
{
// Child process crashes immediately
Environment.FailFast("Child process crash");
return 1;
}

// Start a child process that will also crash (when running under testhost controller)
if (args.Any(a => a == "--internal-testhostcontroller-pid"))
{
try
{
var childProcess = Process.Start(new ProcessStartInfo(path, "--child")
{
UseShellExecute = false,
RedirectStandardOutput = true,
RedirectStandardError = true,
});

if (childProcess != null)
{
// Give child process time to start and crash
Thread.Sleep(500);
}
}
catch
{
// Ignore any errors starting child process
}
}

ITestApplicationBuilder builder = await TestApplication.CreateBuilderAsync(args);
builder.RegisterTestFramework(_ => new TestFrameworkCapabilities(), (_,__) => new DummyTestFramework());
builder.AddCrashDumpProvider();
using ITestApplication app = await builder.BuildAsync();
return await app.RunAsync();
}
}

public class DummyTestFramework : ITestFramework
{
public string Uid => nameof(DummyTestFramework);

public string Version => "2.0.0";

public string DisplayName => nameof(DummyTestFramework);

public string Description => nameof(DummyTestFramework);

public Task<bool> IsEnabledAsync() => Task.FromResult(true);

public Task<CreateTestSessionResult> CreateTestSessionAsync(CreateTestSessionContext context)
=> Task.FromResult(new CreateTestSessionResult() { IsSuccess = true });

public Task<CloseTestSessionResult> CloseTestSessionAsync(CloseTestSessionContext context)
=> Task.FromResult(new CloseTestSessionResult() { IsSuccess = true });

public Task ExecuteRequestAsync(ExecuteRequestContext context)
{
// Parent process crashes
Environment.FailFast("Parent process crash");
context.Complete();
return Task.CompletedTask;
}
}
""";
}

Expand Down
Loading