Skip to content
Merged
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
55 changes: 0 additions & 55 deletions src/mono/wasm/Wasm.Build.Tests/Common/ProcessExtensions.cs

This file was deleted.

69 changes: 31 additions & 38 deletions src/mono/wasm/Wasm.Build.Tests/Common/ToolCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,11 @@ public class ToolCommand : IDisposable

public Dictionary<string, string> Environment { get; } = new Dictionary<string, string>();

public event DataReceivedEventHandler? ErrorDataReceived;

public event DataReceivedEventHandler? OutputDataReceived;
// Per-line callbacks used by ExecuteAsyncInternal. Wired via WithOutputDataReceived /
// WithErrorDataReceived. Replaces the older `event DataReceivedEventHandler` pair that
// were tied to the pre-net11 Process.BeginOutputReadLine pattern.
private Action<string?>? _onOutputLine;
private Action<string?>? _onErrorLine;

public string? WorkingDirectory { get; set; }

Expand Down Expand Up @@ -61,13 +63,13 @@ public ToolCommand WithEnvironmentVariables(IDictionary<string, string>? extraEn

public ToolCommand WithOutputDataReceived(Action<string?> handler)
{
OutputDataReceived += (_, args) => handler(args.Data);
_onOutputLine += handler;
return this;
}

public ToolCommand WithErrorDataReceived(Action<string?> handler)
{
ErrorDataReceived += (_, args) => handler(args.Data);
_onErrorLine += handler;
return this;
}

Expand Down Expand Up @@ -111,42 +113,36 @@ private async Task<CommandResult> ExecuteAsyncInternal(string executable, string
{
var output = new List<string>();
CurrentProcess = CreateProcess(executable, args);
DataReceivedEventHandler errorHandler = (s, e) =>
{
if (e.Data == null || isDisposed)
return;

string msg = $"[{_label}] {e.Data}";
output.Add(msg);
_testOutput.WriteLine(msg);
ErrorDataReceived?.Invoke(s, e);
};

DataReceivedEventHandler outputHandler = (s, e) =>
{
if (e.Data == null || isDisposed)
return;

string msg = $"[{_label}] {e.Data}";
output.Add(msg);
_testOutput.WriteLine(msg);
OutputDataReceived?.Invoke(s, e);
};

CurrentProcess.ErrorDataReceived += errorHandler;
CurrentProcess.OutputDataReceived += outputHandler;

try
{
var completionTask = CurrentProcess.StartAndWaitForExitAsync();
CurrentProcess.BeginOutputReadLine();
CurrentProcess.BeginErrorReadLine();
await completionTask;
CurrentProcess.Start();

// Process.ReadAllLinesAsync (added in .NET 11) yields each redirected stdout/stderr
// line as it arrives and completes once both streams have hit EOF. The streams
// close when the child process closes its pipe handles — typically (but not always)
// observable before Exited fires. We still call WaitForExitAsync after the loop so
// CurrentProcess.ExitCode is safe to read.
await foreach (ProcessOutputLine line in CurrentProcess.ReadAllLinesAsync().ConfigureAwait(false))
{
if (isDisposed)
break;

string msg = $"[{_label}] {line.Content}";
output.Add(msg);
_testOutput.WriteLine(msg);

if (line.StandardError)
_onErrorLine?.Invoke(line.Content);
else
_onOutputLine?.Invoke(line.Content);
}

await CurrentProcess.WaitForExitAsync().ConfigureAwait(false);
}
catch (Exception ex)
{
// If process start (inside of StartAndWaitForExitAsync) fails,
// the `Process` object is in a state "don't touch me"
// If process start fails, the `Process` object is in a state "don't touch me"
// (calling almost everything results in "No process associated with this object"),
// therefore we just set it to null to avoid hiding the root exception.
CurrentProcess = null;
Expand All @@ -155,9 +151,6 @@ private async Task<CommandResult> ExecuteAsyncInternal(string executable, string
throw;
}

CurrentProcess.ErrorDataReceived -= errorHandler;
CurrentProcess.OutputDataReceived -= outputHandler;

RemoveNullTerminator(output);

return new CommandResult(
Expand Down
Loading