Remediate Ε-W3-T1 package-compat-fixtures review findings

Freeze *.rsbank as binary via .gitattributes; add a truncated additive_forward fixture proving the exact-size proof beats TooNew; enumerate the fixture dir to catch orphaned files; make fixture-size checks fatal instead of just logged; pin fixture version asserts as literals, not build-relative.
This commit is contained in:
2026-08-02 15:39:04 -04:00
parent 9521b5339f
commit 3fd3214ff8
8 changed files with 173 additions and 61 deletions
+57 -32
View File
@@ -67,13 +67,16 @@ static void writeBytes(const std::string& path, const std::vector<std::uint8_t>&
static_cast<std::streamsize>(bytes.size()));
}
// Copies a committed fixture to the path the verb will be pointed at. Fails loudly on an
// empty read: an unreadable corpus would otherwise let every "must refuse" suite pass.
static bool stageFixture(const Scratch& scratch, const char* fixture) {
// Copies a committed fixture to the path the verb will be pointed at. Fails loudly on a
// size mismatch (empty included): an unreadable OR mangled corpus would otherwise let
// every "must refuse" suite pass, since a garbled fixture still refuses, just not for
// the reason under test.
static bool stageFixture(const Scratch& scratch, const char* fixture,
std::size_t expectedSize) {
const std::vector<std::uint8_t> bytes = packageFixtureBytes(fixture);
if (bytes.empty()) {
std::printf("FAIL: fixture %s read as 0 bytes (path: %s)\n", fixture,
packageFixturePath(fixture).c_str());
if (bytes.size() != expectedSize) {
std::printf("FAIL: fixture %s is %zu bytes, expected %zu (path: %s)\n", fixture,
bytes.size(), expectedSize, packageFixturePath(fixture).c_str());
++g_fail;
return false;
}
@@ -116,7 +119,7 @@ static ImportLanding runImport(const Scratch& scratch, ReaSamplerSession& sessio
// importing and re-exporting it closes export -> import -> export over frozen bytes.
static void testV1FixtureReExportsByteIdenticalPayloads() {
Scratch scratch("roundtrip");
if (!stageFixture(scratch, "v1_shipping.rsbank")) return;
if (!stageFixture(scratch, "v1_shipping.rsbank", 1207)) return;
ReaSamplerSession session;
int births = 0;
@@ -128,7 +131,7 @@ static void testV1FixtureReExportsByteIdenticalPayloads() {
const std::vector<std::vector<std::uint8_t>> sourcePayloads =
payloadsOf(packageFixtureBytes("v1_shipping.rsbank"));
CHECK(sourcePayloads.size() == 1);
if (sourcePayloads.size() != 1) { CHECK(false); return; }
// The landed file is the package's payload verbatim — the first half of the claim.
const std::string landed = scratch.bankDir() + "/" + landing.plan.entries[0].destFileName;
@@ -153,7 +156,7 @@ static void testV1FixtureReExportsByteIdenticalPayloads() {
static void testRefuseFixtureRefusesTheWholeImportAndNamesTheWriter() {
Scratch scratch("refuse");
if (!stageFixture(scratch, "refuse_structural.rsbank")) return;
if (!stageFixture(scratch, "refuse_structural.rsbank", 1207)) return;
ReaSamplerSession session;
const BankBook before = session.book();
@@ -162,8 +165,10 @@ static void testRefuseFixtureRefusesTheWholeImportAndNamesTheWriter() {
session.book(), kTag, journal);
CHECK(landing.outcome == ImportOutcome::TooNew);
CHECK(landing.header.formatVersion == package::kPackageFormatVersion + 1);
CHECK(landing.header.minReaderVersion == package::kPackageFormatVersion + 1);
// Literal, not kPackageFormatVersion-relative: this is the FIXTURE's frozen pair
// (2/2), not this build's (see test_package_compat.cpp's file header note).
CHECK(landing.header.formatVersion == 2);
CHECK(landing.header.minReaderVersion == 2);
CHECK(landing.header.writerVersion == "1.9.0");
// Nothing planned, nothing on disk, nothing in the index.
CHECK(landing.plan.entries.empty());
@@ -173,18 +178,27 @@ static void testRefuseFixtureRefusesTheWholeImportAndNamesTheWriter() {
// The same eight cuts test_package_compat classifies, driven through the verb's
// incremental prefix reader — the one caller that can ask requiredPrefixSize for more
// bytes than the file holds.
static const char* kTruncationFixtures[] = {
"trunc_magic.rsbank", "trunc_version_pair.rsbank",
"trunc_writer_semver.rsbank", "trunc_manifest_length.rsbank",
"trunc_manifest_body.rsbank", "trunc_payload_start.rsbank",
"trunc_payload_middle.rsbank", "trunc_one_short.rsbank",
// bytes than the file holds. Sizes match test_package_compat.cpp's kTruncations.
struct TruncationFixture {
const char* file;
std::size_t size;
};
static const TruncationFixture kTruncationFixtures[] = {
{"trunc_magic.rsbank", 2},
{"trunc_version_pair.rsbank", 10},
{"trunc_writer_semver.rsbank", 18},
{"trunc_manifest_length.rsbank", 23},
{"trunc_manifest_body.rsbank", 466},
{"trunc_payload_start.rsbank", 907},
{"trunc_payload_middle.rsbank", 1057},
{"trunc_one_short.rsbank", 1206},
};
static void testEveryTruncationRefusesTheImportAsMalformed() {
for (const char* fixture : kTruncationFixtures) {
Scratch scratch(std::string("trunc_") + fixture);
if (!stageFixture(scratch, fixture)) continue;
for (const TruncationFixture& fixture : kTruncationFixtures) {
Scratch scratch(std::string("trunc_") + fixture.file);
if (!stageFixture(scratch, fixture.file, fixture.size)) continue;
ReaSamplerSession session;
const BankBook before = session.book();
@@ -192,7 +206,7 @@ static void testEveryTruncationRefusesTheImportAsMalformed() {
const ImportLanding landing = landPackage(scratch.importPath(), scratch.projectDir(),
session.book(), kTag, journal);
if (landing.outcome != ImportOutcome::Malformed) {
std::printf("FAIL: %s imported as outcome %d, expected Malformed\n", fixture,
std::printf("FAIL: %s imported as outcome %d, expected Malformed\n", fixture.file,
static_cast<int>(landing.outcome));
++g_fail;
}
@@ -202,19 +216,30 @@ static void testEveryTruncationRefusesTheImportAsMalformed() {
}
}
static const char* kHostileFixtures[] = {
"hostile_name_dotdot.rsbank", "hostile_name_parent_slash.rsbank",
"hostile_name_parent_backslash.rsbank", "hostile_name_subdir_slash.rsbank",
"hostile_name_drive_absolute.rsbank", "hostile_name_unc_absolute.rsbank",
"hostile_path_dotdot_slash.rsbank", "hostile_path_dotdot_backslash.rsbank",
"hostile_path_rooted.rsbank", "hostile_path_drive_absolute.rsbank",
"hostile_path_unc_absolute.rsbank",
// Sizes match test_package_compat.cpp's kHostiles.
struct HostileFixtureFile {
const char* file;
std::size_t size;
};
static const HostileFixtureFile kHostileFixtures[] = {
{"hostile_name_dotdot.rsbank", 624},
{"hostile_name_parent_slash.rsbank", 633},
{"hostile_name_parent_backslash.rsbank", 634},
{"hostile_name_subdir_slash.rsbank", 634},
{"hostile_name_drive_absolute.rsbank", 643},
{"hostile_name_unc_absolute.rsbank", 646},
{"hostile_path_dotdot_slash.rsbank", 641},
{"hostile_path_dotdot_backslash.rsbank", 640},
{"hostile_path_rooted.rsbank", 635},
{"hostile_path_drive_absolute.rsbank", 643},
{"hostile_path_unc_absolute.rsbank", 646},
};
static void testEveryHostileNameIsRefusedBeforeThePlannerRuns() {
for (const char* fixture : kHostileFixtures) {
Scratch scratch(std::string("hostile_") + fixture);
if (!stageFixture(scratch, fixture)) continue;
for (const HostileFixtureFile& fixture : kHostileFixtures) {
Scratch scratch(std::string("hostile_") + fixture.file);
if (!stageFixture(scratch, fixture.file, fixture.size)) continue;
ReaSamplerSession session;
const BankBook before = session.book();
@@ -222,7 +247,7 @@ static void testEveryHostileNameIsRefusedBeforeThePlannerRuns() {
const ImportLanding landing = landPackage(scratch.importPath(), scratch.projectDir(),
session.book(), kTag, journal);
if (landing.outcome != ImportOutcome::Malformed) {
std::printf("FAIL: %s imported as outcome %d, expected Malformed\n", fixture,
std::printf("FAIL: %s imported as outcome %d, expected Malformed\n", fixture.file,
static_cast<int>(landing.outcome));
++g_fail;
}