@@ -72,6 +72,24 @@ public async Task CrashDump_InvalidFormat_ShouldFail()
7272 testHostResult . AssertOutputContains ( "Option '--crashdump-type' has invalid arguments: 'invalid' is not a valid dump type. Valid options are 'Mini', 'Heap', 'Triage' and 'Full'" ) ;
7373 }
7474
75+ [ TestMethod ]
76+ public async Task CrashDump_WithChildProcess_CollectsMultipleDumps ( )
77+ {
78+ if ( RuntimeInformation . IsOSPlatform ( OSPlatform . OSX ) )
79+ {
80+ // TODO: Investigate failures on macos
81+ return ;
82+ }
83+
84+ string resultDirectory = Path . Combine ( AssetFixture . TargetAssetPath , Guid . NewGuid ( ) . ToString ( "N" ) ) ;
85+ var testHost = TestInfrastructure . TestHost . LocateFrom ( AssetFixture . TargetAssetPath , "CrashDumpWithChild" , TargetFrameworks . NetCurrent ) ;
86+ TestHostResult testHostResult = await testHost . ExecuteAsync ( $ "--crashdump --results-directory { resultDirectory } ", cancellationToken : TestContext . CancellationToken ) ;
87+ testHostResult . AssertExitCodeIs ( ExitCodes . TestHostProcessExitedNonGracefully ) ;
88+
89+ string [ ] dumpFiles = Directory . GetFiles ( resultDirectory , "*.dmp" , SearchOption . AllDirectories ) ;
90+ Assert . IsTrue ( dumpFiles . Length >= 2 , $ "Expected at least 2 dump files (parent and child), but found { dumpFiles . Length } . Dumps: { string . Join ( ", " , dumpFiles . Select ( Path . GetFileName ) ) } \n { testHostResult } ") ;
91+ }
92+
7593 public sealed class TestAssetFixture ( ) : TestAssetFixtureBase ( AcceptanceFixture . NuGetGlobalPackagesFolder )
7694 {
7795 private const string AssetName = "CrashDumpFixture" ;
@@ -82,6 +100,11 @@ public sealed class TestAssetFixture() : TestAssetFixtureBase(AcceptanceFixture.
82100 Sources
83101 . PatchTargetFrameworks ( TargetFrameworks . All )
84102 . PatchCodeWithReplace ( "$MicrosoftTestingPlatformVersion$" , MicrosoftTestingPlatformVersion ) ) ;
103+
104+ yield return ( "CrashDumpWithChildFixture" , "CrashDumpWithChildFixture" ,
105+ SourcesWithChild
106+ . PatchTargetFrameworks ( TargetFrameworks . All )
107+ . PatchCodeWithReplace ( "$MicrosoftTestingPlatformVersion$" , MicrosoftTestingPlatformVersion ) ) ;
85108 }
86109
87110 public string TargetAssetPath => GetAssetPath ( AssetName ) ;
@@ -152,6 +175,113 @@ public Task ExecuteRequestAsync(ExecuteRequestContext context)
152175 return Task.CompletedTask;
153176 }
154177}
178+ """ ;
179+
180+ private const string SourcesWithChild = """
181+ #file CrashDumpWithChild.csproj
182+ <Project Sdk="Microsoft.NET.Sdk">
183+ <PropertyGroup>
184+ <TargetFrameworks>$TargetFrameworks$</TargetFrameworks>
185+ <OutputType>Exe</OutputType>
186+ <UseAppHost>true</UseAppHost>
187+ <Nullable>enable</Nullable>
188+ <LangVersion>preview</LangVersion>
189+ </PropertyGroup>
190+ <ItemGroup>
191+ <PackageReference Include="Microsoft.Testing.Extensions.CrashDump" Version="$MicrosoftTestingPlatformVersion$" />
192+ </ItemGroup>
193+ </Project>
194+
195+ #file Program.cs
196+ using System;
197+ using System.Diagnostics;
198+ using System.IO;
199+ using System.Linq;
200+ using System.Threading;
201+ using System.Threading.Tasks;
202+ using System.Globalization;
203+ using Microsoft.Testing.Platform;
204+ using Microsoft.Testing.Platform.Extensions.TestFramework;
205+ using Microsoft.Testing.Platform.Builder;
206+ using Microsoft.Testing.Platform.Capabilities.TestFramework;
207+ using Microsoft.Testing.Extensions;
208+ using Microsoft.Testing.Platform.Messages;
209+ using Microsoft.Testing.Platform.Requests;
210+ using Microsoft.Testing.Platform.Services;
211+
212+ public class Startup
213+ {
214+ public static async Task<int> Main(string[] args)
215+ {
216+ Process self = Process.GetCurrentProcess();
217+ string path = self.MainModule!.FileName!;
218+
219+ // Handle child process execution
220+ if (args.Length > 0 && args[0] == "--child")
221+ {
222+ // Child process crashes immediately
223+ Environment.FailFast("Child process crash");
224+ return 1;
225+ }
226+
227+ // Start a child process that will also crash (only when running as testhost controller)
228+ if (args.Any(a => a == "--internal-testhostcontroller-pid"))
229+ {
230+ try
231+ {
232+ var childProcess = Process.Start(new ProcessStartInfo(path, "--child")
233+ {
234+ UseShellExecute = false,
235+ RedirectStandardOutput = true,
236+ RedirectStandardError = true,
237+ });
238+
239+ if (childProcess != null)
240+ {
241+ // Give child process time to start and crash
242+ Thread.Sleep(500);
243+ }
244+ }
245+ catch
246+ {
247+ // Ignore any errors starting child process
248+ }
249+ }
250+
251+ ITestApplicationBuilder builder = await TestApplication.CreateBuilderAsync(args);
252+ builder.RegisterTestFramework(_ => new TestFrameworkCapabilities(), (_,__) => new DummyTestFramework());
253+ builder.AddCrashDumpProvider();
254+ using ITestApplication app = await builder.BuildAsync();
255+ return await app.RunAsync();
256+ }
257+ }
258+
259+ public class DummyTestFramework : ITestFramework
260+ {
261+ public string Uid => nameof(DummyTestFramework);
262+
263+ public string Version => "2.0.0";
264+
265+ public string DisplayName => nameof(DummyTestFramework);
266+
267+ public string Description => nameof(DummyTestFramework);
268+
269+ public Task<bool> IsEnabledAsync() => Task.FromResult(true);
270+
271+ public Task<CreateTestSessionResult> CreateTestSessionAsync(CreateTestSessionContext context)
272+ => Task.FromResult(new CreateTestSessionResult() { IsSuccess = true });
273+
274+ public Task<CloseTestSessionResult> CloseTestSessionAsync(CloseTestSessionContext context)
275+ => Task.FromResult(new CloseTestSessionResult() { IsSuccess = true });
276+
277+ public Task ExecuteRequestAsync(ExecuteRequestContext context)
278+ {
279+ // Parent process crashes
280+ Environment.FailFast("Parent process crash");
281+ context.Complete();
282+ return Task.CompletedTask;
283+ }
284+ }
155285""" ;
156286 }
157287
0 commit comments