up
This commit is contained in:
@@ -0,0 +1,85 @@
|
||||
using System.Text.Json;
|
||||
using FluentAssertions;
|
||||
using Xunit;
|
||||
|
||||
namespace StellaOps.Reachability.FixtureTests;
|
||||
|
||||
public class ReachbenchEvaluationHarnessTests
|
||||
{
|
||||
private static readonly string RepoRoot = LocateRepoRoot();
|
||||
private static readonly string CasesRoot = Path.Combine(
|
||||
RepoRoot,
|
||||
"tests",
|
||||
"reachability",
|
||||
"fixtures",
|
||||
"reachbench-2025-expanded",
|
||||
"cases");
|
||||
|
||||
public static IEnumerable<object[]> CaseIds()
|
||||
{
|
||||
return Directory.EnumerateDirectories(CasesRoot)
|
||||
.OrderBy(path => path, StringComparer.Ordinal)
|
||||
.Select(path => new object[] { Path.GetFileName(path)! });
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(CaseIds))]
|
||||
public void GroundTruthStatusesMatchVariantIntent(string caseId)
|
||||
{
|
||||
var caseJsonPath = Path.Combine(CasesRoot, caseId, "case.json");
|
||||
File.Exists(caseJsonPath).Should().BeTrue();
|
||||
|
||||
using var caseDoc = JsonDocument.Parse(File.ReadAllBytes(caseJsonPath));
|
||||
var groundTruth = caseDoc.RootElement.GetProperty("ground_truth");
|
||||
|
||||
groundTruth.GetProperty("reachable_variant")
|
||||
.GetProperty("status")
|
||||
.GetString()
|
||||
.Should()
|
||||
.Be("affected", $"{caseId} reachable variant should be marked affected for evaluation harness");
|
||||
|
||||
groundTruth.GetProperty("unreachable_variant")
|
||||
.GetProperty("status")
|
||||
.GetString()
|
||||
.Should()
|
||||
.Be("not_affected", $"{caseId} unreachable variant should be marked not_affected for evaluation harness");
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(CaseIds))]
|
||||
public void TruthGraphsAlignWithExpectedReachability(string caseId)
|
||||
{
|
||||
var reachablePaths = CountTruthPaths(caseId, "reachable");
|
||||
reachablePaths.Should().BeGreaterThan(0, $"{caseId} reachable variant should expose at least one execution path");
|
||||
|
||||
var unreachablePaths = CountTruthPaths(caseId, "unreachable");
|
||||
unreachablePaths.Should().Be(0, $"{caseId} unreachable variant should have no execution paths");
|
||||
}
|
||||
|
||||
private static int CountTruthPaths(string caseId, string variant)
|
||||
{
|
||||
var truthPath = Path.Combine(CasesRoot, caseId, "images", variant, "reachgraph.truth.json");
|
||||
File.Exists(truthPath).Should().BeTrue();
|
||||
|
||||
using var truthDoc = JsonDocument.Parse(File.ReadAllBytes(truthPath));
|
||||
var paths = truthDoc.RootElement.GetProperty("paths");
|
||||
paths.ValueKind.Should().Be(JsonValueKind.Array, $"{caseId}:{variant} should list truth paths as an array");
|
||||
return paths.GetArrayLength();
|
||||
}
|
||||
|
||||
private static string LocateRepoRoot()
|
||||
{
|
||||
var current = new DirectoryInfo(AppContext.BaseDirectory);
|
||||
while (current != null)
|
||||
{
|
||||
if (File.Exists(Path.Combine(current.FullName, "Directory.Build.props")))
|
||||
{
|
||||
return current.FullName;
|
||||
}
|
||||
|
||||
current = current.Parent;
|
||||
}
|
||||
|
||||
throw new InvalidOperationException("Cannot locate repository root (missing Directory.Build.props).");
|
||||
}
|
||||
}
|
||||
@@ -122,7 +122,7 @@ public class ReachbenchFixtureTests
|
||||
paths.ValueKind.Should().Be(JsonValueKind.Array);
|
||||
}
|
||||
|
||||
private static string LocateRepoRoot()
|
||||
internal static string LocateRepoRoot()
|
||||
{
|
||||
var current = new DirectoryInfo(AppContext.BaseDirectory);
|
||||
while (current != null)
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
using System.Text.Json;
|
||||
using FluentAssertions;
|
||||
using StellaOps.Replay.Core;
|
||||
using Xunit;
|
||||
|
||||
namespace StellaOps.Replay.Core.Tests;
|
||||
|
||||
public sealed class CanonicalJsonTests
|
||||
{
|
||||
[Fact]
|
||||
public void CanonicalJson_OrdersPropertiesLexicographically()
|
||||
{
|
||||
var payload = new
|
||||
{
|
||||
zeta = 1,
|
||||
alpha = new { z = 9, m = 7 },
|
||||
list = new[] { new { y = 2, x = 1 } }
|
||||
};
|
||||
|
||||
var canonical = CanonicalJson.Serialize(payload);
|
||||
|
||||
canonical.Should().Be("{\"alpha\":{\"m\":7,\"z\":9},\"list\":[{\"x\":1,\"y\":2}],\"zeta\":1}");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CanonicalJson_PreservesNumbersAndBooleans()
|
||||
{
|
||||
var payload = JsonSerializer.Deserialize<JsonElement>("{\"b\":true,\"a\":1.25}");
|
||||
|
||||
var canonical = CanonicalJson.Serialize(payload);
|
||||
|
||||
canonical.Should().Be("{\"a\":1.25,\"b\":true}");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
using System.Text;
|
||||
using FluentAssertions;
|
||||
using StellaOps.Replay.Core;
|
||||
using Xunit;
|
||||
|
||||
namespace StellaOps.Replay.Core.Tests;
|
||||
|
||||
public sealed class DeterministicHashTests
|
||||
{
|
||||
[Fact]
|
||||
public void Sha256Hex_ComputesLowercaseDigest()
|
||||
{
|
||||
var digest = DeterministicHash.Sha256Hex("replay-core");
|
||||
|
||||
digest.Should().Be("a914f5ac6a57aab0189bb55bcb0ef6bcdbd86f77198c8669eab5ae38a325e41d");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MerkleRootHex_IsDeterministic()
|
||||
{
|
||||
var leaves = new[] { "alpha", "beta", "gamma" }
|
||||
.Select(Encoding.UTF8.GetBytes)
|
||||
.ToList();
|
||||
|
||||
var root = DeterministicHash.MerkleRootHex(leaves);
|
||||
|
||||
root.Should().Be("50298939464ed02cbf2b587250a55746b3422e133ac4f09b7e2b07869023bc9e");
|
||||
DeterministicHash.MerkleRootHex(leaves).Should().Be(root);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
using System;
|
||||
using System.Text;
|
||||
using FluentAssertions;
|
||||
using StellaOps.Replay.Core;
|
||||
using Xunit;
|
||||
|
||||
namespace StellaOps.Replay.Core.Tests;
|
||||
|
||||
public sealed class DsseEnvelopeTests
|
||||
{
|
||||
[Fact]
|
||||
public void BuildUnsigned_ProducesCanonicalPayload()
|
||||
{
|
||||
var manifest = new ReplayManifest
|
||||
{
|
||||
Scan = new ReplayScanMetadata
|
||||
{
|
||||
Id = "scan-123",
|
||||
Time = DateTimeOffset.UnixEpoch
|
||||
}
|
||||
};
|
||||
|
||||
var envelope = DssePayloadBuilder.BuildUnsigned(manifest);
|
||||
|
||||
envelope.PayloadType.Should().Be(DssePayloadBuilder.ReplayPayloadType);
|
||||
envelope.Signatures.Should().BeEmpty();
|
||||
|
||||
var payload = Convert.FromBase64String(envelope.Payload);
|
||||
var json = Encoding.UTF8.GetString(payload);
|
||||
|
||||
json.Should().Be("{\"reachability\":{\"graphs\":[],\"runtimeTraces\":[]},\"scan\":{\"id\":\"scan-123\",\"time\":\"1970-01-01T00:00:00+00:00\"},\"schemaVersion\":\"1.0\"}");
|
||||
envelope.DigestSha256.Should().Be(DeterministicHash.Sha256Hex(payload));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Formats.Tar;
|
||||
using System.IO;
|
||||
using FluentAssertions;
|
||||
using StellaOps.Replay.Core;
|
||||
using ZstdSharp;
|
||||
using Xunit;
|
||||
|
||||
namespace StellaOps.Replay.Core.Tests;
|
||||
|
||||
public sealed class ReplayBundleWriterTests
|
||||
{
|
||||
[Fact]
|
||||
public async Task WriteTarZstAsync_IsDeterministicAndSorted()
|
||||
{
|
||||
var entries = new[]
|
||||
{
|
||||
new ReplayBundleEntry("b.txt", "beta"u8.ToArray()),
|
||||
new ReplayBundleEntry("a.txt", "alpha"u8.ToArray())
|
||||
};
|
||||
|
||||
await using var buffer = new MemoryStream();
|
||||
var first = await ReplayBundleWriter.WriteTarZstAsync(entries, buffer, compressionLevel: 3);
|
||||
|
||||
var firstBytes = buffer.ToArray();
|
||||
|
||||
await using var buffer2 = new MemoryStream();
|
||||
var second = await ReplayBundleWriter.WriteTarZstAsync(entries.Reverse(), buffer2, compressionLevel: 3);
|
||||
|
||||
first.ZstSha256.Should().Be(second.ZstSha256);
|
||||
first.TarSha256.Should().Be(second.TarSha256);
|
||||
firstBytes.Should().Equal(buffer2.ToArray());
|
||||
|
||||
// Decompress and verify ordering/content
|
||||
buffer.Position = 0;
|
||||
await using var decompressed = new MemoryStream();
|
||||
await using (var decompress = new DecompressionStream(buffer, 16 * 1024, leaveOpen: true, enableMultiThreaded: false))
|
||||
{
|
||||
await decompress.CopyToAsync(decompressed);
|
||||
}
|
||||
|
||||
decompressed.Position = 0;
|
||||
var reader = new TarReader(decompressed, leaveOpen: true);
|
||||
var names = new List<string>();
|
||||
TarEntry? entry;
|
||||
while ((entry = reader.GetNextEntry()) != null)
|
||||
{
|
||||
names.Add(entry.Name);
|
||||
using var ms = new MemoryStream();
|
||||
entry.DataStream!.CopyTo(ms);
|
||||
var text = System.Text.Encoding.UTF8.GetString(ms.ToArray());
|
||||
text.Should().Be(entry.Name.StartsWith("a") ? "alpha" : "beta");
|
||||
}
|
||||
|
||||
names.Should().BeEquivalentTo(new[] { "a.txt", "b.txt" }, opts => opts.WithStrictOrdering());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void BuildCasUri_UsesPrefixAndShard()
|
||||
{
|
||||
ReplayBundleWriter.BuildCasUri("abcdef", null).Should().Be("cas://replay/ab/abcdef.tar.zst");
|
||||
ReplayBundleWriter.BuildCasUri("1234", "custom").Should().Be("cas://custom/12/1234.tar.zst");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
using FluentAssertions;
|
||||
using MongoDB.Bson.Serialization;
|
||||
using StellaOps.Replay.Core;
|
||||
using Xunit;
|
||||
|
||||
namespace StellaOps.Replay.Core.Tests;
|
||||
|
||||
public sealed class ReplayMongoModelsTests
|
||||
{
|
||||
[Fact]
|
||||
public void ReplayRunRecord_SerializesWithExpectedFields()
|
||||
{
|
||||
var record = new ReplayRunRecord
|
||||
{
|
||||
Id = "scan-1",
|
||||
ManifestHash = "sha256:abc",
|
||||
Status = "verified",
|
||||
Outputs = new ReplayRunOutputs { Sbom = "sha256:sbom", Findings = "sha256:findings", Vex = "sha256:vex" },
|
||||
Signatures = new() { new ReplaySignatureRecord { Profile = "FIPS", Verified = true } }
|
||||
};
|
||||
|
||||
var bson = record.ToBsonDocument();
|
||||
|
||||
bson.Should().ContainKey("_id");
|
||||
bson["manifestHash"].AsString.Should().Be("sha256:abc");
|
||||
bson["status"].AsString.Should().Be("verified");
|
||||
bson["outputs"].AsBsonDocument["sbom"].AsString.Should().Be("sha256:sbom");
|
||||
bson["signatures"].AsBsonArray.Should().HaveCount(1);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ReplayBundleRecord_UsesIdAsDigest()
|
||||
{
|
||||
var record = new ReplayBundleRecord { Id = "abc", Type = "input", Size = 10, Location = "cas://replay/ab/abc.tar.zst" };
|
||||
|
||||
var bson = record.ToBsonDocument();
|
||||
bson["_id"].AsString.Should().Be("abc");
|
||||
bson["type"].AsString.Should().Be("input");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ReplaySubjectRecord_StoresLayers()
|
||||
{
|
||||
var record = new ReplaySubjectRecord
|
||||
{
|
||||
OciDigest = "sha256:img",
|
||||
Layers = new()
|
||||
{
|
||||
new ReplayLayerRecord { LayerDigest = "l1", MerkleRoot = "m1", LeafCount = 2 },
|
||||
new ReplayLayerRecord { LayerDigest = "l2", MerkleRoot = "m2", LeafCount = 3 }
|
||||
}
|
||||
};
|
||||
|
||||
var doc = record.ToBsonDocument();
|
||||
doc["layers"].AsBsonArray.Should().HaveCount(2);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user