Q-W1 pt2: core/shell/app relocation + sub-namespaces; one concrete ui::Rect (LTRB fork retired); slot_map split from bank_book; BankIndex→BankModel; 59/59 green
This commit is contained in:
+31
-30
@@ -6,12 +6,13 @@
|
||||
// present AND absent), dedup-by-hash collapse, tier filter + tier move,
|
||||
// relative-path invariant, empty-index round-trip, malformed/truncated JSON.
|
||||
|
||||
#include "../src/bank_model.h"
|
||||
#include "../src/core/model/bank_model.h"
|
||||
|
||||
#include <cstdio>
|
||||
#include <string>
|
||||
|
||||
using namespace reasampler;
|
||||
using namespace reasampler::model;
|
||||
|
||||
static int g_fail = 0;
|
||||
#define CHECK(cond) do { if(!(cond)) { \
|
||||
@@ -65,12 +66,12 @@ static Sample minimalSample(const std::string& seed) {
|
||||
}
|
||||
|
||||
static void testFullFieldRoundTrip() {
|
||||
BankIndex idx;
|
||||
BankModel idx;
|
||||
CHECK(idx.add(fullSample("a")) == AddResult::Added);
|
||||
CHECK(idx.add(minimalSample("b")) == AddResult::Added);
|
||||
|
||||
std::string json = idx.serialize();
|
||||
auto back = BankIndex::deserialize(json);
|
||||
auto back = BankModel::deserialize(json);
|
||||
CHECK(back.has_value());
|
||||
CHECK(back && *back == idx);
|
||||
|
||||
@@ -104,7 +105,7 @@ static void testFullFieldRoundTrip() {
|
||||
}
|
||||
|
||||
static void testDedupByHash() {
|
||||
BankIndex idx;
|
||||
BankModel idx;
|
||||
Sample a = fullSample("x");
|
||||
CHECK(idx.add(a) == AddResult::Added);
|
||||
|
||||
@@ -128,7 +129,7 @@ static void testDedupByHash() {
|
||||
}
|
||||
|
||||
static void testTierFilterAndMove() {
|
||||
BankIndex idx;
|
||||
BankModel idx;
|
||||
Sample scratch = minimalSample("s"); scratch.tier = Tier::Scratch;
|
||||
Sample archive = fullSample("a"); archive.tier = Tier::Archive;
|
||||
CHECK(idx.add(scratch) == AddResult::Added);
|
||||
@@ -153,7 +154,7 @@ static void testTierFilterAndMove() {
|
||||
}
|
||||
|
||||
static void testRelativePathInvariant() {
|
||||
BankIndex idx;
|
||||
BankModel idx;
|
||||
|
||||
// POSIX absolute, Windows drive, Windows backslash, UNC — all rejected.
|
||||
const char* absolutes[] = {
|
||||
@@ -184,7 +185,7 @@ static void testRelativePathInvariant() {
|
||||
// bypasses dedup (an in-place refresh is not a new insert). The relative-paths-only
|
||||
// invariant still guards the replacement.
|
||||
static void testUpdateInPlace() {
|
||||
BankIndex idx;
|
||||
BankModel idx;
|
||||
CHECK(idx.add(minimalSample("a")) == AddResult::Added); // id "min-a"
|
||||
CHECK(idx.add(minimalSample("b")) == AddResult::Added); // id "min-b"
|
||||
CHECK(idx.add(minimalSample("c")) == AddResult::Added); // id "min-c"
|
||||
@@ -221,10 +222,10 @@ static void testUpdateInPlace() {
|
||||
}
|
||||
|
||||
static void testEmptyIndexRoundTrip() {
|
||||
BankIndex idx;
|
||||
BankModel idx;
|
||||
CHECK(idx.empty());
|
||||
std::string json = idx.serialize();
|
||||
auto back = BankIndex::deserialize(json);
|
||||
auto back = BankModel::deserialize(json);
|
||||
CHECK(back.has_value());
|
||||
CHECK(back && back->empty());
|
||||
CHECK(back && *back == idx);
|
||||
@@ -244,17 +245,17 @@ static void testMalformedJson() {
|
||||
"{\"samples\":[{\"createdTimestamp\":notanumber}]}",
|
||||
};
|
||||
for (const char* j : bad) {
|
||||
auto r = BankIndex::deserialize(j);
|
||||
auto r = BankModel::deserialize(j);
|
||||
CHECK(!r.has_value()); // signaled as nullopt, no crash / UB
|
||||
}
|
||||
|
||||
// A well-formed empty object deserializes to an empty index (lenient root).
|
||||
auto ok = BankIndex::deserialize("{}");
|
||||
auto ok = BankModel::deserialize("{}");
|
||||
CHECK(ok.has_value() && ok->empty());
|
||||
}
|
||||
|
||||
static void testRemoveAndQuery() {
|
||||
BankIndex idx;
|
||||
BankModel idx;
|
||||
CHECK(idx.add(fullSample("1")) == AddResult::Added);
|
||||
CHECK(idx.add(fullSample("2")) == AddResult::Added);
|
||||
CHECK(idx.query("id-1") != nullptr);
|
||||
@@ -267,7 +268,7 @@ static void testRemoveAndQuery() {
|
||||
|
||||
// Fix 1: drive-relative and bare-drive forms must be rejected by add().
|
||||
static void testAbsolutePathDriveRelative() {
|
||||
BankIndex idx;
|
||||
BankModel idx;
|
||||
|
||||
// Drive-relative: resolves against the drive's CWD, not the project root.
|
||||
Sample dr = minimalSample("dr");
|
||||
@@ -295,7 +296,7 @@ static void testAbsolutePathDriveRelative() {
|
||||
static void testUnicodeEscapeDecoding() {
|
||||
// é = U+00E9 → 2-byte UTF-8: 0xC3 0xA9
|
||||
// JSON: "é"
|
||||
auto r1 = BankIndex::deserialize(
|
||||
auto r1 = BankModel::deserialize(
|
||||
"{\"samples\":[{\"id\":\"u1\",\"relativePath\":\"bank/u.wav\","
|
||||
"\"displayName\":\"\\u00e9\","
|
||||
"\"sourceMode\":0,\"sourceRange\":{\"startSeconds\":0.0,\"endSeconds\":0.0,"
|
||||
@@ -319,7 +320,7 @@ static void testUnicodeEscapeDecoding() {
|
||||
|
||||
// 中 = U+4E2D → 3-byte UTF-8: 0xE4 0xB8 0xAD
|
||||
// JSON: "中"
|
||||
auto r2 = BankIndex::deserialize(
|
||||
auto r2 = BankModel::deserialize(
|
||||
"{\"samples\":[{\"id\":\"u2\",\"relativePath\":\"bank/u.wav\","
|
||||
"\"displayName\":\"\\u4e2d\","
|
||||
"\"sourceMode\":0,\"sourceRange\":{\"startSeconds\":0.0,\"endSeconds\":0.0,"
|
||||
@@ -343,7 +344,7 @@ static void testUnicodeEscapeDecoding() {
|
||||
}
|
||||
|
||||
// 😀 = U+1F600 → surrogate pair 😀 → 4-byte UTF-8: 0xF0 0x9F 0x98 0x80
|
||||
auto r3 = BankIndex::deserialize(
|
||||
auto r3 = BankModel::deserialize(
|
||||
"{\"samples\":[{\"id\":\"u3\",\"relativePath\":\"bank/u.wav\","
|
||||
"\"displayName\":\"\\uD83D\\uDE00\","
|
||||
"\"sourceMode\":0,\"sourceRange\":{\"startSeconds\":0.0,\"endSeconds\":0.0,"
|
||||
@@ -368,7 +369,7 @@ static void testUnicodeEscapeDecoding() {
|
||||
}
|
||||
|
||||
// Unpaired high surrogate (no following \uDCxx) → nullopt.
|
||||
auto r4 = BankIndex::deserialize(
|
||||
auto r4 = BankModel::deserialize(
|
||||
"{\"samples\":[{\"id\":\"u4\",\"relativePath\":\"bank/u.wav\","
|
||||
"\"displayName\":\"\\uD83D\","
|
||||
"\"sourceMode\":0,\"sourceRange\":{\"startSeconds\":0.0,\"endSeconds\":0.0,"
|
||||
@@ -384,7 +385,7 @@ static void testUnicodeEscapeDecoding() {
|
||||
// Fix 3: strtoll overflow must reject the value, not clamp it silently.
|
||||
static void testIntegerOverflow() {
|
||||
// A timestamp value that overflows int64_t (> 9223372036854775807).
|
||||
auto r = BankIndex::deserialize(
|
||||
auto r = BankModel::deserialize(
|
||||
"{\"samples\":[{\"id\":\"ov1\",\"relativePath\":\"bank/ov.wav\","
|
||||
"\"displayName\":\"\","
|
||||
"\"sourceMode\":0,\"sourceRange\":{\"startSeconds\":0.0,\"endSeconds\":0.0,"
|
||||
@@ -400,7 +401,7 @@ static void testIntegerOverflow() {
|
||||
// Fix 4: out-of-range enum values must reject the sample, not produce invalid enum.
|
||||
static void testEnumRangeValidation() {
|
||||
// tier: 99 is not a valid Tier enumerator.
|
||||
auto r1 = BankIndex::deserialize(
|
||||
auto r1 = BankModel::deserialize(
|
||||
"{\"samples\":[{\"id\":\"en1\",\"relativePath\":\"bank/en.wav\","
|
||||
"\"displayName\":\"\","
|
||||
"\"sourceMode\":0,\"sourceRange\":{\"startSeconds\":0.0,\"endSeconds\":0.0,"
|
||||
@@ -413,7 +414,7 @@ static void testEnumRangeValidation() {
|
||||
CHECK(!r1.has_value());
|
||||
|
||||
// sourceMode: 99 is not a valid SourceMode enumerator.
|
||||
auto r2 = BankIndex::deserialize(
|
||||
auto r2 = BankModel::deserialize(
|
||||
"{\"samples\":[{\"id\":\"en2\",\"relativePath\":\"bank/en.wav\","
|
||||
"\"displayName\":\"\","
|
||||
"\"sourceMode\":99,\"sourceRange\":{\"startSeconds\":0.0,\"endSeconds\":0.0,"
|
||||
@@ -442,7 +443,7 @@ static void testLegacyJsonDefaults() {
|
||||
"\"key\":null,\"levels\":{\"peakDb\":0.0,\"rmsDb\":0.0,\"lufs\":0.0},"
|
||||
"\"clipped\":false,\"tier\":0,\"contentHash\":\"h-leg1\","
|
||||
"\"provenance\":null,\"createdTimestamp\":0}]}";
|
||||
auto r = BankIndex::deserialize(legacy);
|
||||
auto r = BankModel::deserialize(legacy);
|
||||
CHECK(r.has_value());
|
||||
if (r) {
|
||||
const Sample* s = r->query("leg1");
|
||||
@@ -453,7 +454,7 @@ static void testLegacyJsonDefaults() {
|
||||
// Re-serialize is lossless: parsing it again yields an equal index. This
|
||||
// proves the absent fields did not silently gain values on the way out.
|
||||
std::string out = r->serialize();
|
||||
auto again = BankIndex::deserialize(out);
|
||||
auto again = BankModel::deserialize(out);
|
||||
CHECK(again.has_value());
|
||||
CHECK(again && *again == *r);
|
||||
if (again) {
|
||||
@@ -471,7 +472,7 @@ static void testLegacyJsonDefaults() {
|
||||
// storing a bogus value.
|
||||
static void testSeamFieldBoundaries() {
|
||||
// rootNote at both MIDI edges + equal-and-end-anchored loop points round-trip.
|
||||
BankIndex idx;
|
||||
BankModel idx;
|
||||
Sample lo = minimalSample("lo"); lo.contentHash = "h-lo";
|
||||
lo.rootNote = 0;
|
||||
lo.loop = LoopPoints{0, 0}; // zero-length marker at frame 0
|
||||
@@ -484,7 +485,7 @@ static void testSeamFieldBoundaries() {
|
||||
CHECK(idx.add(hi) == AddResult::Added);
|
||||
CHECK(idx.add(end) == AddResult::Added);
|
||||
|
||||
auto back = BankIndex::deserialize(idx.serialize());
|
||||
auto back = BankModel::deserialize(idx.serialize());
|
||||
CHECK(back.has_value());
|
||||
CHECK(back && *back == idx);
|
||||
if (back) {
|
||||
@@ -514,21 +515,21 @@ static void testSeamFieldBoundaries() {
|
||||
"\"clipped\":false,\"tier\":0,\"contentHash\":\"h-bad\","
|
||||
"\"provenance\":null,\"createdTimestamp\":0}]}";
|
||||
|
||||
CHECK(!BankIndex::deserialize(std::string(head) + "\"rootNote\":128," + tail).has_value());
|
||||
CHECK(!BankIndex::deserialize(std::string(head) + "\"rootNote\":-1," + tail).has_value());
|
||||
CHECK(!BankIndex::deserialize(
|
||||
CHECK(!BankModel::deserialize(std::string(head) + "\"rootNote\":128," + tail).has_value());
|
||||
CHECK(!BankModel::deserialize(std::string(head) + "\"rootNote\":-1," + tail).has_value());
|
||||
CHECK(!BankModel::deserialize(
|
||||
std::string(head) + "\"loop\":{\"start\":10,\"end\":5}," + tail).has_value()); // start > end
|
||||
CHECK(!BankIndex::deserialize(
|
||||
CHECK(!BankModel::deserialize(
|
||||
std::string(head) + "\"loop\":{\"start\":-1,\"end\":5}," + tail).has_value()); // negative start
|
||||
}
|
||||
|
||||
// S2 test case 3: the seam-field addition is purely additive — dedup-by-hash, tier
|
||||
// moves/filtering, and BankIndex ordering are byte-for-byte unchanged by the
|
||||
// moves/filtering, and BankModel ordering are byte-for-byte unchanged by the
|
||||
// presence (or absence) of rootNote/loop. Two samples differing ONLY in seam fields
|
||||
// but sharing a content hash still collapse; a seam-populated sample tiers exactly
|
||||
// like any other.
|
||||
static void testSeamFieldsAdditiveInvariant() {
|
||||
BankIndex idx;
|
||||
BankModel idx;
|
||||
Sample a = fullSample("z"); // has rootNote + loop populated
|
||||
CHECK(idx.add(a) == AddResult::Added);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user