2022-01-21 07:59:36 -06:00
|
|
|
// Licensed to the .NET Foundation under one or more agreements.
|
|
|
|
// The .NET Foundation licenses this file to you under the MIT license.
|
|
|
|
// See the LICENSE file in the project root for more information.
|
|
|
|
|
|
|
|
using System;
|
|
|
|
using System.Diagnostics;
|
|
|
|
using System.IO;
|
2022-09-08 14:23:06 -05:00
|
|
|
using System.Linq;
|
2023-06-30 13:55:24 -05:00
|
|
|
using System.Net;
|
|
|
|
using System.Net.Http;
|
|
|
|
using System.Net.Sockets;
|
|
|
|
using Xunit;
|
2022-01-21 07:59:36 -06:00
|
|
|
using Xunit.Abstractions;
|
|
|
|
|
|
|
|
namespace Microsoft.DotNet.SourceBuild.SmokeTests;
|
|
|
|
|
|
|
|
internal class DotNetHelper
|
|
|
|
{
|
2022-03-03 08:02:48 -06:00
|
|
|
private static readonly object s_lockObj = new();
|
2022-02-10 15:58:01 -06:00
|
|
|
|
2022-03-03 08:02:48 -06:00
|
|
|
public static string DotNetPath { get; } = Path.Combine(Config.DotNetDirectory, "dotnet");
|
|
|
|
public static string PackagesDirectory { get; } = Path.Combine(Directory.GetCurrentDirectory(), "packages");
|
|
|
|
public static string ProjectsDirectory { get; } = Path.Combine(Directory.GetCurrentDirectory(), $"projects-{DateTime.Now:yyyyMMddHHmmssffff}");
|
|
|
|
|
|
|
|
private ITestOutputHelper OutputHelper { get; }
|
2023-07-10 10:08:15 -05:00
|
|
|
public bool IsMonoRuntime { get; }
|
2022-01-21 07:59:36 -06:00
|
|
|
|
|
|
|
public DotNetHelper(ITestOutputHelper outputHelper)
|
|
|
|
{
|
2022-03-03 08:02:48 -06:00
|
|
|
OutputHelper = outputHelper;
|
|
|
|
|
2022-02-10 15:58:01 -06:00
|
|
|
lock (s_lockObj)
|
2022-01-21 07:59:36 -06:00
|
|
|
{
|
2022-02-10 15:58:01 -06:00
|
|
|
if (!Directory.Exists(Config.DotNetDirectory))
|
2022-01-21 07:59:36 -06:00
|
|
|
{
|
2022-03-03 08:02:48 -06:00
|
|
|
if (!File.Exists(Config.SdkTarballPath))
|
2022-02-10 15:58:01 -06:00
|
|
|
{
|
2022-03-03 08:02:48 -06:00
|
|
|
throw new InvalidOperationException($"Tarball path '{Config.SdkTarballPath}' specified in {Config.SdkTarballPath} does not exist.");
|
2022-02-10 15:58:01 -06:00
|
|
|
}
|
2022-01-21 07:59:36 -06:00
|
|
|
|
2022-02-10 15:58:01 -06:00
|
|
|
Directory.CreateDirectory(Config.DotNetDirectory);
|
2023-06-08 16:09:53 +02:00
|
|
|
Utilities.ExtractTarball(Config.SdkTarballPath, Config.DotNetDirectory, outputHelper);
|
2022-03-03 08:02:48 -06:00
|
|
|
}
|
2023-03-14 14:03:24 +01:00
|
|
|
IsMonoRuntime = DetermineIsMonoRuntime(Config.DotNetDirectory);
|
2022-03-03 08:02:48 -06:00
|
|
|
|
|
|
|
if (!Directory.Exists(ProjectsDirectory))
|
|
|
|
{
|
|
|
|
Directory.CreateDirectory(ProjectsDirectory);
|
|
|
|
InitNugetConfig();
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!Directory.Exists(PackagesDirectory))
|
|
|
|
{
|
|
|
|
Directory.CreateDirectory(PackagesDirectory);
|
|
|
|
}
|
2022-01-21 07:59:36 -06:00
|
|
|
}
|
2022-03-03 08:02:48 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
private static void InitNugetConfig()
|
|
|
|
{
|
|
|
|
bool useLocalPackages = !string.IsNullOrEmpty(Config.PrereqsPath);
|
|
|
|
string nugetConfigPrefix = useLocalPackages ? "local" : "online";
|
|
|
|
string nugetConfigPath = Path.Combine(ProjectsDirectory, "NuGet.Config");
|
|
|
|
File.Copy(
|
|
|
|
Path.Combine(BaselineHelper.GetAssetsDirectory(), $"{nugetConfigPrefix}.NuGet.Config"),
|
|
|
|
nugetConfigPath);
|
2022-01-21 07:59:36 -06:00
|
|
|
|
2022-03-03 08:02:48 -06:00
|
|
|
if (useLocalPackages)
|
|
|
|
{
|
2022-09-08 14:23:06 -05:00
|
|
|
// When using local packages this feed is always required. It contains packages that are
|
|
|
|
// not produced by source-build but are required by the various project templates.
|
2022-03-03 08:02:48 -06:00
|
|
|
if (!Directory.Exists(Config.PrereqsPath))
|
|
|
|
{
|
|
|
|
throw new InvalidOperationException(
|
|
|
|
$"Prereqs path '{Config.PrereqsPath}' specified in {Config.PrereqsPathEnv} does not exist.");
|
|
|
|
}
|
|
|
|
|
|
|
|
string nugetConfig = File.ReadAllText(nugetConfigPath);
|
|
|
|
nugetConfig = nugetConfig.Replace("SMOKE_TEST_PACKAGE_FEED", Config.PrereqsPath);
|
2022-09-08 14:23:06 -05:00
|
|
|
|
|
|
|
// This package feed is optional. You can use an additional feed of source-built packages to run the
|
|
|
|
// smoke-tests as offline as possible.
|
|
|
|
if (Config.CustomPackagesPath != null)
|
|
|
|
{
|
|
|
|
if (!Directory.Exists(Config.CustomPackagesPath))
|
|
|
|
{
|
|
|
|
throw new ArgumentException($"Specified --with-packages {Config.CustomPackagesPath} does not exist.");
|
|
|
|
}
|
|
|
|
nugetConfig = nugetConfig.Replace("CUSTOM_PACKAGE_FEED", Config.CustomPackagesPath);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
nugetConfig = string.Join(Environment.NewLine, nugetConfig.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries).Where(s => !s.Contains("CUSTOM_PACKAGE_FEED")).ToArray());
|
|
|
|
}
|
2022-03-03 08:02:48 -06:00
|
|
|
File.WriteAllText(nugetConfigPath, nugetConfig);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-06-30 13:55:24 -05:00
|
|
|
public void ExecuteCmd(string args, string? workingDirectory = null, Action<Process>? processConfigCallback = null,
|
|
|
|
int? expectedExitCode = 0, int millisecondTimeout = -1)
|
2022-03-03 08:02:48 -06:00
|
|
|
{
|
|
|
|
(Process Process, string StdOut, string StdErr) executeResult = ExecuteHelper.ExecuteProcess(
|
|
|
|
DotNetPath,
|
|
|
|
args,
|
|
|
|
OutputHelper,
|
2023-06-30 13:55:24 -05:00
|
|
|
configureCallback: (process) => configureProcess(process, workingDirectory),
|
2022-03-15 11:00:38 -07:00
|
|
|
millisecondTimeout: millisecondTimeout);
|
|
|
|
|
2022-10-13 06:05:35 -07:00
|
|
|
if (expectedExitCode != null) {
|
|
|
|
ExecuteHelper.ValidateExitCode(executeResult, (int) expectedExitCode);
|
|
|
|
}
|
2022-04-15 15:24:48 -05:00
|
|
|
|
|
|
|
void configureProcess(Process process, string? workingDirectory)
|
|
|
|
{
|
|
|
|
ConfigureProcess(process, workingDirectory);
|
|
|
|
|
2023-06-30 13:55:24 -05:00
|
|
|
processConfigCallback?.Invoke(process);
|
2022-04-15 15:24:48 -05:00
|
|
|
}
|
2022-01-21 07:59:36 -06:00
|
|
|
}
|
|
|
|
|
2023-08-23 06:29:20 -07:00
|
|
|
public static void ConfigureProcess(Process process, string? workingDirectory)
|
2022-01-21 07:59:36 -06:00
|
|
|
{
|
2022-03-03 08:02:48 -06:00
|
|
|
if (workingDirectory != null)
|
|
|
|
{
|
|
|
|
process.StartInfo.WorkingDirectory = workingDirectory;
|
|
|
|
}
|
|
|
|
|
|
|
|
process.StartInfo.EnvironmentVariables["DOTNET_CLI_TELEMETRY_OPTOUT"] = "1";
|
|
|
|
process.StartInfo.EnvironmentVariables["DOTNET_SKIP_FIRST_TIME_EXPERIENCE"] = "1";
|
|
|
|
process.StartInfo.EnvironmentVariables["DOTNET_ROOT"] = Config.DotNetDirectory;
|
|
|
|
process.StartInfo.EnvironmentVariables["NUGET_PACKAGES"] = PackagesDirectory;
|
2023-08-23 06:29:20 -07:00
|
|
|
process.StartInfo.EnvironmentVariables["PATH"] = $"{Config.DotNetDirectory}:{Environment.GetEnvironmentVariable("PATH")}";
|
2022-01-21 07:59:36 -06:00
|
|
|
}
|
2022-02-18 06:51:43 -08:00
|
|
|
|
2022-03-03 08:02:48 -06:00
|
|
|
public void ExecuteBuild(string projectName) =>
|
|
|
|
ExecuteCmd($"build {GetBinLogOption(projectName, "build")}", GetProjectDirectory(projectName));
|
|
|
|
|
2022-02-18 06:51:43 -08:00
|
|
|
/// <summary>
|
|
|
|
/// Create a new .NET project and return the path to the created project folder.
|
|
|
|
/// </summary>
|
2022-03-03 08:02:48 -06:00
|
|
|
public string ExecuteNew(string projectType, string name, string? language = null, string? customArgs = null)
|
|
|
|
{
|
|
|
|
string projectDirectory = GetProjectDirectory(name);
|
|
|
|
string options = $"--name {name} --output {projectDirectory}";
|
|
|
|
if (language != null)
|
|
|
|
{
|
|
|
|
options += $" --language \"{language}\"";
|
|
|
|
}
|
|
|
|
if (string.IsNullOrEmpty(customArgs))
|
|
|
|
{
|
|
|
|
options += $" {customArgs}";
|
|
|
|
}
|
|
|
|
|
|
|
|
ExecuteCmd($"new {projectType} {options}");
|
|
|
|
|
|
|
|
return projectDirectory;
|
|
|
|
}
|
|
|
|
|
2023-06-30 13:55:24 -05:00
|
|
|
public void ExecutePublish(string projectName, DotNetTemplate template, bool? selfContained = null, string? rid = null, bool trimmed = false, bool readyToRun = false)
|
2022-02-18 06:51:43 -08:00
|
|
|
{
|
2022-03-03 08:02:48 -06:00
|
|
|
string options = string.Empty;
|
|
|
|
string binlogDifferentiator = string.Empty;
|
2022-02-18 06:51:43 -08:00
|
|
|
|
2022-03-03 08:02:48 -06:00
|
|
|
if (selfContained.HasValue)
|
|
|
|
{
|
|
|
|
options += $"--self-contained {selfContained.Value.ToString().ToLowerInvariant()}";
|
|
|
|
if (selfContained.Value)
|
|
|
|
{
|
|
|
|
binlogDifferentiator += "self-contained";
|
|
|
|
if (!string.IsNullOrEmpty(rid))
|
|
|
|
{
|
|
|
|
options += $" -r {rid}";
|
|
|
|
binlogDifferentiator += $"-{rid}";
|
|
|
|
}
|
|
|
|
if (trimmed)
|
|
|
|
{
|
|
|
|
options += " /p:PublishTrimmed=true";
|
|
|
|
binlogDifferentiator += "-trimmed";
|
|
|
|
}
|
|
|
|
if (readyToRun)
|
|
|
|
{
|
|
|
|
options += " /p:PublishReadyToRun=true";
|
|
|
|
binlogDifferentiator += "-R2R";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2022-02-18 06:51:43 -08:00
|
|
|
|
2023-06-30 13:55:24 -05:00
|
|
|
string projDir = GetProjectDirectory(projectName);
|
|
|
|
string publishDir = Path.Combine(projDir, "bin", "publish");
|
|
|
|
|
2022-03-03 08:02:48 -06:00
|
|
|
ExecuteCmd(
|
2023-06-30 13:55:24 -05:00
|
|
|
$"publish {options} {GetBinLogOption(projectName, "publish", binlogDifferentiator)} -o {publishDir}",
|
|
|
|
projDir);
|
|
|
|
|
|
|
|
if (template == DotNetTemplate.Console)
|
|
|
|
{
|
|
|
|
ExecuteCmd($"{projectName}.dll", publishDir, expectedExitCode: 0);
|
|
|
|
}
|
|
|
|
else if (template == DotNetTemplate.ClassLib || template == DotNetTemplate.BlazorWasm)
|
|
|
|
{
|
|
|
|
// Can't run the published output of classlib (no entrypoint) or WASM (needs a server)
|
|
|
|
}
|
|
|
|
// Assume it is a web-based template
|
|
|
|
else
|
|
|
|
{
|
|
|
|
ExecuteWebDll(projectName, publishDir, template);
|
|
|
|
}
|
2022-02-18 06:51:43 -08:00
|
|
|
}
|
2022-03-03 08:02:48 -06:00
|
|
|
|
|
|
|
public void ExecuteRun(string projectName) =>
|
|
|
|
ExecuteCmd($"run {GetBinLogOption(projectName, "run")}", GetProjectDirectory(projectName));
|
|
|
|
|
2023-06-30 13:55:24 -05:00
|
|
|
public void ExecuteRunWeb(string projectName, DotNetTemplate template)
|
2022-03-03 08:02:48 -06:00
|
|
|
{
|
2023-02-22 16:52:28 +01:00
|
|
|
// 'dotnet run' exit code differs between CoreCLR and Mono (https://github.com/dotnet/sdk/issues/30095).
|
|
|
|
int expectedExitCode = IsMonoRuntime ? 143 : 0;
|
2022-03-03 08:02:48 -06:00
|
|
|
|
2023-06-30 13:55:24 -05:00
|
|
|
ExecuteWeb(
|
|
|
|
projectName,
|
|
|
|
$"run --no-launch-profile {GetBinLogOption(projectName, "run")}",
|
|
|
|
GetProjectDirectory(projectName),
|
|
|
|
template,
|
|
|
|
expectedExitCode);
|
2022-03-03 08:02:48 -06:00
|
|
|
}
|
|
|
|
|
2023-06-30 13:55:24 -05:00
|
|
|
public void ExecuteWebDll(string projectName, string workingDirectory, DotNetTemplate template) =>
|
|
|
|
ExecuteWeb(projectName, $"{projectName}.dll", workingDirectory, template, expectedExitCode: 0);
|
|
|
|
|
2022-03-03 08:02:48 -06:00
|
|
|
public void ExecuteTest(string projectName) =>
|
|
|
|
ExecuteCmd($"test {GetBinLogOption(projectName, "test")}", GetProjectDirectory(projectName));
|
|
|
|
|
2023-06-30 13:55:24 -05:00
|
|
|
private void ExecuteWeb(string projectName, string args, string workingDirectory, DotNetTemplate template, int expectedExitCode)
|
|
|
|
{
|
|
|
|
WebAppValidator validator = new(OutputHelper, template);
|
|
|
|
ExecuteCmd(
|
|
|
|
args,
|
|
|
|
workingDirectory,
|
|
|
|
processConfigCallback: validator.Validate,
|
|
|
|
expectedExitCode: expectedExitCode,
|
|
|
|
millisecondTimeout: 30000);
|
|
|
|
Assert.True(validator.IsValidated);
|
|
|
|
if (validator.ValidationException is not null)
|
|
|
|
{
|
|
|
|
throw validator.ValidationException;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-03-03 08:02:48 -06:00
|
|
|
private static string GetBinLogOption(string projectName, string command, string? differentiator = null)
|
|
|
|
{
|
|
|
|
string fileName = $"{projectName}-{command}";
|
|
|
|
if (!string.IsNullOrEmpty(differentiator))
|
|
|
|
{
|
|
|
|
fileName += $"-{differentiator}";
|
|
|
|
}
|
|
|
|
|
2023-10-12 15:50:19 -05:00
|
|
|
return $"/bl:{Path.Combine(TestBase.LogsDirectory, $"{fileName}.binlog")}";
|
2022-03-03 08:02:48 -06:00
|
|
|
}
|
|
|
|
|
2023-03-14 14:03:24 +01:00
|
|
|
private static bool DetermineIsMonoRuntime(string dotnetRoot)
|
2023-02-22 16:52:28 +01:00
|
|
|
{
|
|
|
|
string sharedFrameworkRoot = Path.Combine(dotnetRoot, "shared", "Microsoft.NETCore.App");
|
|
|
|
if (!Directory.Exists(sharedFrameworkRoot))
|
|
|
|
{
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
string? version = Directory.GetDirectories(sharedFrameworkRoot).FirstOrDefault();
|
|
|
|
if (version is null)
|
|
|
|
{
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
string sharedFramework = Path.Combine(sharedFrameworkRoot, version);
|
|
|
|
|
|
|
|
// Check the presence of one of the mono header files.
|
|
|
|
return File.Exists(Path.Combine(sharedFramework, "mono-gc.h"));
|
|
|
|
}
|
|
|
|
|
2022-03-03 08:02:48 -06:00
|
|
|
private static string GetProjectDirectory(string projectName) => Path.Combine(ProjectsDirectory, projectName);
|
2023-06-30 13:55:24 -05:00
|
|
|
|
2023-09-12 19:11:24 +05:30
|
|
|
public static bool ShouldPublishComplex() =>
|
|
|
|
string.Equals(Config.TargetArchitecture,"ppc64le") || string.Equals(Config.TargetArchitecture,"s390x");
|
|
|
|
|
2023-06-30 13:55:24 -05:00
|
|
|
private class WebAppValidator
|
|
|
|
{
|
|
|
|
private readonly ITestOutputHelper _outputHelper;
|
|
|
|
private readonly DotNetTemplate _template;
|
|
|
|
|
|
|
|
public WebAppValidator(ITestOutputHelper outputHelper, DotNetTemplate template)
|
|
|
|
{
|
|
|
|
_outputHelper = outputHelper;
|
|
|
|
_template = template;
|
|
|
|
}
|
|
|
|
|
|
|
|
public bool IsValidated { get; set; }
|
|
|
|
public Exception? ValidationException { get; set; }
|
|
|
|
|
|
|
|
private static int GetAvailablePort()
|
|
|
|
{
|
|
|
|
TcpListener listener = new(IPAddress.Loopback, 0);
|
|
|
|
listener.Start();
|
|
|
|
int port = ((IPEndPoint)listener.LocalEndpoint).Port;
|
|
|
|
listener.Stop();
|
|
|
|
return port;
|
|
|
|
}
|
|
|
|
|
|
|
|
public void Validate(Process process)
|
|
|
|
{
|
|
|
|
int port = GetAvailablePort();
|
|
|
|
process.StartInfo.EnvironmentVariables.Add("ASPNETCORE_HTTP_PORTS", port.ToString());
|
|
|
|
process.OutputDataReceived += new DataReceivedEventHandler((sender, e) =>
|
|
|
|
{
|
|
|
|
try
|
|
|
|
{
|
|
|
|
if (e.Data?.Contains("Application started. Press Ctrl+C to shut down.") ?? false)
|
|
|
|
{
|
|
|
|
_outputHelper.WriteLine("Detected app has started. Sending web request to validate...");
|
|
|
|
|
|
|
|
using HttpClient httpClient = new();
|
|
|
|
string url = $"http://localhost:{port}";
|
|
|
|
if (_template == DotNetTemplate.WebApi)
|
|
|
|
{
|
|
|
|
url += "/WeatherForecast";
|
|
|
|
}
|
|
|
|
|
|
|
|
using HttpResponseMessage resultMsg = httpClient.GetAsync(new Uri(url)).Result;
|
|
|
|
_outputHelper.WriteLine($"Status code returned: {resultMsg.StatusCode}");
|
|
|
|
resultMsg.EnsureSuccessStatusCode();
|
|
|
|
IsValidated = true;
|
|
|
|
|
|
|
|
ExecuteHelper.ExecuteProcessValidateExitCode("kill", $"-s TERM {process.Id}", _outputHelper);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
catch (Exception ex)
|
|
|
|
{
|
|
|
|
ValidationException = ex;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
2022-01-21 07:59:36 -06:00
|
|
|
}
|