Use Process.RunAndCaptureText in src/tests child-process helpers#129779
Use Process.RunAndCaptureText in src/tests child-process helpers#129779AaronRobinsonMSFT wants to merge 14 commits into
Process.RunAndCaptureText in src/tests child-process helpers#129779Conversation
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…eText Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…aptureText Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Process.RunAndCaptureText in src/tests child-process helpers
|
Tagging subscribers to this area: @dotnet/runtime-infrastructure |
There was a problem hiding this comment.
Pull request overview
This PR refactors multiple src/tests helpers and test utilities to use System.Diagnostics.Process.RunAndCaptureText / ProcessTextOutput for the common “run child process, capture full stdout/stderr, check exit code” pattern.
Changes:
- Replaces ad-hoc
Process.Start+ async stream reads / event handlers withProcess.RunAndCaptureText(...)across several test areas. - Updates call sites to consume
ProcessTextOutput.StandardOutput/.StandardErrorandresult.ExitStatus.ExitCode(and timeout cancellation where applicable). - Simplifies a few helpers by removing manual
WaitForExit/ReadToEndAsyncpatterns.
Show a summary per file
| File | Description |
|---|---|
| src/tests/tracing/userevents/common/UserEventsRequirements.cs | Switches tracefs/user_events/ldd checks to RunAndCaptureText and prints captured output lines. |
| src/tests/profiler/common/ProfilerTestRunner.cs | Refactors profilee execution to RunAndCaptureText and verifies captured stdout via TextReader. |
| src/tests/nativeaot/SmokeTests/DwarfDump/Program.cs | Uses RunAndCaptureText for the llvm-dwarfdump --verify invocation and parses captured output. |
| src/tests/nativeaot/AggregateExecutableLibrary/ValidatorExe/ValidatorExe.cs | Simplifies ValidatorExe process execution using captured stdout/stderr and exit code. |
| src/tests/Loader/PlatformNativeR2R/PlatformNativeR2R.cs | Uses captured stdout/stderr for R2RDump execution and failure reporting. |
| src/tests/Loader/binding/tracing/BinderTracingTest.cs | Runs subprocess via RunAndCaptureText and writes captured stdout/stderr to the console. |
| src/tests/GC/Stress/Framework/RFLogging.cs | Updates status-reporting script invocation to RunAndCaptureText. |
| src/tests/GC/Stress/Framework/ReliabilityFramework.cs | Updates harness status reporting invocation to RunAndCaptureText. |
| src/tests/GC/LargeMemory/memcheck.cs | Uses RunAndCaptureText for a simple command execution helper. |
| src/tests/Common/XUnitLogChecker/XUnitLogChecker.cs | Replaces manual timeout/kill/read logic with RunAndCaptureText(..., timeout) and checks ExitStatus.Canceled. |
| src/tests/Common/CoreCLRTestLibrary/CoreclrTestWrapperLib.cs | Uses RunAndCaptureText for pgrep and wmic helpers, capturing output text directly. |
| src/tests/baseservices/exceptions/unhandled/unhandledTester.cs | Refactors subprocess execution to RunAndCaptureText and parses captured stderr lines. |
| src/tests/baseservices/exceptions/stackoverflow/stackoverflowtester.cs | Refactors subprocess execution to RunAndCaptureText and parses captured stderr lines. |
Copilot's findings
- Files reviewed: 13/13 changed files
- Comments generated: 6
Process.RunAndCaptureText throws InvalidOperationException unless both
RedirectStandardOutput and RedirectStandardError are set. Several converted
call sites only redirected one stream. Also fix RFLogging status message
to include the failing exit code via a {0} placeholder.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
/azp list |
|
/azp run runtime-coreclr outerloop |
|
Azure Pipelines successfully started running 1 pipeline(s). |
adamsitnik
left a comment
There was a problem hiding this comment.
@AaronRobinsonMSFT big thanks for dogfooding the new APIs! I've left some comments, PTAL
| bool endOfStackTrace = false; | ||
|
|
||
| testProcess.ErrorDataReceived += (sender, line) => | ||
| foreach (string rawLine in result.StandardError.Split('\n')) |
There was a problem hiding this comment.
nit: this is a test helper, so we don't really care that much about performance, but I just want to point out that an alternative would be to use Process.ReadAllLines and avoid the need to split the captured text into separate lines:
Sth like this:
using Process testProcess = Process.Start(startInfo);
foreach (ProcessOutputLine line in testProcess.ReadAllLines)
{
if (line.StandardError)
{
// the logic goes here
}
}
testProcess.WaitForExit(); // a process can close std handles but keep running| process.Start(); | ||
| string output = process.StandardOutput.ReadToEnd(); | ||
| process.WaitForExit(100); // wait for 100 ms | ||
| string output = Process.RunAndCaptureText(process.StartInfo).StandardOutput; |
There was a problem hiding this comment.
previous implementation was using timeout, pointing this out as I am not sure whether this was intentional or not
| string output = Process.RunAndCaptureText(process.StartInfo).StandardOutput; | |
| string output = Process.RunAndCaptureText(process.StartInfo, timeout: TimeSpan.FromMilliseconds(100)).StandardOutput; |
| ProcessTextOutput result = Process.RunAndCaptureText(startInfo); | ||
| Console.Write(result.StandardOutput); | ||
| Console.Error.Write(result.StandardError); |
There was a problem hiding this comment.
Instead of redirecting the output and error to pipes, reading it and then writing it to parent standard output/error we can just use Process.Run. It's going to inherit the standard handles from parent and the child process is going to write directly to parent standard output/error.
| ProcessTextOutput result = Process.RunAndCaptureText(startInfo); | |
| Console.Write(result.StandardOutput); | |
| Console.Error.Write(result.StandardError); | |
| ProcessTextOutput result = Process.Run(startInfo); |
| string error = result.StandardError.Trim(); | ||
| if (result.ExitStatus.ExitCode != 0 || output != "Hello from HelloExe") | ||
| { | ||
| Console.Error.WriteLine($"Failed to start {helloExePath}"); | ||
| return 1; | ||
| } | ||
|
|
||
| Task<string> outputTask = helloExe.StandardOutput.ReadToEndAsync(); | ||
| Task<string> errorTask = helloExe.StandardError.ReadToEndAsync(); | ||
| helloExe.WaitForExit(); | ||
| string output = (await outputTask).Trim(); | ||
| string error = (await errorTask).Trim(); | ||
| if (helloExe.ExitCode != 0 || output != "Hello from HelloExe") | ||
| { | ||
| Console.Error.WriteLine($"Unexpected HelloExe result. Exit code: {helloExe.ExitCode}; output: '{output}'; error: '{error}'"); | ||
| Console.Error.WriteLine($"Unexpected HelloExe result. Exit code: {result.ExitStatus.ExitCode}; output: '{output}'; error: '{error}'"); |
There was a problem hiding this comment.
nit: trim the error only when we need it
| string error = result.StandardError.Trim(); | |
| if (result.ExitStatus.ExitCode != 0 || output != "Hello from HelloExe") | |
| { | |
| Console.Error.WriteLine($"Failed to start {helloExePath}"); | |
| return 1; | |
| } | |
| Task<string> outputTask = helloExe.StandardOutput.ReadToEndAsync(); | |
| Task<string> errorTask = helloExe.StandardError.ReadToEndAsync(); | |
| helloExe.WaitForExit(); | |
| string output = (await outputTask).Trim(); | |
| string error = (await errorTask).Trim(); | |
| if (helloExe.ExitCode != 0 || output != "Hello from HelloExe") | |
| { | |
| Console.Error.WriteLine($"Unexpected HelloExe result. Exit code: {helloExe.ExitCode}; output: '{output}'; error: '{error}'"); | |
| Console.Error.WriteLine($"Unexpected HelloExe result. Exit code: {result.ExitStatus.ExitCode}; output: '{output}'; error: '{error}'"); | |
| if (result.ExitStatus.ExitCode != 0 || output != "Hello from HelloExe") | |
| { | |
| string error = result.StandardError.Trim(); | |
| Console.Error.WriteLine($"Unexpected HelloExe result. Exit code: {result.ExitStatus.ExitCode}; output: '{output}'; error: '{error}'"); |
| foreach (string key in Environment.GetEnvironmentVariables().Keys) | ||
| { | ||
| process.StartInfo.EnvironmentVariables[key] = Environment.GetEnvironmentVariable(key); | ||
| startInfo.EnvironmentVariables[key] = Environment.GetEnvironmentVariable(key); | ||
| } |
There was a problem hiding this comment.
there is no need to perform this logic, ProcessStartInfo.EnvironmentVariables is always by default initialized to parent process env vars.
It's a test app so we don't care, but when ProcessStartInfo.EnvironmentVariables is not modified at all, we can avoid allocating a lot of strings on Windows (we just let the OS know "don't use explicit env vars, just inherit all" )
| using Process checkTracefs = new() { StartInfo = checkTracefsStartInfo }; | ||
| checkTracefs.OutputDataReceived += (_, e) => | ||
| ProcessTextOutput result = Process.RunAndCaptureText(checkTracefsStartInfo); | ||
| foreach (string rawLine in result.StandardOutput.Split('\n')) |
There was a problem hiding this comment.
I think it would be better to use process.ReadAllLines here. We would avoid the need to split (and trim) and print all updates as they appear, not when they are all finished
| ProcessTextOutput result = Process.RunAndCaptureText(startInfo, TimeSpan.FromMilliseconds(DEFAULT_TIMEOUT_MS)); | ||
| if (result.ExitStatus.Canceled) | ||
| { | ||
| proc.Kill(true); | ||
| outputWriter.WriteLine($"Timedout: '{fileName} {arguments}"); | ||
| return false; | ||
| } |
There was a problem hiding this comment.
When Process.Run* APIs timeout, they kill the process and then throw TimeoutException
| ProcessTextOutput result = Process.RunAndCaptureText(startInfo, TimeSpan.FromMilliseconds(DEFAULT_TIMEOUT_MS)); | |
| if (result.ExitStatus.Canceled) | |
| { | |
| proc.Kill(true); | |
| outputWriter.WriteLine($"Timedout: '{fileName} {arguments}"); | |
| return false; | |
| } | |
| ProcessTextOutput result; | |
| try | |
| { | |
| result = Process.RunAndCaptureText(startInfo, TimeSpan.FromMilliseconds(DEFAULT_TIMEOUT_MS)); | |
| } | |
| catch (TimeoutException) | |
| { | |
| outputWriter.WriteLine($"Timedout: '{fileName} {arguments}"); | |
| return false; | |
| } |
Adopts the new .NET 11
Process.RunAndCaptureTextAPI acrosssrc/testsC# tests/helpers that followed the "launch a child process, wait for exit, read exit code + full stdout/stderr" pattern (no stdin, no live process control).Converted:
Loader/PlatformNativeR2R/PlatformNativeR2R.csnativeaot/AggregateExecutableLibrary/ValidatorExe/ValidatorExe.csGC/Stress/Framework/RFLogging.cs(WriteToReport)baseservices/exceptions/unhandled/unhandledTester.csbaseservices/exceptions/stackoverflow/stackoverflowtester.csLoader/binding/tracing/BinderTracingTest.csprofiler/common/ProfilerTestRunner.csnativeaot/SmokeTests/DwarfDump/Program.cstracing/userevents/common/UserEventsRequirements.csGC/Stress/Framework/ReliabilityFramework.cs(AddFailure)GC/LargeMemory/memcheck.cs(RunCommand)Common/CoreCLRTestLibrary/CoreclrTestWrapperLib.cs(pgrep + wmic)Common/XUnitLogChecker/XUnitLogChecker.cs(RunProcess)Call sites that write to stdin, coordinate with a live process (PID/signals/EventPipe), or rely on
sudo/root process-tree kills were intentionally left unchanged.Note
This PR description was generated with the assistance of GitHub Copilot.