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:
@@ -10,8 +10,10 @@
|
||||
|
||||
#include "../src/core/package/bank_package.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <filesystem>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
@@ -80,15 +82,18 @@ static PackageManifest expectedV1Manifest() {
|
||||
|
||||
// A fixture that failed to open reads as an empty buffer, and an empty buffer decodes
|
||||
// Malformed — which would let half this file pass vacuously. Every suite loads through
|
||||
// here.
|
||||
static std::vector<std::uint8_t> load(const char* name, std::size_t expectedSize) {
|
||||
std::vector<std::uint8_t> bytes = packageFixtureBytes(name);
|
||||
if (bytes.size() != expectedSize) {
|
||||
// here. A size mismatch is fatal to the caller (false), not just recorded: proceeding
|
||||
// with a short or empty buffer would let classifyFixture/le32At index out of range and
|
||||
// the TooNew test's hand-lifted manifest slice overread the heap, rather than fail clean.
|
||||
static bool load(const char* name, std::size_t expectedSize, std::vector<std::uint8_t>& out) {
|
||||
out = packageFixtureBytes(name);
|
||||
if (out.size() != expectedSize) {
|
||||
std::printf("FAIL: fixture %s is %zu bytes, expected %zu (path: %s)\n", name,
|
||||
bytes.size(), expectedSize, packageFixturePath(name).c_str());
|
||||
out.size(), expectedSize, packageFixturePath(name).c_str());
|
||||
++g_fail;
|
||||
return false;
|
||||
}
|
||||
return bytes;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool containsToken(const std::vector<std::uint8_t>& bytes, const std::string& token) {
|
||||
@@ -112,7 +117,8 @@ static PackageReadability classifyFixture(const std::vector<std::uint8_t>& bytes
|
||||
}
|
||||
|
||||
static void testV1FixtureDecodesToTheRecordTheShippingBuildWrote() {
|
||||
const std::vector<std::uint8_t> bytes = load(kV1File, kV1TotalSize);
|
||||
std::vector<std::uint8_t> bytes;
|
||||
if (!load(kV1File, kV1TotalSize, bytes)) return;
|
||||
CHECK(classifyFixture(bytes) == PackageReadability::Readable);
|
||||
|
||||
const DecodedPackage dec = decodePackage(bytes, bytes.size());
|
||||
@@ -147,7 +153,8 @@ static void testV1FixtureDecodesToTheRecordTheShippingBuildWrote() {
|
||||
// root, one on the entry, one inside the nested Sample blob — and must still decode to
|
||||
// exactly what the v1 fixture decodes to.
|
||||
static void testAdditiveForwardFixtureReadsWithEveryKnownFieldIntact() {
|
||||
const std::vector<std::uint8_t> bytes = load("additive_forward.rsbank", 1283);
|
||||
std::vector<std::uint8_t> bytes;
|
||||
if (!load("additive_forward.rsbank", 1283, bytes)) return;
|
||||
|
||||
// Non-vacuity: the unknown keys are genuinely in the bytes, so the equality below
|
||||
// is "skipped without error", not "there was nothing to skip".
|
||||
@@ -159,8 +166,10 @@ static void testAdditiveForwardFixtureReadsWithEveryKnownFieldIntact() {
|
||||
|
||||
const DecodedPackage dec = decodePackage(bytes, bytes.size());
|
||||
CHECK(dec.status == PackageReadability::Readable);
|
||||
CHECK(dec.header.formatVersion == kPackageFormatVersion + 1);
|
||||
CHECK(dec.header.minReaderVersion == kPackageMinReaderVersion);
|
||||
// Literal, not kPackageFormatVersion-relative: these are the FIXTURE's frozen
|
||||
// version pair (2/1), not this build's — see the file header note.
|
||||
CHECK(dec.header.formatVersion == 2);
|
||||
CHECK(dec.header.minReaderVersion == 1);
|
||||
CHECK(dec.header.writerVersion == "1.9.0");
|
||||
|
||||
// Every known field, end to end: same manifest the v1 fixture yields.
|
||||
@@ -172,14 +181,16 @@ static void testAdditiveForwardFixtureReadsWithEveryKnownFieldIntact() {
|
||||
// --- direction 2: a structural newer writer is refused whole -----------------
|
||||
|
||||
static void testRefuseFixtureIsTooNewAndStillNamesTheWriter() {
|
||||
const std::vector<std::uint8_t> bytes = load("refuse_structural.rsbank", kV1TotalSize);
|
||||
std::vector<std::uint8_t> bytes;
|
||||
if (!load("refuse_structural.rsbank", kV1TotalSize, bytes)) return;
|
||||
CHECK(classifyFixture(bytes) == PackageReadability::TooNew);
|
||||
|
||||
const DecodedPackage dec = decodePackage(bytes, bytes.size());
|
||||
CHECK(dec.status == PackageReadability::TooNew);
|
||||
// The three facts the refusal message owes the user.
|
||||
CHECK(dec.header.formatVersion == kPackageFormatVersion + 1);
|
||||
CHECK(dec.header.minReaderVersion == kPackageFormatVersion + 1);
|
||||
// The three facts the refusal message owes the user. Literal, not
|
||||
// kPackageFormatVersion-relative — this is the FIXTURE's frozen pair (2/2).
|
||||
CHECK(dec.header.formatVersion == 2);
|
||||
CHECK(dec.header.minReaderVersion == 2);
|
||||
CHECK(dec.header.writerVersion == "1.9.0");
|
||||
|
||||
// Nothing else: no manifest, no layout, no partial success.
|
||||
@@ -223,7 +234,8 @@ static const Truncation kTruncations[] = {
|
||||
|
||||
static void testEveryTruncationIsMalformedNeverTooNew() {
|
||||
for (const Truncation& t : kTruncations) {
|
||||
const std::vector<std::uint8_t> bytes = load(t.file, t.size);
|
||||
std::vector<std::uint8_t> bytes;
|
||||
if (!load(t.file, t.size, bytes)) continue;
|
||||
const DecodedPackage dec = decodePackage(bytes, bytes.size());
|
||||
if (dec.status != PackageReadability::Malformed) {
|
||||
std::printf("FAIL: %s (%s) classified %s, expected Malformed\n", t.file, t.site,
|
||||
@@ -247,6 +259,27 @@ static void testEveryTruncationIsMalformedNeverTooNew() {
|
||||
}
|
||||
}
|
||||
|
||||
// --- truncation: the additive-forward ladder direction, not just v1 ---------
|
||||
|
||||
// Every truncation above is a prefix of v1_shipping.rsbank (formatVersion == ours), so
|
||||
// none of them ever puts formatVersion > kPackageFormatVersion on the exact-size-proof
|
||||
// failure path — the one relabeling branch (src/core/package/CLAUDE.md: "the parse
|
||||
// branch is the ONLY one that relabels") never gets exercised from the TooNew side.
|
||||
// This fixture is additive_forward.rsbank (formatVersion 2, one past ours) cut exactly
|
||||
// at its manifest/payload boundary: the manifest parses whole, so the failure is the
|
||||
// exact-size proof, not a parse failure — it must stay Malformed, not relabel to TooNew.
|
||||
static void testAdditiveForwardTruncatedAtPayloadBoundaryStaysMalformed() {
|
||||
std::vector<std::uint8_t> bytes;
|
||||
if (!load("trunc_additive_forward.rsbank", 983, bytes)) return;
|
||||
CHECK(classifyFixture(bytes) == PackageReadability::Readable);
|
||||
|
||||
const DecodedPackage dec = decodePackage(bytes, bytes.size());
|
||||
CHECK(dec.status == PackageReadability::Malformed);
|
||||
CHECK(dec.manifest.entries.empty());
|
||||
CHECK(dec.layout.empty());
|
||||
CHECK(dec.prefixSize == 0);
|
||||
}
|
||||
|
||||
// --- hostile names: refused at decode, before any planner exists -------------
|
||||
|
||||
// The two fields carry DIFFERENT rules (src/core/package/CLAUDE.md): the entry name may
|
||||
@@ -292,7 +325,8 @@ static void testEveryHostileNameIsRefusedAtDecode() {
|
||||
CHECK(isValidNestedSamplePath("kick.wav"));
|
||||
|
||||
for (const HostileFixture& h : kHostiles) {
|
||||
const std::vector<std::uint8_t> bytes = load(h.file, h.size);
|
||||
std::vector<std::uint8_t> bytes;
|
||||
if (!load(h.file, h.size, bytes)) continue;
|
||||
if (!containsToken(bytes, jsonEscaped(h.offending))) {
|
||||
std::printf("FAIL: %s does not carry the form it is named for (%s)\n", h.file,
|
||||
h.offending);
|
||||
@@ -318,12 +352,46 @@ static void testEveryHostileNameIsRefusedAtDecode() {
|
||||
}
|
||||
}
|
||||
|
||||
// --- inventory: every fixture on disk is exercised by SOME test above --------
|
||||
|
||||
// This TU's own tables (the three named fixtures plus kTruncations and kHostiles) are
|
||||
// the fullest account of the corpus in the tree — every other consumer (the round-trip
|
||||
// harness, the README) tests a subset of these same files. Enumerating the fixture
|
||||
// directory here and failing on anything absent from this list is the one check that
|
||||
// catches a fixture added to disk but never wired into a table: a silent coverage drop
|
||||
// that would otherwise leave a green suite.
|
||||
static void testEveryFixtureOnDiskIsInSomeTable() {
|
||||
std::vector<std::string> known = {kV1File, "additive_forward.rsbank",
|
||||
"refuse_structural.rsbank",
|
||||
"trunc_additive_forward.rsbank"};
|
||||
for (const Truncation& t : kTruncations) known.push_back(t.file);
|
||||
for (const HostileFixture& h : kHostiles) known.push_back(h.file);
|
||||
|
||||
std::error_code ec;
|
||||
for (const auto& entry :
|
||||
std::filesystem::directory_iterator(REASAMPLER_PACKAGE_FIXTURE_DIR, ec)) {
|
||||
if (entry.path().extension() != ".rsbank") continue;
|
||||
const std::string name = entry.path().filename().string();
|
||||
if (std::find(known.begin(), known.end(), name) == known.end()) {
|
||||
std::printf("FAIL: fixture %s exists on disk but is in no table in this file\n",
|
||||
name.c_str());
|
||||
++g_fail;
|
||||
}
|
||||
}
|
||||
if (ec) {
|
||||
std::printf("FAIL: could not list fixture directory: %s\n", ec.message().c_str());
|
||||
++g_fail;
|
||||
}
|
||||
}
|
||||
|
||||
int main() {
|
||||
testV1FixtureDecodesToTheRecordTheShippingBuildWrote();
|
||||
testAdditiveForwardFixtureReadsWithEveryKnownFieldIntact();
|
||||
testRefuseFixtureIsTooNewAndStillNamesTheWriter();
|
||||
testEveryTruncationIsMalformedNeverTooNew();
|
||||
testAdditiveForwardTruncatedAtPayloadBoundaryStaysMalformed();
|
||||
testEveryHostileNameIsRefusedAtDecode();
|
||||
testEveryFixtureOnDiskIsInSomeTable();
|
||||
|
||||
if (g_fail == 0) {
|
||||
std::printf("package_compat_tests: all passed\n");
|
||||
|
||||
Reference in New Issue
Block a user