refactor: tests
Some checks failed
CI / build (push) Successful in 2m46s
release / publish (push) Failing after 1m7s

This commit is contained in:
Louis Seubert 2026-10-27 19:33:01 +01:00
commit 016db6f076
Signed by: louis9902
SSH key fingerprint: SHA256:HkW1JCsONisFW0uQchtmqod/1QUpxsEIxbg7aIquMX0
25 changed files with 339 additions and 217 deletions

View file

@ -22,8 +22,11 @@ jobs:
- name: dotnet format check - name: dotnet format check
run: dotnet format arcade.slnx --verify-no-changes --verbosity minimal run: dotnet format arcade.slnx --verify-no-changes --verbosity minimal
- name: dotnet pack sdk
run: dotnet pack src/sdk/Sdk.slnx -p:Version=${{ steps.version.outputs.version }}
- name: dotnet pack - name: dotnet pack
run: dotnet pack arcade.slnx -p:Version=${{ steps.version.outputs.version }} run: dotnet pack arcade.slnx -p:Version=${{ steps.version.outputs.version }}
- name: dotnet test - name: dotnet test
run: dotnet test --solution arcade.slnx -p:Version=${{ steps.version.outputs.version }} run: dotnet test --solution arcade.slnx -p:Version=${{ steps.version.outputs.version }}

View file

@ -19,8 +19,11 @@ jobs:
id: version id: version
uses: ./.forgejo/actions/version uses: ./.forgejo/actions/version
- name: dotnet pack sdk
run: dotnet pack src/sdk/Sdk.slnx -p:Version=${{ steps.version.outputs.version }}
- name: dotnet pack - name: dotnet pack
run: dotnet pack arcade.slnx -p:Version=${{ steps.version.outputs.version }} run: dotnet pack arcade.slnx -p:Version=${{ steps.version.outputs.version }}
- name: upload packages - name: upload packages
uses: https://code.geekeey.de/actions/artifacts/push@ae8d20e174a8706675e6d54ebd3719a2802ea9dd # 1 uses: https://code.geekeey.de/actions/artifacts/push@ae8d20e174a8706675e6d54ebd3719a2802ea9dd # 1

View file

@ -1,8 +1,8 @@
<Project> <Project>
<PropertyGroup> <PropertyGroup>
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally> <ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageVersion Include="TUnit" Version="1.75.0" /> <PackageVersion Include="TUnit" Version="1.76.28" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View file

@ -1,7 +1,3 @@
<Solution> <Solution>
<Project Path="src/sdk/Sdk/Geekeey.Arcade.NET.Sdk/Geekeey.Arcade.NET.Sdk.csproj" />
<Project Path="src/sdk/Sdk/Geekeey.Arcade.NET.Sdk.Razor/Geekeey.Arcade.NET.Sdk.Razor.csproj" />
<Project Path="src/sdk/Sdk/Geekeey.Arcade.NET.Sdk.Test/Geekeey.Arcade.NET.Sdk.Test.csproj" />
<Project Path="src/sdk/Sdk/Geekeey.Arcade.NET.Sdk.Web/Geekeey.Arcade.NET.Sdk.Web.csproj" />
<Project Path="src/sdk.tests/Geekeey.Arcade.NET.Tests.csproj" /> <Project Path="src/sdk.tests/Geekeey.Arcade.NET.Tests.csproj" />
</Solution> </Solution>

View file

@ -19,5 +19,18 @@ public static class DirectoryExtensions
File.Copy(file, Path.Combine(destinationDirectory, Path.GetRelativePath(sourceDirectory, file))); File.Copy(file, Path.Combine(destinationDirectory, Path.GetRelativePath(sourceDirectory, file)));
} }
} }
public static string? GetDirectoryNameOfFileAbove(string directory, string file)
{
for (var current = new DirectoryInfo(directory); current is not null; current = current.Parent)
{
if (File.Exists(Path.Combine(current.FullName, file)))
{
return current.FullName;
}
}
return null;
}
} }
} }

View file

