114 lines
4.3 KiB
C#
114 lines
4.3 KiB
C#
// Copyright (c) .NET Foundation. All rights reserved.
|
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
|
|
|
using System;
|
|
using System.Diagnostics;
|
|
using System.Text;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
|
|
namespace Microsoft.AspNetCore.Razor.Design.IntegrationTests
|
|
{
|
|
internal static class MSBuildProcessManager
|
|
{
|
|
public static Task<MSBuildResult> RunProcessAsync(
|
|
ProjectDirectory project,
|
|
string arguments,
|
|
TimeSpan? timeout = null,
|
|
MSBuildProcessKind msBuildProcessKind = MSBuildProcessKind.Dotnet)
|
|
{
|
|
timeout = timeout ?? TimeSpan.FromSeconds(60);
|
|
|
|
var processStartInfo = new ProcessStartInfo()
|
|
{
|
|
WorkingDirectory = project.DirectoryPath,
|
|
UseShellExecute = false,
|
|
RedirectStandardError = true,
|
|
RedirectStandardOutput = true,
|
|
};
|
|
|
|
if (msBuildProcessKind == MSBuildProcessKind.Desktop)
|
|
{
|
|
if (string.IsNullOrEmpty(BuildVariables.MSBuildPath))
|
|
{
|
|
throw new ArgumentException("Unable to locate MSBuild.exe to run desktop tests. " +
|
|
"MSBuild.exe is located using state created as part of running build[cmd|sh] at the root of the repository. Run build /t:Prepeare to set this up if this hasn't been done.");
|
|
}
|
|
|
|
processStartInfo.FileName = BuildVariables.MSBuildPath;
|
|
processStartInfo.Arguments = arguments;
|
|
}
|
|
else
|
|
{
|
|
processStartInfo.FileName = "dotnet";
|
|
processStartInfo.Arguments = $"msbuild {arguments}";
|
|
}
|
|
|
|
var process = new Process()
|
|
{
|
|
StartInfo = processStartInfo,
|
|
EnableRaisingEvents = true,
|
|
};
|
|
|
|
var output = new StringBuilder();
|
|
|
|
process.ErrorDataReceived += Process_ErrorDataReceived;
|
|
process.OutputDataReceived += Process_OutputDataReceived;
|
|
|
|
process.Start();
|
|
process.BeginErrorReadLine();
|
|
process.BeginOutputReadLine();
|
|
|
|
var timeoutTask = Task.Delay(timeout.Value).ContinueWith((t) =>
|
|
{
|
|
// Don't timeout during debug sessions
|
|
while (Debugger.IsAttached)
|
|
{
|
|
Thread.Sleep(TimeSpan.FromSeconds(1));
|
|
}
|
|
|
|
if (process.HasExited)
|
|
{
|
|
// This will happen on success, the 'real' task has already completed so this value will
|
|
// never be visible.
|
|
return (MSBuildResult)null;
|
|
}
|
|
|
|
// This is a timeout.
|
|
process.Kill();
|
|
throw new TimeoutException($"command '${process.StartInfo.FileName} {process.StartInfo.Arguments}' timed out after {timeout}.");
|
|
});
|
|
|
|
var waitTask = Task.Run(() =>
|
|
{
|
|
// We need to use two WaitForExit calls to ensure that all of the output/events are processed. Previously
|
|
// this code used Process.Exited, which could result in us missing some output due to the ordering of
|
|
// events.
|
|
//
|
|
// See the remarks here: https://msdn.microsoft.com/en-us/library/ty0d8k56(v=vs.110).aspx
|
|
if (!process.WaitForExit(Int32.MaxValue))
|
|
{
|
|
// unreachable - the timeoutTask will kill the process before this happens.
|
|
throw new TimeoutException();
|
|
}
|
|
|
|
process.WaitForExit();
|
|
var result = new MSBuildResult(project, process.StartInfo.FileName, process.StartInfo.Arguments, process.ExitCode, output.ToString());
|
|
return result;
|
|
});
|
|
|
|
return Task.WhenAny<MSBuildResult>(waitTask, timeoutTask).Unwrap();
|
|
|
|
void Process_ErrorDataReceived(object sender, DataReceivedEventArgs e)
|
|
{
|
|
output.AppendLine(e.Data);
|
|
}
|
|
|
|
void Process_OutputDataReceived(object sender, DataReceivedEventArgs e)
|
|
{
|
|
output.AppendLine(e.Data);
|
|
}
|
|
}
|
|
}
|
|
}
|