|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using Xunit.Abstractions;
namespace Microsoft.Extensions.Internal;
internal class ProcessEx : IDisposable
{
private readonly ITestOutputHelper _output;
private readonly Process _process;
private readonly StringBuilder _stderrCapture = new StringBuilder();
private readonly StringBuilder _stdoutCapture = new StringBuilder();
private readonly object _pipeCaptureLock = new object();
private BlockingCollection<string> _stdoutLines = new BlockingCollection<string>();
private TaskCompletionSource<int> _exited;
private ProcessEx(ITestOutputHelper output, Process proc)
{
_output = output;
_process = proc;
proc.EnableRaisingEvents = true;
proc.OutputDataReceived += OnOutputData;
proc.ErrorDataReceived += OnErrorData;
proc.Exited += OnProcessExited;
proc.BeginOutputReadLine();
proc.BeginErrorReadLine();
_exited = new TaskCompletionSource<int>(TaskCreationOptions.RunContinuationsAsynchronously);
}
public Task Exited => _exited.Task;
public bool HasExited => _process.HasExited;
public string Output
{
get
{
lock (_pipeCaptureLock)
{
return _stdoutCapture.ToString();
}
}
}
public int ExitCode => _process.ExitCode;
public static ProcessEx Run(ITestOutputHelper output, string workingDirectory, string command, string args = null, IDictionary<string, string> envVars = null)
{
var startInfo = new ProcessStartInfo(command, args)
{
RedirectStandardOutput = true,
RedirectStandardError = true,
UseShellExecute = false,
CreateNoWindow = true,
WorkingDirectory = workingDirectory
};
if (envVars != null)
{
foreach (var envVar in envVars)
{
startInfo.EnvironmentVariables[envVar.Key] = envVar.Value;
}
}
output.WriteLine($"==> {startInfo.FileName} {startInfo.Arguments} [{startInfo.WorkingDirectory}]");
var proc = Process.Start(startInfo);
return new ProcessEx(output, proc);
}
private void OnErrorData(object sender, DataReceivedEventArgs e)
{
if (e.Data == null)
{
return;
}
lock (_pipeCaptureLock)
{
_stderrCapture.AppendLine(e.Data);
}
_output.WriteLine("[ERROR] " + e.Data);
}
private void OnOutputData(object sender, DataReceivedEventArgs e)
{
if (e.Data == null)
{
return;
}
lock (_pipeCaptureLock)
{
_stdoutCapture.AppendLine(e.Data);
}
_output.WriteLine(e.Data);
if (_stdoutLines != null)
{
_stdoutLines.Add(e.Data);
}
}
private void OnProcessExited(object sender, EventArgs e)
{
_process.WaitForExit();
_stdoutLines.CompleteAdding();
_stdoutLines = null;
_exited.TrySetResult(_process.ExitCode);
}
public void Dispose()
{
if (_process != null && !_process.HasExited)
{
_process.KillTree();
}
_process.CancelOutputRead();
_process.CancelErrorRead();
_process.ErrorDataReceived -= OnErrorData;
_process.OutputDataReceived -= OnOutputData;
_process.Exited -= OnProcessExited;
_process.Dispose();
if (_stdoutLines != null)
{
_stdoutLines.Dispose();
}
}
}
|