@ -0,0 +1,16 @@
// Copyright (c) The Geekeey Authors
// SPDX-License-Identifier: EUPL-1.2
namespace Geekeey.Arcade.NET.Tests;
public static class FileExtensions
{
extension(File)
{
public static string? GetPathOfFileAbove(string directory, string file)
{
var directoryName = Directory.GetDirectoryNameOfFileAbove(directory, file);
return directoryName is null ? null : Path.Combine(directoryName, file);
}
}
}

View file

@ -2,14 +2,15 @@
<PropertyGroup> <PropertyGroup>
<TargetFramework>net10.0</TargetFramework> <TargetFramework>net10.0</TargetFramework>
<IsPackable>false</IsPackable> <IsPackable>false</IsPackable>
<IsTestProject>true</IsTestProject>
<OutputType>Exe</OutputType> <OutputType>Exe</OutputType>
<DefaultItemExcludes>$(DefaultItemExcludes);$(MSBuildProjectDirectory)/fixtures/**/*</DefaultItemExcludes>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="TUnit" /> <PackageReference Include="TUnit" />
<Compile Remove="fixtures/**/*.cs" /> </ItemGroup>
<None Remove="fixtures/**/*" />
<Content Include="fixtures/**/*" CopyToOutputDirectory="PreserveNewest" /> <ItemGroup>
<TestingPlatformCommandLineOptionDefault Include="results-directory" Value="$([System.IO.Path]::Combine('$(ArtifactsPath)', 'log'))" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View file

@ -0,0 +1,97 @@
// Copyright (c) The Geekeey Authors
// SPDX-License-Identifier: EUPL-1.2
using System.Text.Json;
using System.Text.Json.Serialization;
using Geekeey.Arcade.NET.Tests.Shared;
namespace Geekeey.Arcade.NET.Tests;
internal sealed class SdkNuGetTests
{
private static Dictionary<string, string> Feed { get; set; } = [];
[Before(Class)]
public static async Task BuildSdkBeforeTest()
{
var path = Directory.GetDirectoryNameOfFileAbove(AppContext.BaseDirectory, "global.json")
?? throw new InvalidOperationException("Could not locate the repository root containing global.json.");
var package = Path.Combine(path, "artifacts", "package", "release");
File.Delete(Path.Combine(package, "Geekeey.Arcade.NET.Sdk.Test.1.0.0.nupkg"));
var command = Command.WithWorkingDirectory(path)
.WithThrowOnExit()
.WithEnvironment("DOTNET_CLI_DO_NOT_USE_MSBUILD_SERVER", "1");
await command.RunAsync("dotnet", "pack", Path.Combine("src", "sdk", "Sdk.slnx"));
var search = await command.RunAsync("dotnet", "package", "search", "--format=json", "--source", package, "Geekeey.Arcade.NET.Sdk");
var result = JsonSerializer.Deserialize<PackageSearchResponse>(search.StdOut)
?? throw new InvalidOperationException("The package search response was empty.");
Feed = result.SearchResults
.SelectMany(static result => result.Packages)
.ToDictionary(static result => result.Id, static result => result.LatestVersion);
}
[Test]
[Arguments("Geekeey.Arcade.NET.Sdk", "base")]
[Arguments("Geekeey.Arcade.NET.Sdk.Razor", "razor")]
[Arguments("Geekeey.Arcade.NET.Sdk.Web", "web")]
[Arguments("Geekeey.Arcade.NET.Sdk.Test", "test")]
public async Task DotNetBuildCanRestoreSdkFromNuGet(string packageId, string name)
{
// Arrange
using var project = await CreateProjectFromTemplate(packageId, name);
// Act
var output = await project.RunAsync("dotnet", "build");
// Assert
Console.WriteLine(output.StdOut);
Console.WriteLine(output.StdErr);
await Assert.That(output.ExitCode).IsZero();
await Assert.That(project.File("bin", "Debug", "net10.0", "project.dll")).Exists();
}
[Test]
[Arguments("Geekeey.Arcade.NET.Sdk", "base")]
[Arguments("Geekeey.Arcade.NET.Sdk.Razor", "razor")]
public async Task DotNetPackCanRestoreSdkFromNuGet(string sdkPackageId, string name)
{
// Arrange
using var project = await CreateProjectFromTemplate(sdkPackageId, name);
// Act
var output = await project.RunAsync("dotnet", "pack");
// Assert
await Assert.That(output.ExitCode).IsZero();
await Assert.That(project.File("bin", "Release", "My.Package.1.0.0.nupkg")).Exists();
}
private static async Task<Project> CreateProjectFromTemplate(string packageId, string name)
{
var repository = Directory.GetDirectoryNameOfFileAbove(AppContext.BaseDirectory, "global.json")
?? throw new InvalidOperationException("Could not locate the repository root containing global.json.");
var package = Path.Combine(repository, "artifacts", "package", "release");
var project = new Project(Directory.CreateDirectory(Path.Combine(Path.GetTempPath(), $"arcade-sdk-test-{Guid.NewGuid():N}")));
project.CopyFrom(Path.Combine(repository, "src", "sdk.tests", "fixtures", name));
await project.Template("nuget.config", "{0}", package);
await project.Template("project.csproj", "{0}", Feed[packageId]);
return project;
}
private sealed record PackageSearchResponse(
[property: JsonPropertyName("searchResult")]
IReadOnlyList<PackageSearchSource> SearchResults);
private sealed record PackageSearchSource(
[property: JsonPropertyName("packages")]
IReadOnlyList<PackageSearchPackage> Packages);
private sealed record PackageSearchPackage(
[property: JsonPropertyName("id")] string Id,
[property: JsonPropertyName("latestVersion")]
string LatestVersion);
}

