arcade/src/sdk.tests/SdkPackageIntegrationTests.cs
Louis Seubert 172f2ef3e2
All checks were successful
CI / build (push) Successful in 3m26s
feat: initialize Geekeey Arcade .NET SDK repository
Single-commit squash of the repository history containing the Arcade
.NET MSBuild SDK packages (base, Razor, Web, Test), their analyzer and
editorconfig configuration, test suites, and CI workflows.
2026-10-23 23:23:40 +02:00

163 lines
5.8 KiB
C#

// Copyright (c) The Geekeey Authors
// SPDX-License-Identifier: EUPL-1.2
using System.Diagnostics;
using System.Text.RegularExpressions;
namespace Geekeey.Arcade.NET.Tests;
public sealed class SdkPackageIntegrationTests
{
private static string RepositoryPath
{
get
{
for (var directory = new DirectoryInfo(AppContext.BaseDirectory); directory is not null; directory = directory.Parent)
{
if (File.Exists(Path.Combine(directory.FullName, "global.json")))
{
return directory.FullName;
}
}
throw new InvalidOperationException("Could not locate the repository root containing global.json.");
}
}
private static string NuGetFeedPath { get; } = Path.Combine(RepositoryPath, "artifacts", "package", "release");
[Test]
[Arguments("Geekeey.Arcade.NET.Sdk", "base")]
[Arguments("Geekeey.Arcade.NET.Sdk.Razor", "razor")]
[Arguments("Geekeey.Arcade.NET.Sdk.Web", "web")]
public async Task SdkPackageCanBeConsumedFromAFeed(string packageId, string name)
{
// Arrange
var version = FindPackageVersion(packageId);
using var sut = await SdkTestProject.CreateAsync(name, version);
// Act
await sut.RunDotnetAsync($"restore --configfile \"{sut.NuGetConfigPath}\" --packages \"{sut.PackagesPath}\" --nologo");
await sut.RunDotnetAsync("build --no-restore --nologo");
await sut.RunDotnetAsync($"pack --no-restore --configuration Debug --output \"{sut.PackedPath}\" --nologo");
// Assert
await Assert.That(File.Exists(Path.Combine(sut.ProjectPath, "bin", "Debug", "net10.0", $"{sut.ProjectName}.dll"))).IsTrue();
await Assert.That(Directory.Exists(sut.PackedPath)).IsTrue();
await Assert.That(Directory.EnumerateFiles(sut.PackedPath, $"{sut.ProjectName}.*.nupkg").Any()).IsTrue();
}
[Test]
public async Task TestSdkPackageStoresTestOutputInArtifacts()
{
// Arrange
var version = FindPackageVersion("Geekeey.Arcade.NET.Sdk.Test");
using var sut = await SdkTestProject.CreateAsync("test", version);
await sut.RunDotnetAsync($"restore --configfile \"{sut.NuGetConfigPath}\" --packages \"{sut.PackagesPath}\" --nologo");
await sut.RunDotnetAsync("build --no-restore --nologo");
var outputDirectory = Path.Combine(sut.ProjectPath, "artifacts", "bin", sut.ProjectName, "debug");
await Assert.That(File.Exists(Path.Combine(outputDirectory, $"{sut.ProjectName}.dll"))).IsTrue();
// Act
var stdout = await sut.RunDotnetAsync("run --no-build --no-restore");
// Assert
await Assert.That(stdout.Contains("succeeded: 1", StringComparison.Ordinal)).IsTrue();
}
private static string FindPackageVersion(string packageId)
{
var pattern = $"""^{Regex.Escape(packageId)}\.(?<version>\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?)\.nupkg$""";
foreach (var package in Directory.EnumerateFiles(NuGetFeedPath, "*.nupkg"))
{
var match = Regex.Match(Path.GetFileName(package), pattern, RegexOptions.CultureInvariant);
if (match.Success)
{
return match.Groups["version"].Value;
}
}
throw new InvalidOperationException($"No current package found for {packageId}. Run dotnet pack first.");
}
private sealed class SdkTestProject : IDisposable
{
private SdkTestProject(string root)
{
RootPath = root;
ProjectPath = Path.Combine(RootPath, "Project");
PackagesPath = Path.Combine(RootPath, "packages");
PackedPath = Path.Combine(RootPath, "packed");
NuGetConfigPath = Path.Combine(RootPath, "NuGet.config");
}
public static async Task<SdkTestProject> CreateAsync(string name, string version)
{
var sut = new SdkTestProject(Path.Combine(Path.GetTempPath(), $"arcade-sdk-test-{Guid.NewGuid():N}"));
try
{
Directory.CreateDirectory(sut.ProjectPath);
Directory.Copy(Path.Combine(AppContext.BaseDirectory, "fixtures", name), sut.ProjectPath);
sut.ProjectFilePath = Directory.EnumerateFiles(sut.ProjectPath, "*.csproj").Single();
sut.ProjectName = Path.GetFileNameWithoutExtension(sut.ProjectFilePath);
var project = await File.ReadAllTextAsync(sut.ProjectFilePath);
await File.WriteAllTextAsync(sut.ProjectFilePath, project.Replace("{0}", version, StringComparison.Ordinal));
var contents = $"""
<configuration>
<packageSources>
<clear />
<add key="local" value="{Path.Combine(RepositoryPath, "artifacts", "package", "release")}" />
<add key="nuget.org" value="https://api.nuget.org/v3/index.json" />
</packageSources>
</configuration>
""";
await File.WriteAllTextAsync(sut.NuGetConfigPath, contents);
return sut;
}
catch
{
sut.Dispose();
throw;
}
}
public string RootPath { get; }
public string ProjectPath { get; }
public string ProjectFilePath { get; private set; } = "";
public string ProjectName { get; private set; } = "";
public string PackagesPath { get; }
public string PackedPath { get; }
public string NuGetConfigPath { get; }
public async Task<string> RunDotnetAsync(string arguments)
{
var startInfo = new ProcessStartInfo("dotnet", arguments)
{
WorkingDirectory = ProjectPath,
RedirectStandardOutput = true,
RedirectStandardError = true,
UseShellExecute = false,
CreateNoWindow = true,
Environment = { ["NUGET_PACKAGES"] = PackagesPath, ["DOTNET_CLI_DO_NOT_USE_MSBUILD_SERVER"] = "1" }
};
var process = Process.Start(startInfo) ?? throw new InvalidOperationException("Could not start dotnet.");
var output = await process.StandardOutput.ReadToEndAsync();
var error = await process.StandardError.ReadToEndAsync();
await process.WaitForExitAsync();
return process.ExitCode is 0 ? output : throw new InvalidOperationException($"dotnet {arguments} failed with exit code {process.ExitCode}.\n{output}\n{error}");
}
public void Dispose()
{
Directory.Delete(RootPath, recursive: true);
}
}
}