feat(release): add reproducible signed artifacts (#19)
This commit is contained in:
@@ -0,0 +1,185 @@
|
||||
using System.Reflection;
|
||||
using System.Text.Json;
|
||||
using System.Xml.Linq;
|
||||
using FinalFactory.Rendezvous.Client;
|
||||
using FinalFactory.Rendezvous.Contracts;
|
||||
using FinalFactory.Rendezvous.Server.Operations;
|
||||
|
||||
namespace FinalFactory.Rendezvous.Tests.Release;
|
||||
|
||||
public sealed class ReleaseCompatibilityTests
|
||||
{
|
||||
[Fact]
|
||||
public void CentralVersionsRuntimeWindowAndPublishedMatrixStayAligned()
|
||||
{
|
||||
string root = FindRepositoryRoot();
|
||||
XDocument versions = XDocument.Load(Path.Combine(root, "eng/Versions.props"));
|
||||
string releaseVersion = Property(versions, "RendezvousVersion");
|
||||
int releaseMajor = int.Parse(Property(versions, "RendezvousMajorVersion"), System.Globalization.CultureInfo.InvariantCulture);
|
||||
string[] numericVersion = releaseVersion.Split(['-', '+'], 2)[0].Split('.');
|
||||
Assert.Equal(releaseMajor, int.Parse(numericVersion[0], System.Globalization.CultureInfo.InvariantCulture));
|
||||
Assert.Equal(Property(versions, "RendezvousMinorVersion"), numericVersion[1]);
|
||||
Assert.Equal(Property(versions, "RendezvousPatchVersion"), numericVersion[2]);
|
||||
int httpVersion = int.Parse(Property(versions, "HttpContractVersion"), System.Globalization.CultureInfo.InvariantCulture);
|
||||
int udpVersion = int.Parse(Property(versions, "UdpContractVersion"), System.Globalization.CultureInfo.InvariantCulture);
|
||||
int ticketVersion = int.Parse(Property(versions, "ConnectionTicketFormatVersion"), System.Globalization.CultureInfo.InvariantCulture);
|
||||
|
||||
Assert.Equal(releaseVersion, typeof(RendezvousPublisherClient).Assembly.GetCustomAttribute<AssemblyInformationalVersionAttribute>()!.InformationalVersion.Split('+')[0]);
|
||||
Assert.Equal(releaseVersion, typeof(ContractLimits).Assembly.GetCustomAttribute<AssemblyInformationalVersionAttribute>()!.InformationalVersion.Split('+')[0]);
|
||||
Assert.Equal(ContractLimits.ContractVersion, httpVersion);
|
||||
|
||||
OperatorCompatibilityResponse runtime = ReleaseCompatibility.CreateResponse();
|
||||
Assert.Equal(releaseVersion, runtime.ServerVersion);
|
||||
Assert.Equal(Property(versions, "MinimumClientVersion"), runtime.MinimumClientVersion);
|
||||
Assert.Equal(int.Parse(Property(versions, "MaximumClientMajorVersion"), System.Globalization.CultureInfo.InvariantCulture), runtime.MaximumClientMajorVersion);
|
||||
Assert.Equal([httpVersion], runtime.HttpContractVersions);
|
||||
Assert.Equal([udpVersion], runtime.UdpContractVersions);
|
||||
Assert.Equal([ticketVersion], runtime.ConnectionTicketFormatVersions);
|
||||
Assert.Equal(int.Parse(Property(versions, "LiteNetLibMajorVersion"), System.Globalization.CultureInfo.InvariantCulture), runtime.LiteNetLibMajorVersion);
|
||||
Assert.Equal("exact-per-tenant", runtime.GameplayProtocolCompatibility);
|
||||
|
||||
using JsonDocument matrix = JsonDocument.Parse(File.ReadAllText(Path.Combine(root, "docs/releases/compatibility.json")));
|
||||
JsonElement document = matrix.RootElement;
|
||||
Assert.Equal(releaseVersion, document.GetProperty("release").GetString());
|
||||
Assert.Equal(releaseVersion, document.GetProperty("packages").GetProperty("FinalFactory.Rendezvous.Client").GetString());
|
||||
Assert.Equal(releaseVersion, document.GetProperty("packages").GetProperty("FinalFactory.Rendezvous.Contracts").GetString());
|
||||
Assert.Equal(runtime.MinimumClientVersion, document.GetProperty("server").GetProperty("minimumClientVersion").GetString());
|
||||
Assert.Equal(runtime.MaximumClientMajorVersion, document.GetProperty("server").GetProperty("maximumClientMajorVersion").GetInt32());
|
||||
Assert.Equal(httpVersion, Assert.Single(document.GetProperty("contracts").GetProperty("http").EnumerateArray()).GetInt32());
|
||||
Assert.Equal(udpVersion, Assert.Single(document.GetProperty("contracts").GetProperty("udp").EnumerateArray()).GetInt32());
|
||||
Assert.Equal(ticketVersion, Assert.Single(document.GetProperty("contracts").GetProperty("connectionTicket").EnumerateArray()).GetInt32());
|
||||
Assert.Equal(runtime.GameplayProtocolCompatibility, document.GetProperty("contracts").GetProperty("gameplay").GetString());
|
||||
Assert.Equal("LiteNetLib", document.GetProperty("transport").GetProperty("package").GetString());
|
||||
Assert.Equal(Property(versions, "LiteNetLibVersion"), document.GetProperty("transport").GetProperty("version").GetString());
|
||||
Assert.Equal(runtime.LiteNetLibMajorVersion, document.GetProperty("transport").GetProperty("major").GetInt32());
|
||||
|
||||
foreach ((string consumer, string fixture) in new[]
|
||||
{
|
||||
("SpaceGame", "spacegame/SpaceGame.Rendezvous.Consumer.csproj"),
|
||||
("Unscouted", "unscouted/Unscouted.Rendezvous.Consumer.csproj"),
|
||||
})
|
||||
{
|
||||
XDocument fixtureProject = XDocument.Load(Path.Combine(root, "tests/consumers", fixture));
|
||||
Assert.Equal(
|
||||
fixtureProject.Descendants("TargetFramework").Single().Value,
|
||||
document.GetProperty("consumers").GetProperty(consumer).GetString());
|
||||
}
|
||||
|
||||
string snapshots = Path.Combine(root, $"tests/FinalFactory.Rendezvous.Tests/TestData/Contracts/v{releaseMajor}");
|
||||
Assert.True(File.Exists(Path.Combine(snapshots, "client-public-api.txt")));
|
||||
Assert.True(File.Exists(Path.Combine(snapshots, "contracts-public-api.txt")));
|
||||
Assert.True(File.Exists(Path.Combine(root, $"docs/api/rendezvous-v{httpVersion}.json")));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ReleaseDefinitionIsImmutableTagOnlyAndDocumentsRequiredNotes()
|
||||
{
|
||||
string root = FindRepositoryRoot();
|
||||
XDocument versions = XDocument.Load(Path.Combine(root, "eng/Versions.props"));
|
||||
string releaseVersion = Property(versions, "RendezvousVersion");
|
||||
string workflow = File.ReadAllText(Path.Combine(root, ".gitea/workflows/release.yml"));
|
||||
Assert.Contains("tags:", workflow, StringComparison.Ordinal);
|
||||
Assert.Contains("RELEASE_TOKEN", workflow, StringComparison.Ordinal);
|
||||
Assert.Contains("RELEASE_USERNAME", workflow, StringComparison.Ordinal);
|
||||
Assert.Contains("COSIGN_PRIVATE_KEY", workflow, StringComparison.Ordinal);
|
||||
Assert.DoesNotContain(":latest", workflow, StringComparison.OrdinalIgnoreCase);
|
||||
Assert.DoesNotContain("skip-duplicate", workflow, StringComparison.OrdinalIgnoreCase);
|
||||
Assert.Contains("check-compatibility.sh", workflow, StringComparison.Ordinal);
|
||||
Assert.Contains("--platform linux/amd64", workflow, StringComparison.Ordinal);
|
||||
Assert.Contains("ignore-unfixed: false", workflow, StringComparison.Ordinal);
|
||||
Assert.Contains("format: spdx-json", workflow, StringComparison.Ordinal);
|
||||
Assert.Contains("normalize-container-sbom", workflow, StringComparison.Ordinal);
|
||||
Assert.Contains("finalize-release-candidate.sh", workflow, StringComparison.Ordinal);
|
||||
Assert.Contains("verify-real-consumers.sh", workflow, StringComparison.Ordinal);
|
||||
Assert.Contains("RENDEZVOUS_RELEASE_BUILDER", workflow, StringComparison.Ordinal);
|
||||
Assert.Contains("--image-id", workflow, StringComparison.Ordinal);
|
||||
|
||||
string publisher = File.ReadAllText(Path.Combine(root, "scripts/publish-release.sh"));
|
||||
Assert.Contains("expected_image_id", publisher, StringComparison.Ordinal);
|
||||
Assert.Contains("finalize-signing-ready-release.sh", publisher, StringComparison.Ordinal);
|
||||
|
||||
XDocument packages = XDocument.Load(Path.Combine(root, "Directory.Packages.props"));
|
||||
XElement liteNetLib = Assert.Single(packages.Descendants("PackageVersion"), static item => (string?)item.Attribute("Include") == "LiteNetLib");
|
||||
Assert.Equal("[$(LiteNetLibVersion)]", (string?)liteNetLib.Attribute("Version"));
|
||||
|
||||
string changelog = File.ReadAllText(Path.Combine(root, "CHANGELOG.md"));
|
||||
Assert.Contains("### Compatibility", changelog, StringComparison.Ordinal);
|
||||
Assert.Contains("### Security and configuration", changelog, StringComparison.Ordinal);
|
||||
Assert.Contains("### Migration", changelog, StringComparison.Ordinal);
|
||||
Assert.DoesNotContain($"{releaseVersion} - Unreleased", changelog, StringComparison.Ordinal);
|
||||
|
||||
foreach (string consumer in new[] { "spacegame", "unscouted" })
|
||||
{
|
||||
string consumerDirectory = Path.Combine(root, "tests/consumers", consumer);
|
||||
string projectPath = Assert.Single(Directory.GetFiles(consumerDirectory, "*.csproj"));
|
||||
XDocument consumerProject = XDocument.Load(projectPath);
|
||||
XElement[] references = consumerProject.Descendants("PackageReference")
|
||||
.Where(static item => ((string?)item.Attribute("Include"))?.StartsWith("FinalFactory.Rendezvous.", StringComparison.Ordinal) == true)
|
||||
.ToArray();
|
||||
Assert.Equal(2, references.Length);
|
||||
Assert.All(references, static reference =>
|
||||
Assert.Equal("[$(RendezvousPackageVersion)]", (string?)reference.Attribute("Version")));
|
||||
|
||||
Assert.Equal(
|
||||
"false",
|
||||
consumerProject.Descendants("RestorePackagesWithLockFile").Single().Value);
|
||||
}
|
||||
|
||||
XDocument unscouted = XDocument.Load(Path.Combine(
|
||||
root,
|
||||
"tests/consumers/unscouted/Unscouted.Rendezvous.Consumer.csproj"));
|
||||
XElement directTransport = Assert.Single(
|
||||
unscouted.Descendants("PackageReference"),
|
||||
static item => (string?)item.Attribute("Include") == "LiteNetLib");
|
||||
Assert.Equal("[2.1.4]", (string?)directTransport.Attribute("Version"));
|
||||
|
||||
using JsonDocument consumers = JsonDocument.Parse(
|
||||
File.ReadAllText(Path.Combine(root, "eng/consumer-revisions.json")));
|
||||
JsonElement[] pinnedConsumers = consumers.RootElement.GetProperty("consumers")
|
||||
.EnumerateArray()
|
||||
.ToArray();
|
||||
Assert.Equal(
|
||||
["SpaceGame", "Unscouted"],
|
||||
pinnedConsumers.Select(static item => item.GetProperty("name").GetString()!).ToArray());
|
||||
Assert.All(pinnedConsumers, static item =>
|
||||
Assert.Matches("^[0-9a-f]{40}$", item.GetProperty("revision").GetString()));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ServerSourceManifestIsCompleteSortedAndDeterministic()
|
||||
{
|
||||
string root = FindRepositoryRoot();
|
||||
string projectDirectory = Path.Combine(root, "src/FinalFactory.Rendezvous.Server");
|
||||
XDocument project = XDocument.Load(Path.Combine(projectDirectory, "FinalFactory.Rendezvous.Server.csproj"));
|
||||
string[] declared = project.Descendants("Compile")
|
||||
.Select(static item => (string)item.Attribute("Include")!)
|
||||
.ToArray();
|
||||
string[] actual = Directory.GetFiles(projectDirectory, "*.cs", SearchOption.AllDirectories)
|
||||
.Where(static path => !path.Contains($"{Path.DirectorySeparatorChar}bin{Path.DirectorySeparatorChar}", StringComparison.Ordinal)
|
||||
&& !path.Contains($"{Path.DirectorySeparatorChar}obj{Path.DirectorySeparatorChar}", StringComparison.Ordinal))
|
||||
.Select(path => Path.GetRelativePath(projectDirectory, path).Replace(Path.DirectorySeparatorChar, '/'))
|
||||
.Order(StringComparer.Ordinal)
|
||||
.ToArray();
|
||||
|
||||
Assert.Equal(actual, declared);
|
||||
}
|
||||
|
||||
private static string Property(XDocument document, string name) =>
|
||||
document.Descendants(name).Single().Value;
|
||||
|
||||
private static string FindRepositoryRoot()
|
||||
{
|
||||
DirectoryInfo? directory = new(AppContext.BaseDirectory);
|
||||
while (directory is not null)
|
||||
{
|
||||
if (File.Exists(Path.Combine(directory.FullName, "Rendezvous.slnx")))
|
||||
{
|
||||
return directory.FullName;
|
||||
}
|
||||
|
||||
directory = directory.Parent;
|
||||
}
|
||||
|
||||
throw new DirectoryNotFoundException("Could not locate repository root.");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user