View file

@ -1,168 +0,0 @@
// 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",
["TUNIT_DISABLE_GITHUB_REPORTER"] = "true"
}
};
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);
}
}
}

View file

@ -0,0 +1,74 @@
// Copyright (c) The Geekeey Authors
// SPDX-License-Identifier: EUPL-1.2
using System.Diagnostics;
namespace Geekeey.Arcade.NET.Tests.Shared;
internal sealed record Command(string WorkingDirectory, IEnumerable<KeyValuePair<string, string>> Environment)
{
public bool ThrowOnExit { get; init; }
public static Command WithWorkingDirectory(string path)
{
return new Command(path, []);
}
public Command WithEnvironment(string name, string value)
{
return WithEnvironment([new KeyValuePair<string, string>(name, value)]);
}
public Command WithEnvironment(IEnumerable<KeyValuePair<string, string>> environment)
{
var env = new Dictionary<string, string>(Environment);
foreach (var pair in environment)
{
env[pair.Key] = pair.Value;
}
return this with { Environment = env };
}
public Command WithThrowOnExit()
{
return this with { ThrowOnExit = true };
}
public async Task<CommandOutput> RunAsync(string exe, params IReadOnlyList<string> args)
{
ArgumentNullException.ThrowIfNull(args);
var startInfo = new ProcessStartInfo(exe)
{
WorkingDirectory = WorkingDirectory,
RedirectStandardOutput = true,
RedirectStandardError = true,
UseShellExecute = false,
CreateNoWindow = true
};
foreach (var argument in args)
{
startInfo.ArgumentList.Add(argument);
}
foreach (var pair in Environment)
{
startInfo.Environment[pair.Key] = pair.Value;
}
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();
_ = ThrowOnExit && process.ExitCode is not 0
? throw new InvalidOperationException($"'{exe}' exited with code {process.ExitCode}.{System.Environment.NewLine}{error}{output}")
: 0;
return new CommandOutput(output, error, process.ExitCode);
}
}

View file

@ -0,0 +1,6 @@
// Copyright (c) The Geekeey Authors
// SPDX-License-Identifier: EUPL-1.2
namespace Geekeey.Arcade.NET.Tests.Shared;
internal sealed record CommandOutput(string StdOut, string StdErr, int ExitCode);

View file

