189 lines
7.6 KiB
C++
189 lines
7.6 KiB
C++
// Standalone tests for reasampler::wire::bake_wire — no VST3, no REAPER, no framework.
|
|
// Same fast assert loop as the sibling wire tests.
|
|
//
|
|
// Covers: the exact bytes each record encodes to (the two artifacts ship independently, so
|
|
// a field reorder or an inserted field must fail here rather than pass a round-trip and
|
|
// break a mixed-version pair); request + outcome round-trips including bytes that would
|
|
// break a delimiter-based format; the refusals every house wire record shares (wrong tag,
|
|
// truncation, trailing garbage, a swapped record kind); an unrecognized status integer
|
|
// degrading to Failed rather than to Ok; and the action lookup name's leading underscore.
|
|
|
|
#include "../src/core/wire/bake_wire.h"
|
|
|
|
#include "../src/core/version/app_version.h"
|
|
|
|
#include <cstdio>
|
|
#include <string>
|
|
|
|
using namespace reasampler::wire;
|
|
|
|
static int g_fail = 0;
|
|
#define CHECK(cond) do { if(!(cond)) { \
|
|
std::printf("FAIL line %d: %s\n", __LINE__, #cond); ++g_fail; } } while(0)
|
|
|
|
int main() {
|
|
// --- The exact bytes on the wire -------------------------------------------------
|
|
// A round-trip alone would pass a reordered or inserted field; the tags exist to guard
|
|
// the LAYOUT, so the layout is what is pinned. Changing either literal below means an
|
|
// already-shipped pair of artifacts can no longer talk — bump the tag, don't edit it.
|
|
{
|
|
BakeRequest req;
|
|
req.instanceGuid = "abcd";
|
|
req.stagedFilePath = "T/b.wav";
|
|
req.sourceSampleId = "cap-1";
|
|
req.sourceRelativePath = "bank/k.wav";
|
|
req.sourceDisplayName = "Kick";
|
|
req.ownUsageKey = "rsusage_abcd";
|
|
req.rootNote = 36;
|
|
req.generation = 1893456000;
|
|
CHECK(encodeBakeRequest(req) ==
|
|
"rsbakereq1"
|
|
"4:abcd"
|
|
"7:T/b.wav"
|
|
"5:cap-1"
|
|
"10:bank/k.wav"
|
|
"4:Kick"
|
|
"12:rsusage_abcd"
|
|
"2:36"
|
|
"10:1893456000");
|
|
|
|
BakeOutcome out;
|
|
out.status = BakeStatus::Ok;
|
|
out.sampleId = "bake-1";
|
|
out.relativePath = "bank/k2.wav";
|
|
out.displayName = "Kick r2";
|
|
out.rootNote = 36;
|
|
out.channelCount = 2;
|
|
out.replaced = true;
|
|
out.message = "replaced";
|
|
out.generation = 1893456000;
|
|
CHECK(encodeBakeOutcome(out) ==
|
|
"rsbakeout1"
|
|
"1:0"
|
|
"6:bake-1"
|
|
"11:bank/k2.wav"
|
|
"7:Kick r2"
|
|
"2:36"
|
|
"1:2"
|
|
"1:1"
|
|
"8:replaced"
|
|
"10:1893456000");
|
|
}
|
|
|
|
// --- Request round-trip, with hostile field content -----------------------------
|
|
{
|
|
BakeRequest req;
|
|
req.instanceGuid = "0123abcd";
|
|
req.stagedFilePath = "C:/Temp/re:sampler 9000/bake 12:34.wav"; // colons + spaces
|
|
req.sourceSampleId = "cap-1";
|
|
req.sourceRelativePath = "reasampler_bank/kick.wav";
|
|
req.sourceDisplayName = "Kick r2";
|
|
req.ownUsageKey = "rsusage_0123abcd";
|
|
req.rootNote = 36;
|
|
req.generation = 1893456000;
|
|
|
|
const std::string encoded = encodeBakeRequest(req);
|
|
const auto decoded = decodeBakeRequest(encoded);
|
|
CHECK(decoded.has_value());
|
|
CHECK(*decoded == req);
|
|
|
|
// Empty strings and a zero generation survive too (a first, un-named source).
|
|
BakeRequest bare;
|
|
CHECK(decodeBakeRequest(encodeBakeRequest(bare)) == bare);
|
|
}
|
|
|
|
// --- Outcome round-trip -----------------------------------------------------------
|
|
{
|
|
BakeOutcome out;
|
|
out.status = BakeStatus::Ok;
|
|
out.sampleId = "bake-1893456000-kick_1893456000.wav";
|
|
out.relativePath = "reasampler_bank/kick_1893456000.wav";
|
|
out.displayName = "Kick r3";
|
|
out.rootNote = 36;
|
|
out.channelCount = 2;
|
|
out.replaced = true;
|
|
out.message = "replaced the bank entry";
|
|
out.generation = 1893456000;
|
|
|
|
const auto decoded = decodeBakeOutcome(encodeBakeOutcome(out));
|
|
CHECK(decoded.has_value());
|
|
CHECK(*decoded == out);
|
|
CHECK(decoded->replaced);
|
|
|
|
out.replaced = false;
|
|
CHECK(decodeBakeOutcome(encodeBakeOutcome(out))->replaced == false);
|
|
}
|
|
|
|
// --- Malformed input is refused, never half-parsed ---------------------------------
|
|
{
|
|
BakeRequest req;
|
|
req.instanceGuid = "abc";
|
|
req.rootNote = 60;
|
|
const std::string good = encodeBakeRequest(req);
|
|
|
|
CHECK(!decodeBakeRequest("").has_value());
|
|
CHECK(!decodeBakeRequest("rsbakereq0" + good.substr(10)).has_value()); // wrong tag
|
|
CHECK(!decodeBakeRequest(good.substr(0, good.size() - 3)).has_value()); // truncated
|
|
CHECK(!decodeBakeRequest(good + "junk").has_value()); // trailing
|
|
// The two records share a key; each must refuse the other's bytes outright.
|
|
CHECK(!decodeBakeOutcome(good).has_value());
|
|
CHECK(!decodeBakeRequest(encodeBakeOutcome(BakeOutcome{})).has_value());
|
|
}
|
|
|
|
// --- A status integer this build does not know reads as a FAILURE ------------------
|
|
{
|
|
// Hand-built with a future status value; every other field is well-formed, so only
|
|
// the vocabulary gap is under test.
|
|
BakeOutcome out;
|
|
out.status = BakeStatus::Ok;
|
|
out.generation = 7;
|
|
std::string wire = encodeBakeOutcome(out);
|
|
// The status field is the first after the tag: "<len>':'<digits>".
|
|
const std::string okField = "1:0";
|
|
const std::size_t at = wire.find(okField);
|
|
CHECK(at != std::string::npos);
|
|
wire.replace(at, okField.size(), "2:99");
|
|
|
|
const auto decoded = decodeBakeOutcome(wire);
|
|
CHECK(decoded.has_value());
|
|
CHECK(decoded->status == BakeStatus::Failed); // never Ok
|
|
CHECK(decoded->generation == 7);
|
|
|
|
// Every status this build DOES know survives its own round trip — including the
|
|
// most recently appended one, which an older reader will see as Failed.
|
|
for (const BakeStatus s :
|
|
{BakeStatus::Ok, BakeStatus::Failed, BakeStatus::NoProject,
|
|
BakeStatus::StagedMissing, BakeStatus::NoSource, BakeStatus::IndexRejected,
|
|
BakeStatus::WrongProject}) {
|
|
BakeOutcome one;
|
|
one.status = s;
|
|
const auto back = decodeBakeOutcome(encodeBakeOutcome(one));
|
|
CHECK(back.has_value() && back->status == s);
|
|
}
|
|
}
|
|
|
|
// --- The lookup name carries the underscore the registration string does not -------
|
|
// The load-bearing half is the REGISTRATION string: main.cpp registers that spelling
|
|
// verbatim, and NamedCommandLookup needs exactly one underscore in front of it. If
|
|
// channelCommandId ever grew one of its own, the lookup would carry two and resolve to
|
|
// nothing.
|
|
{
|
|
const std::string registered =
|
|
reasampler::version::channelCommandId(kBakeActionSuffix);
|
|
const std::string lookup = bakeActionLookupName();
|
|
const std::size_t suffixLen = std::string(kBakeActionSuffix).size();
|
|
CHECK(!registered.empty());
|
|
CHECK(registered.front() != '_');
|
|
CHECK(lookup.size() == registered.size() + 1);
|
|
CHECK(lookup.front() == '_' && lookup[1] != '_');
|
|
CHECK(lookup.compare(1, std::string::npos, registered) == 0);
|
|
// The suffix is the TAIL of the id — the channel prefix goes in front of it, and a
|
|
// suffix that drifted into the middle would name a different action.
|
|
CHECK(lookup.size() > suffixLen);
|
|
CHECK(lookup.compare(lookup.size() - suffixLen, suffixLen, kBakeActionSuffix) == 0);
|
|
}
|
|
|
|
if (g_fail == 0) std::printf("bake_wire: all tests passed\n");
|
|
return g_fail ? 1 : 0;
|
|
}
|