191 lines
7.1 KiB
C++
191 lines
7.1 KiB
C++
// Standalone tests for reasampler::wire — no REAPER, no framework. The ONE
|
|
// length-prefixed ext-state wire codec (Q-W1, T2-01b) behind provenance /
|
|
// assignment_request / sample_usage / bank_sync. The consumers' own suites
|
|
// prove their record grammars round-trip; this suite pins the CODEC contract —
|
|
// byte-exact encode, the full hardening (length caps, overflow guards,
|
|
// subtraction-first bounds), and the fixed fieldInt range rejection.
|
|
//
|
|
// NOTE: wire::Cursor BORROWS its input string, so every helper takes a named /
|
|
// reference-bound std::string — the Cursor never outlives its buffer.
|
|
|
|
#include "../src/core/wire/wire.h"
|
|
|
|
#include <cstdio>
|
|
#include <string>
|
|
|
|
using namespace reasampler;
|
|
|
|
static int g_fail = 0;
|
|
#define CHECK(cond) do { if(!(cond)) { \
|
|
std::printf("FAIL line %d: %s\n", __LINE__, #cond); ++g_fail; } } while(0)
|
|
|
|
// One length-prefixed field around `v` — the writer-side convention.
|
|
static std::string enc(const std::string& v) {
|
|
std::string out;
|
|
wire::putField(out, v);
|
|
return out;
|
|
}
|
|
|
|
// Single-field decode helpers (Cursor + buffer share the call's lifetime).
|
|
static bool fieldFrom(const std::string& s, std::string& out) {
|
|
wire::Cursor c(s);
|
|
return c.field(out);
|
|
}
|
|
static bool i64From(const std::string& s, std::int64_t& out) {
|
|
wire::Cursor c(s);
|
|
return c.fieldInt64(out);
|
|
}
|
|
static bool intFrom(const std::string& s, int& out) {
|
|
wire::Cursor c(s);
|
|
return c.fieldInt(out);
|
|
}
|
|
static bool sizeFrom(const std::string& s, std::size_t& out) {
|
|
wire::Cursor c(s);
|
|
return c.fieldSizeT(out);
|
|
}
|
|
static bool dblFrom(const std::string& s, double& out) {
|
|
wire::Cursor c(s);
|
|
return c.fieldDouble(out);
|
|
}
|
|
|
|
// --- putField: byte-exact encode ----------------------------------------------
|
|
|
|
static void testPutFieldExactBytes() {
|
|
std::string out;
|
|
wire::putField(out, "abc");
|
|
CHECK(out == "3:abc");
|
|
wire::putField(out, ""); // empty field is legal: "0:"
|
|
CHECK(out == "3:abc0:");
|
|
wire::putField(out, "a:b"); // ':' inside a value cannot shift the parse
|
|
CHECK(out == "3:abc0:3:a:b");
|
|
}
|
|
|
|
// --- Cursor: round-trip + literal ---------------------------------------------
|
|
|
|
static void testFieldRoundTripIncludingSeparators() {
|
|
std::string out = "magic";
|
|
wire::putField(out, "12:34"); // digits + colons in the value
|
|
wire::putField(out, "");
|
|
wire::putField(out, "tail");
|
|
wire::Cursor c(out);
|
|
std::string a, b, t;
|
|
CHECK(c.literal("magic"));
|
|
CHECK(c.field(a) && a == "12:34");
|
|
CHECK(c.field(b) && b.empty());
|
|
CHECK(c.field(t) && t == "tail");
|
|
CHECK(c.ok() && c.atEnd());
|
|
}
|
|
|
|
static void testLiteralMismatchFails() {
|
|
const std::string good = "rsprov1x";
|
|
const std::string wrong = "rsprov0x";
|
|
const std::string truncated = "rspro";
|
|
{ wire::Cursor c(good); CHECK(c.literal("rsprov1")); }
|
|
{ wire::Cursor c(wrong); CHECK(!c.literal("rsprov1")); CHECK(!c.ok()); }
|
|
{ wire::Cursor c(truncated); CHECK(!c.literal("rsprov1")); }
|
|
}
|
|
|
|
// --- Cursor: field hardening ---------------------------------------------------
|
|
|
|
static void testFieldRejectsMalformedLengths() {
|
|
std::string f;
|
|
CHECK(!fieldFrom("abc", f)); // no colon
|
|
CHECK(!fieldFrom(":x", f)); // empty length
|
|
CHECK(!fieldFrom("2x:ab", f)); // non-digit length
|
|
CHECK(!fieldFrom("9:ab", f)); // runs past end
|
|
// A 200-digit length cannot accumulate past SIZE_MAX (digit-run cap).
|
|
CHECK(!fieldFrom(std::string(200, '9') + ":x", f));
|
|
// Exactly-20-digit values: SIZE_MAX itself passes the accumulate but fails the
|
|
// bounds check; one past SIZE_MAX trips the overflow guard.
|
|
CHECK(!fieldFrom("18446744073709551615:x", f));
|
|
CHECK(!fieldFrom("18446744073709551616:x", f));
|
|
}
|
|
|
|
static void testFailureLatchesOk() {
|
|
// After one failed read every subsequent read fails too — the caller may
|
|
// check ok() once at the end (the "never a partial value" discipline).
|
|
const std::string s = "3:abc";
|
|
wire::Cursor c(s);
|
|
std::string f;
|
|
CHECK(!c.literal("nope"));
|
|
CHECK(!c.field(f));
|
|
CHECK(!c.ok());
|
|
}
|
|
|
|
// --- Cursor: fieldInt64 / fieldInt --------------------------------------------
|
|
|
|
static void testFieldInt64AcceptsAndRejects() {
|
|
std::int64_t v = 0;
|
|
CHECK(i64From(enc("12345"), v)); CHECK(v == 12345);
|
|
CHECK(i64From(enc("-42"), v)); CHECK(v == -42);
|
|
CHECK(i64From(enc("9223372036854775807"), v)); // INT64_MAX
|
|
CHECK(v == 9223372036854775807LL);
|
|
CHECK(!i64From(enc("9223372036854775808"), v)); // overflow
|
|
CHECK(!i64From(enc("12345678901234567890"), v)); // 20-digit cap
|
|
CHECK(!i64From(enc("-"), v)); // bare sign
|
|
CHECK(!i64From(enc("1a"), v)); // non-digit
|
|
CHECK(!i64From(enc(""), v)); // empty
|
|
}
|
|
|
|
static void testFieldIntRejectsOutOfIntRange() {
|
|
// The fixed form of the old provenance strtol TODO: an out-of-int-range field
|
|
// FAILS the parse instead of silently narrowing.
|
|
int v = 0;
|
|
CHECK(intFrom(enc("2147483647"), v)); CHECK(v == 2147483647);
|
|
CHECK(intFrom(enc("-2147483648"), v)); CHECK(v == -2147483647 - 1);
|
|
CHECK(!intFrom(enc("2147483648"), v));
|
|
CHECK(!intFrom(enc("3000000000"), v));
|
|
}
|
|
|
|
// --- Cursor: fieldSizeT / fieldDouble ------------------------------------------
|
|
|
|
static void testFieldSizeT() {
|
|
std::size_t v = 1;
|
|
CHECK(sizeFrom(enc("0"), v)); CHECK(v == 0);
|
|
CHECK(sizeFrom(enc("4096"), v)); CHECK(v == 4096);
|
|
CHECK(!sizeFrom(enc("-1"), v)); // sign = non-digit
|
|
CHECK(!sizeFrom(enc(std::string(21, '9')), v)); // 21-digit cap
|
|
CHECK(!sizeFrom(enc("18446744073709551616"), v)); // overflow guard
|
|
}
|
|
|
|
static void testFieldDoubleRoundTrip() {
|
|
char buf[32];
|
|
std::snprintf(buf, sizeof(buf), "%.17g", 3141.592653589793);
|
|
double v = 0;
|
|
CHECK(dblFrom(enc(buf), v));
|
|
CHECK(v == 3141.592653589793);
|
|
CHECK(!dblFrom(enc("1.5x"), v)); // trailing bytes
|
|
}
|
|
|
|
// --- parseUnsignedDecimal (the bank_sync generation core) -----------------------
|
|
|
|
static void testParseUnsignedDecimal() {
|
|
std::int64_t v = 0;
|
|
CHECK(wire::parseUnsignedDecimal("0", v) && v == 0);
|
|
CHECK(wire::parseUnsignedDecimal("1721947293", v) && v == 1721947293);
|
|
CHECK(wire::parseUnsignedDecimal("9223372036854775807", v) && v == 9223372036854775807LL);
|
|
CHECK(!wire::parseUnsignedDecimal("", v));
|
|
CHECK(!wire::parseUnsignedDecimal("+5", v)); // sign rejected (non-digit)
|
|
CHECK(!wire::parseUnsignedDecimal("-5", v));
|
|
CHECK(!wire::parseUnsignedDecimal("12a", v));
|
|
CHECK(!wire::parseUnsignedDecimal("9223372036854775808", v)); // overflow
|
|
CHECK(!wire::parseUnsignedDecimal(std::string(40, '9'), v)); // long run cannot wrap
|
|
}
|
|
|
|
int main() {
|
|
testPutFieldExactBytes();
|
|
testFieldRoundTripIncludingSeparators();
|
|
testLiteralMismatchFails();
|
|
testFieldRejectsMalformedLengths();
|
|
testFailureLatchesOk();
|
|
testFieldInt64AcceptsAndRejects();
|
|
testFieldIntRejectsOutOfIntRange();
|
|
testFieldSizeT();
|
|
testFieldDoubleRoundTrip();
|
|
testParseUnsignedDecimal();
|
|
|
|
if (g_fail == 0) std::printf("wire: all tests passed\n");
|
|
else std::printf("wire: %d CHECK(s) FAILED\n", g_fail);
|
|
return g_fail == 0 ? 0 : 1;
|
|
}
|