@ -0,0 +1,69 @@
// Copyright (c) The Geekeey Authors
// SPDX-License-Identifier: EUPL-1.2
namespace Geekeey.Arcade.NET.Tests.Shared;
internal sealed class Project : IDisposable
{
private readonly DirectoryInfo _root;
private readonly DirectoryInfo _source;
private readonly DirectoryInfo _packages;
private readonly Command _shell;
public Project(DirectoryInfo root)
{
_root = root;
_source = _root.CreateSubdirectory("src");
_packages = _root.CreateSubdirectory("packages");
_shell = Command.WithWorkingDirectory(_source.FullName)
.WithEnvironment("DOTNET_CLI_DO_NOT_USE_MSBUILD_SERVER", "1")
.WithEnvironment("TUNIT_DISABLE_GITHUB_REPORTER", "true")
.WithEnvironment("NUGET_PACKAGES", _packages.FullName);
}
public string Path
{
get => _source.FullName;
}
public Task<CommandOutput> RunAsync(string exe, params IReadOnlyList<string> args)
{
return _shell.RunAsync(exe, args);
}
public void CopyFrom(string path)
{
System.IO.Directory.Copy(path, _source.FullName);
}
public FileInfo File(params IReadOnlyList<string> path)
{
return new FileInfo(System.IO.Path.Combine([_source.FullName, .. path]));
}
public DirectoryInfo Directory(params IReadOnlyList<string> path)
{
return new DirectoryInfo(System.IO.Path.Combine([_source.FullName, .. path]));
}
public async Task Template(string path, string from, string insert)
{
string content;
var file = File(path);
using (var text = file.OpenText())
{
content = await text.ReadToEndAsync();
}
content = content.Replace(from, insert, StringComparison.Ordinal);
await using var writer = new StreamWriter(file.OpenWrite());
await writer.WriteAsync(content);
}
public void Dispose()
{
_root.Delete(recursive: true);
}
}

View file

@ -0,0 +1,7 @@
<configuration>
<packageSources>
<clear />
<add key="local" value="{0}" />
<add key="nuget.org" value="https://api.nuget.org/v3/index.json" />
</packageSources>
</configuration>

View file

@ -0,0 +1,7 @@
<Project Sdk="Geekeey.Arcade.NET.Sdk/{0}">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<PackageId>My.Package</PackageId>
<NoWarn>IDE0055</NoWarn>
</PropertyGroup>
</Project>

View file

@ -0,0 +1,7 @@
<configuration>
<packageSources>
<clear />
<add key="local" value="{0}" />
<add key="nuget.org" value="https://api.nuget.org/v3/index.json" />
</packageSources>
</configuration>

View file

@ -0,0 +1,7 @@
<Project Sdk="Geekeey.Arcade.NET.Sdk.Razor/{0}">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<PackageId>My.Package</PackageId>
<NoWarn>IDE0055</NoWarn>
</PropertyGroup>
</Project>

View file

@ -1,5 +0,0 @@
<Project>
<PropertyGroup>
<ArtifactsPath>$(MSBuildProjectDirectory)/artifacts/</ArtifactsPath>
</PropertyGroup>
</Project>

View file

@ -0,0 +1,7 @@
<configuration>
<packageSources>
<clear />
<add key="local" value="{0}" />
<add key="nuget.org" value="https://api.nuget.org/v3/index.json" />
</packageSources>
</configuration>

View file

@ -0,0 +1,11 @@
<Project Sdk="Geekeey.Arcade.NET.Sdk.Test/{0}">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<PackageId>My.Package</PackageId>
<OutputType>Exe</OutputType>
<NoWarn>IDE0055</NoWarn>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="TUnit" Version="1.76.28" />
</ItemGroup>
</Project>

View file

@ -0,0 +1,7 @@
<configuration>
<packageSources>
<clear />
<add key="local" value="{0}" />
<add key="nuget.org" value="https://api.nuget.org/v3/index.json" />
</packageSources>
</configuration>

Some files were not shown because too many files have changed in this diff Show more