147 lines
4.2 KiB
C#
Raw Normal View History

using System;
using System.Diagnostics;
using System.Text;
using Xunit;
using Xunit.Abstractions;
namespace ILLink.Tests
{
public class CommandRunner
{
protected readonly ITestOutputHelper outputHelper;
private string command;
private string args;
private string workingDir;
private string additionalPath;
private int timeout = Int32.MaxValue;
private string terminatingOutput;
public CommandRunner(string command, ITestOutputHelper outputHelper) {
this.command = command;
this.outputHelper = outputHelper;
}
public CommandRunner WithArguments(string args) {
this.args = args;
return this;
}
public CommandRunner WithWorkingDir(string workingDir) {
this.workingDir = workingDir;
return this;
}
public CommandRunner WithAdditionalPath(string additionalPath) {
this.additionalPath = additionalPath;
return this;
}
public CommandRunner WithTimeout(int timeout) {
this.timeout = timeout;
return this;
}
public CommandRunner WithTerminatingOutput(string terminatingOutput) {
this.terminatingOutput = terminatingOutput;
return this;
}
public int Run()
{
return Run(out string commandOutputUnused);
}
public int Run(out string commandOutput)
{
if (String.IsNullOrEmpty(command)) {
throw new Exception("No command was specified specified.");
}
if (outputHelper == null) {
throw new Exception("No output helper present.");
}
var psi = new ProcessStartInfo
{
FileName = command,
UseShellExecute = false,
RedirectStandardOutput = true,
RedirectStandardError = true,
};
outputHelper.WriteLine($"caller working directory: {Environment.CurrentDirectory}");
if (!String.IsNullOrEmpty(args)) {
psi.Arguments = args;
outputHelper.WriteLine($"{command} {args}");
} else {
outputHelper.WriteLine($"{command}");
}
if (!String.IsNullOrEmpty(workingDir)) {
outputHelper.WriteLine($"working directory: {workingDir}");
psi.WorkingDirectory = workingDir;
}
if (!String.IsNullOrEmpty(additionalPath)) {
string path = psi.Environment["PATH"];
psi.Environment["PATH"] = path + ";" + additionalPath;
}
var process = new Process();
process.StartInfo = psi;
// dotnet sets some environment variables that
// may cause problems in the child process.
psi.Environment.Remove("MSBuildExtensionsPath");
psi.Environment.Remove("MSBuildLoadMicrosoftTargetsReadOnly");
psi.Environment.Remove("MSBuildSDKsPath");
psi.Environment.Remove("VbcToolExe");
psi.Environment.Remove("CscToolExe");
psi.Environment.Remove("MSBUILD_EXE_PATH");
outputHelper.WriteLine("environment:");
foreach (var item in psi.Environment) {
outputHelper.WriteLine($"\t{item.Key}={item.Value}");
}
StringBuilder processOutput = new StringBuilder();
DataReceivedEventHandler handler = (sender, e) => {
processOutput.Append(e.Data);
processOutput.AppendLine();
};
StringBuilder processError = new StringBuilder();
DataReceivedEventHandler ehandler = (sender, e) => {
processError.Append(e.Data);
processError.AppendLine();
};
process.OutputDataReceived += handler;
process.ErrorDataReceived += ehandler;
// terminate process if output contains specified string
if (!String.IsNullOrEmpty(terminatingOutput)) {
DataReceivedEventHandler terminatingOutputHandler = (sender, e) => {
if (!String.IsNullOrEmpty(e.Data) && e.Data.Contains(terminatingOutput)) {
process.Kill();
}
};
process.OutputDataReceived += terminatingOutputHandler;
}
// start the process
process.Start();
process.BeginOutputReadLine();
process.BeginErrorReadLine();
if (!process.WaitForExit(timeout)) {
outputHelper.WriteLine($"killing process after {timeout} ms");
process.Kill();
}
// WaitForExit with timeout doesn't guarantee
// that the async output handlers have been
// called, so WaitForExit needs to be called
// afterwards.
process.WaitForExit();
string processOutputStr = processOutput.ToString();
string processErrorStr = processError.ToString();
outputHelper.WriteLine(processOutputStr);
outputHelper.WriteLine(processErrorStr);
commandOutput = processOutputStr;
return process.ExitCode;
}
}
}