aspnetcore/AspNetCoreSdkTests/Util/DotNetUtil.cs

177 lines
7.4 KiB
C#

using Microsoft.Extensions.Internal;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
namespace AspNetCoreSdkTests.Util
{
internal static class DotNetUtil
{
private const string _clearPackageSourcesNuGetConfig =
@"<?xml version=""1.0"" encoding=""utf-8""?>
<configuration>
<packageSources>
<clear />
</packageSources>
</configuration>
";
// Bind to dynamic port 0 to avoid port conflicts during parallel tests
private const string _urls = "--urls http://127.0.0.1:0;https://127.0.0.1:0";
public static string PublishOutput => "pub";
private static IEnumerable<KeyValuePair<string, string>> GetEnvironment(string workingDirectory)
{
// Set NUGET_PACKAGES to an empty folder to ensure all packages are loaded from either NuGetFallbackFolder or configured sources,
// and *not* loaded from the default per-user global-packages folder.
yield return new KeyValuePair<string, string>("NUGET_PACKAGES", Path.Combine(workingDirectory, ".nuget", "packages"));
}
public static string New(string template, string workingDirectory)
{
// Clear all packages sources by default. May be overridden by NuGetPackageSource parameter.
File.WriteAllText(Path.Combine(workingDirectory, "NuGet.config"), _clearPackageSourcesNuGetConfig);
return RunDotNet($"new {template} --name {template} --output . --no-restore", workingDirectory, GetEnvironment(workingDirectory));
}
public static string Restore(string workingDirectory, NuGetPackageSource packageSource)
{
var sourceArgument = GetSourceArgument(packageSource);
return RunDotNet($"restore --no-cache{sourceArgument}", workingDirectory, GetEnvironment(workingDirectory));
}
public static string Build(string workingDirectory)
{
return RunDotNet("build --no-restore", workingDirectory, GetEnvironment(workingDirectory));
}
public static (Process Process, ConcurrentStringBuilder OutputBuilder, ConcurrentStringBuilder ErrorBuilder) Run(string workingDirectory)
{
return StartDotNet($"run --no-build {_urls}", workingDirectory, GetEnvironment(workingDirectory));
}
internal static (Process Process, ConcurrentStringBuilder OutputBuilder, ConcurrentStringBuilder ErrorBuilder) Exec(string workingDirectory, string name)
{
var path = Path.Combine(PublishOutput, $"{name}.dll");
return StartDotNet($"exec {path} {_urls}", workingDirectory, GetEnvironment(workingDirectory));
}
public static string Publish(string workingDirectory)
{
return RunDotNet($"publish --no-build -o {PublishOutput}", workingDirectory, GetEnvironment(workingDirectory));
}
private static string GetSourceArgument(NuGetPackageSource packageSource)
{
switch (packageSource)
{
case NuGetPackageSource.None:
return string.Empty;
case NuGetPackageSource.NuGetOrg:
return " --source https://api.nuget.org/v3/index.json";
case NuGetPackageSource.EnvironmentVariable:
var env = Environment.GetEnvironmentVariable("NUGET_PACKAGE_SOURCE");
return string.IsNullOrEmpty(env) ?
throw new InvalidOperationException("Environment variable NUGET_PACKAGE_SOURCE is required but not set") :
$" --source env";
default:
throw new ArgumentException("Invalid value", nameof(packageSource));
}
}
private static string RunDotNet(string arguments, string workingDirectory,
IEnumerable<KeyValuePair<string, string>> environment = null, bool throwOnError = true)
{
var p = StartDotNet(arguments, workingDirectory, environment);
return WaitForExit(p, throwOnError: throwOnError);
}
private static (Process Process, ConcurrentStringBuilder OutputBuilder, ConcurrentStringBuilder ErrorBuilder) StartDotNet(
string arguments, string workingDirectory, IEnumerable<KeyValuePair<string, string>> environment = null)
{
return StartProcess("dotnet", arguments, workingDirectory, environment);
}
private static (Process Process, ConcurrentStringBuilder OutputBuilder, ConcurrentStringBuilder ErrorBuilder) StartProcess(
string filename, string arguments, string workingDirectory, IEnumerable<KeyValuePair<string, string>> environment = null)
{
var process = new Process()
{
StartInfo =
{
FileName = filename,
Arguments = arguments,
RedirectStandardInput = true,
RedirectStandardOutput = true,
RedirectStandardError = true,
UseShellExecute = false,
WorkingDirectory = workingDirectory,
},
};
if (environment != null)
{
foreach (var kvp in environment)
{
process.StartInfo.Environment.Add(kvp);
}
}
var outputBuilder = new ConcurrentStringBuilder();
process.OutputDataReceived += (_, e) =>
{
outputBuilder.AppendLine(e.Data);
};
var errorBuilder = new ConcurrentStringBuilder();
process.ErrorDataReceived += (_, e) =>
{
errorBuilder.AppendLine(e.Data);
};
process.Start();
process.BeginOutputReadLine();
process.BeginErrorReadLine();
return (process, outputBuilder, errorBuilder);
}
public static string StopProcess((Process Process, ConcurrentStringBuilder OutputBuilder, ConcurrentStringBuilder ErrorBuilder) process,
bool throwOnError = true)
{
if (!process.Process.HasExited)
{
process.Process.KillTree();
}
return WaitForExit(process, throwOnError: throwOnError);
}
public static string WaitForExit((Process Process, ConcurrentStringBuilder OutputBuilder, ConcurrentStringBuilder ErrorBuilder) process,
bool throwOnError = true)
{
// Workaround issue where WaitForExit() blocks until child processes are killed, which is problematic
// for the dotnet.exe NodeReuse child processes. I'm not sure why this is problematic for dotnet.exe child processes
// but not for MSBuild.exe child processes. The workaround is to specify a large timeout.
// https://stackoverflow.com/a/37983587/102052
process.Process.WaitForExit(int.MaxValue);
if (throwOnError && process.Process.ExitCode != 0)
{
var sb = new ConcurrentStringBuilder();
sb.AppendLine($"Command {process.Process.StartInfo.FileName} {process.Process.StartInfo.Arguments} returned exit code {process.Process.ExitCode}");
sb.AppendLine();
sb.AppendLine(process.OutputBuilder.ToString());
throw new InvalidOperationException(sb.ToString());
}
return process.OutputBuilder.ToString();
}
}
}