Merge T2: realtime capture tail trimming

This commit is contained in:
2026-07-23 18:27:27 -04:00
12 changed files with 1053 additions and 10 deletions
+75
View File
@@ -278,6 +278,76 @@ static void testLargeBinCountOverflowGuard() {
CHECK(env[0][7].min == 0.0f && env[0][7].max == 0.0f);
}
// --- lastFrameAboveThreshold: the realtime tail's decay-scan boundary primitive --
// A mono decaying ramp: frame i has amplitude that falls linearly to zero. With a
// threshold set between two frames' levels, the last frame above it is deterministic.
static void testLastFrameDecayingRamp() {
// 10 mono frames, amplitude 1.0 - i*0.1: frame0=1.0 ... frame9=0.1.
std::vector<AudioSample> buf(10);
for (std::size_t i = 0; i < 10; ++i) buf[i] = 1.0f - 0.1f * (float)i;
// Threshold 0.35: frames 0..6 (levels 1.0..0.4) exceed it; frame 6 is the last
// (level 0.4 > 0.35), frame 7 (0.3) does not. Strict > semantics.
CHECK(lastFrameAboveThreshold(buf, 1, 10, 0.35f) == 6);
// Threshold just under frame 9's level (0.1): the very last frame stays.
CHECK(lastFrameAboveThreshold(buf, 1, 10, 0.05f) == 9);
// Threshold above the loudest frame: nothing survives.
CHECK(lastFrameAboveThreshold(buf, 1, 10, 1.5f) == kNoFrameAboveThreshold);
}
// Pure silence at or below the threshold -> sentinel (the "trim back to end" case:
// no frame in the tail window exceeds -72 dB).
static void testLastFrameSilence() {
std::vector<AudioSample> zeros(20, 0.0f);
CHECK(lastFrameAboveThreshold(zeros, 2, 10, 0.001f) == kNoFrameAboveThreshold);
// A DC level exactly AT the threshold does not count (strict >).
std::vector<AudioSample> atThresh(8, 0.25f);
CHECK(lastFrameAboveThreshold(atThresh, 1, 8, 0.25f) == kNoFrameAboveThreshold);
}
// Every frame above the threshold (a non-decaying source): the last frame is the
// boundary — the caller keeps the whole window (the 8 s cap did its job).
static void testLastFrameAllAbove() {
std::vector<AudioSample> loud(12, 0.8f); // 6 stereo frames
CHECK(lastFrameAboveThreshold(loud, 2, 6, 0.1f) == 5);
}
// Per-frame peak is the MAX abs across channels (no fold): a frame with one loud
// channel and one silent channel is "above" on the strength of the loud one, and a
// negative sample is compared by magnitude.
static void testLastFramePerChannelMaxAbs() {
// 3 stereo frames. Frame0: (0.9, 0.0) loud L. Frame1: (0.0, -0.9) loud R (negative
// -> abs). Frame2: (0.05, -0.05) both quiet.
std::vector<AudioSample> buf = {0.9f, 0.0f, 0.0f, -0.9f, 0.05f, -0.05f};
// Threshold 0.5: frame2 is below (peak 0.05), frame1 is above (|-0.9|=0.9).
CHECK(lastFrameAboveThreshold(buf, 2, 3, 0.5f) == 1);
// If both channels of the last frame mattered independently, a fold-average
// (0.9+0.0)/2 = 0.45 on frame0 would fall below 0.5 — but frame0's L alone (0.9)
// is above, proving max-abs, not average. Lower the threshold to isolate frame0.
std::vector<AudioSample> f0 = {0.9f, 0.0f};
CHECK(lastFrameAboveThreshold(f0, 2, 1, 0.5f) == 0);
}
// Degenerate: zero channels, zero frames, and a frameCount that overstates the
// buffer (must clamp to available frames, no OOB read).
static void testLastFrameDegenerate() {
std::vector<AudioSample> buf = {0.5f, 0.5f, 0.5f, 0.5f}; // 2 stereo frames
CHECK(lastFrameAboveThreshold(buf, 0, 2, 0.1f) == kNoFrameAboveThreshold);
CHECK(lastFrameAboveThreshold(buf, 2, 0, 0.1f) == kNoFrameAboveThreshold);
std::vector<AudioSample> empty;
CHECK(lastFrameAboveThreshold(empty, 2, 10, 0.1f) == kNoFrameAboveThreshold);
// frameCount=100 but only 2 real stereo frames: clamps to frame 1 (the last real
// frame), which is above -> index 1, no read past the buffer.
CHECK(lastFrameAboveThreshold(buf, 2, 100, 0.1f) == 1);
}
int main() {
testSineEnvelope();
testRampMonotonic();
@@ -289,6 +359,11 @@ int main() {
testSingleBinWholeBuffer();
testDegenerateInputs();
testLargeBinCountOverflowGuard();
testLastFrameDecayingRamp();
testLastFrameSilence();
testLastFrameAllAbove();
testLastFramePerChannelMaxAbs();
testLastFrameDegenerate();
if (g_fail == 0) std::printf("All tests passed.\n");
return g_fail ? 1 : 0;
+28
View File
@@ -126,6 +126,31 @@ static void testTailManualClampsToCap() {
CHECK(tailRenderSettingsFor(TailMode::Manual, -50.0).tailMs == 0.0);
}
// --- realtimeRecordWindowEnd: the T2 record-window extension -----------------
static void testRealtimeWindowNoneIsExact() {
// None -> the exact range end, no extra recording (byte-identical to today).
CHECK(realtimeRecordWindowEnd(TailMode::None, 12.5, 2000.0) == 12.5);
// manualTailMs is ignored for None.
CHECK(realtimeRecordWindowEnd(TailMode::None, 12.5, 0.0) == 12.5);
}
static void testRealtimeWindowAutoAddsCap() {
// Auto -> range end + the 8 s runaway cap (trimmed later by the decay scan).
CHECK(realtimeRecordWindowEnd(TailMode::Auto, 10.0, 0.0) == 10.0 + kMaxTailSeconds);
// manualTailMs is ignored for Auto (the cap is fixed).
CHECK(realtimeRecordWindowEnd(TailMode::Auto, 10.0, 3000.0) == 10.0 + kMaxTailSeconds);
}
static void testRealtimeWindowManualAddsClampedLength() {
// Manual -> range end + the set length in seconds (fixed, no trim).
CHECK(realtimeRecordWindowEnd(TailMode::Manual, 5.0, 2000.0) == 5.0 + 2.0);
// Clamped to the 8 s cap: > 8000 ms -> +8 s.
CHECK(realtimeRecordWindowEnd(TailMode::Manual, 5.0, 9000.0) == 5.0 + kMaxTailSeconds);
// Negative floors to 0 -> no extra window (never records before the range end).
CHECK(realtimeRecordWindowEnd(TailMode::Manual, 5.0, -100.0) == 5.0);
}
// --- parseRazorEdits: P_RAZOREDITS string -> ranges --------------------------
static void testParseSingleTrackAudioArea() {
@@ -267,6 +292,9 @@ int main() {
testAutoTrimRatioDerivesFromDb();
testTailManualFixedNoTrim();
testTailManualClampsToCap();
testRealtimeWindowNoneIsExact();
testRealtimeWindowAutoAddsCap();
testRealtimeWindowManualAddsClampedLength();
testParseSingleTrackAudioArea();
testParseMultipleAreas();
testParseSkipsEnvelopeLaneAreas();
+372
View File
@@ -0,0 +1,372 @@
// Standalone tests for reasampler::wav_trim — no REAPER, no test framework.
// Builds synthetic 32-bit-float WAV byte buffers, asserts the parse geometry, the
// float extraction, and the truncate-plan arithmetic (the header size-field patch).
//
// Covers: canonical stereo/mono 32-bit-float parse; a leading unknown chunk skipped;
// format rejection (16-bit PCM, non-WAV, data-before-fmt, truncated data); frame
// extraction (whole / tail window / clamp / out-of-range); truncate plan (kept<all,
// no-op keep-all, kept==0, grow rejected) with exact size-field values.
#include "../src/wav_trim.h"
#include <cstdint>
#include <cstdio>
#include <cstring>
#include <vector>
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)
// --- Synthetic WAV builder ---------------------------------------------------
static void putU16(std::vector<std::uint8_t>& b, std::uint16_t v) {
b.push_back(static_cast<std::uint8_t>(v & 0xFF));
b.push_back(static_cast<std::uint8_t>((v >> 8) & 0xFF));
}
static void putU32(std::vector<std::uint8_t>& b, std::uint32_t v) {
b.push_back(static_cast<std::uint8_t>(v & 0xFF));
b.push_back(static_cast<std::uint8_t>((v >> 8) & 0xFF));
b.push_back(static_cast<std::uint8_t>((v >> 16) & 0xFF));
b.push_back(static_cast<std::uint8_t>((v >> 24) & 0xFF));
}
static void putTag(std::vector<std::uint8_t>& b, const char* t) {
for (int i = 0; i < 4; ++i) b.push_back(static_cast<std::uint8_t>(t[i]));
}
static void putFloat(std::vector<std::uint8_t>& b, float f) {
std::uint8_t tmp[4];
std::memcpy(tmp, &f, 4);
for (int i = 0; i < 4; ++i) b.push_back(tmp[i]);
}
// A canonical 32-bit-float WAV: RIFF/WAVE, fmt (tag 3, 16-byte body), data holding
// `frames` interleaved frames of `channels`. `leadingJunk` optionally inserts an
// unknown chunk before fmt to exercise the chunk walk. Samples: frame f, channel c
// = value(f,c).
template <typename Fn>
static std::vector<std::uint8_t> buildFloatWav(std::uint16_t channels,
std::uint32_t sampleRate,
std::size_t frames,
Fn value,
bool leadingJunk = false,
std::uint16_t fmtTag = 3,
std::uint16_t bits = 32) {
const std::uint32_t dataBytes =
static_cast<std::uint32_t>(frames * channels * (bits / 8));
std::vector<std::uint8_t> chunks; // everything after "WAVE"
if (leadingJunk) {
putTag(chunks, "LIST");
putU32(chunks, 4);
putTag(chunks, "INFO"); // 4-byte body, even -> no pad
}
// fmt chunk (16-byte body).
putTag(chunks, "fmt ");
putU32(chunks, 16);
putU16(chunks, fmtTag); // format tag
putU16(chunks, channels);
putU32(chunks, sampleRate);
const std::uint32_t byteRate = sampleRate * channels * (bits / 8);
putU32(chunks, byteRate);
putU16(chunks, static_cast<std::uint16_t>(channels * (bits / 8))); // block align
putU16(chunks, bits);
// data chunk.
putTag(chunks, "data");
putU32(chunks, dataBytes);
for (std::size_t f = 0; f < frames; ++f)
for (std::uint16_t c = 0; c < channels; ++c)
putFloat(chunks, value(f, c));
std::vector<std::uint8_t> wav;
putTag(wav, "RIFF");
putU32(wav, static_cast<std::uint32_t>(4 + chunks.size())); // "WAVE" + chunks
putTag(wav, "WAVE");
wav.insert(wav.end(), chunks.begin(), chunks.end());
return wav;
}
// Builds a WAVE_FORMAT_EXTENSIBLE (0xFFFE) WAV with a 40-byte fmt body.
// `subFormatTag` is the 2-byte leading tag embedded in the SubFormat GUID:
// 0x0003 = IEEE float, 0x0001 = PCM integer (and any other value to exercise rejection).
// bitsPerSample and the PCM data are always 32-bit float bytes regardless of subFormatTag
// (we're testing that the parser correctly rejects/accepts based on the GUID, not the data).
template <typename Fn>
static std::vector<std::uint8_t> buildExtensibleWav(std::uint16_t channels,
std::uint32_t sampleRate,
std::size_t frames,
Fn value,
std::uint16_t subFormatTag) {
const std::uint32_t dataBytes =
static_cast<std::uint32_t>(frames * channels * 4u);
// WAVEFORMATEXTENSIBLE fmt body (40 bytes):
// [0..1] wFormatTag = 0xFFFE
// [2..3] nChannels
// [4..7] nSamplesPerSec
// [8..11] nAvgBytesPerSec
// [12..13] nBlockAlign
// [14..15] wBitsPerSample = 32
// [16..17] cbSize = 22 (extension size beyond the 18-byte WAVEFORMATEX)
// [18..19] wValidBitsPerSample = 32
// [20..23] dwChannelMask = 0
// [24..39] SubFormat GUID: first 2 bytes = subFormatTag (LE), rest = standard
// KSDATAFORMAT_SUBTYPE base GUID {00000000-0000-0010-8000-00aa00389b71}
std::vector<std::uint8_t> fmt;
putU16(fmt, 0xFFFE); // wFormatTag
putU16(fmt, channels); // nChannels
putU32(fmt, sampleRate); // nSamplesPerSec
putU32(fmt, sampleRate * channels * 4u); // nAvgBytesPerSec
putU16(fmt, static_cast<std::uint16_t>(channels * 4)); // nBlockAlign
putU16(fmt, 32); // wBitsPerSample
putU16(fmt, 22); // cbSize
putU16(fmt, 32); // wValidBitsPerSample
putU32(fmt, 0); // dwChannelMask
// SubFormat GUID (16 bytes): [subFormatTag, 0x0000, 0x00, 0x00, 0x10, 0x00,
// 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71]
putU16(fmt, subFormatTag); // bytes [24..25]: the effective format tag
putU16(fmt, 0x0000); // bytes [26..27]
fmt.push_back(0x00); fmt.push_back(0x00); // bytes [28..29]
fmt.push_back(0x10); fmt.push_back(0x00); // bytes [30..31]
fmt.push_back(0x80); fmt.push_back(0x00); // bytes [32..33]
fmt.push_back(0x00); fmt.push_back(0xaa); // bytes [34..35]
fmt.push_back(0x00); fmt.push_back(0x38); // bytes [36..37]
fmt.push_back(0x9b); fmt.push_back(0x71); // bytes [38..39]
std::vector<std::uint8_t> chunks;
putTag(chunks, "fmt ");
putU32(chunks, static_cast<std::uint32_t>(fmt.size())); // 40
chunks.insert(chunks.end(), fmt.begin(), fmt.end());
putTag(chunks, "data");
putU32(chunks, dataBytes);
for (std::size_t f = 0; f < frames; ++f)
for (std::uint16_t c = 0; c < channels; ++c)
putFloat(chunks, value(f, c));
std::vector<std::uint8_t> wav;
putTag(wav, "RIFF");
putU32(wav, static_cast<std::uint32_t>(4 + chunks.size()));
putTag(wav, "WAVE");
wav.insert(wav.end(), chunks.begin(), chunks.end());
return wav;
}
// --- Parse tests -------------------------------------------------------------
static void testParseCanonicalStereo() {
auto wav = buildFloatWav(2, 48000, 5,
[](std::size_t f, std::uint16_t c) {
return static_cast<float>(f) + 0.1f * c;
});
WavLayout L = parseWavLayout(wav);
CHECK(L.valid);
CHECK(L.channelCount == 2);
CHECK(L.sampleRate == 48000);
CHECK(L.dataByteLength == 5 * 2 * 4);
CHECK(L.frameCount() == 5);
// data body sits after RIFF(12) + fmt(8 header + 16 body) + data(8 header) = 44.
CHECK(L.dataByteOffset == 44);
CHECK(L.dataSizeFieldOffset == 40); // the 4 bytes before dataByteOffset
CHECK(L.riffSizeFieldOffset == 4);
}
static void testParseMonoAndLeadingChunk() {
// A leading LIST/INFO chunk before fmt must be skipped by the walk.
auto wav = buildFloatWav(1, 44100, 3,
[](std::size_t f, std::uint16_t) {
return static_cast<float>(f);
},
/*leadingJunk=*/true);
WavLayout L = parseWavLayout(wav);
CHECK(L.valid);
CHECK(L.channelCount == 1);
CHECK(L.frameCount() == 3);
// Data still parses correctly despite the leading chunk shifting its offset.
auto pcm = extractFloatFrames(wav, L, 0, 3);
CHECK(pcm.size() == 3);
CHECK(pcm[0] == 0.0f && pcm[1] == 1.0f && pcm[2] == 2.0f);
}
static void testParseRejectsNon32BitAndNonWav() {
// 16-bit PCM (tag 1, bits 16) -> rejected.
auto pcm16 = buildFloatWav(2, 48000, 4,
[](std::size_t, std::uint16_t) { return 0.0f; },
false, /*fmtTag=*/1, /*bits=*/16);
CHECK(!parseWavLayout(pcm16).valid);
// Not a RIFF file.
std::vector<std::uint8_t> junk = {'N','O','P','E', 0,0,0,0, 'W','A','V','E'};
CHECK(!parseWavLayout(junk).valid);
// Too short to hold even the RIFF header.
std::vector<std::uint8_t> tiny = {'R','I','F','F'};
CHECK(!parseWavLayout(tiny).valid);
}
static void testParseRejectsLyingDataLength() {
// Build a valid WAV, then inflate the `data` size field so it claims more bytes
// than the buffer holds -> must be rejected (no OOB trust).
auto wav = buildFloatWav(2, 48000, 4,
[](std::size_t, std::uint16_t) { return 1.0f; });
WavLayout good = parseWavLayout(wav);
CHECK(good.valid);
// Overwrite the data size field with a huge value.
wav[good.dataSizeFieldOffset + 0] = 0xFF;
wav[good.dataSizeFieldOffset + 1] = 0xFF;
wav[good.dataSizeFieldOffset + 2] = 0xFF;
wav[good.dataSizeFieldOffset + 3] = 0x7F;
CHECK(!parseWavLayout(wav).valid);
}
// --- Extraction tests --------------------------------------------------------
static void testExtractTailWindow() {
// Stereo, 10 frames. Sample value encodes frame+channel so a mis-index is caught.
auto wav = buildFloatWav(2, 48000, 10,
[](std::size_t f, std::uint16_t c) {
return static_cast<float>(f) * 10.0f + c;
});
WavLayout L = parseWavLayout(wav);
CHECK(L.valid);
// The "tail region" the realtime trim scans: frames 6..9 (start at frame 6).
auto tail = extractFloatFrames(wav, L, 6, 100 /*clamps*/);
CHECK(tail.size() == 4 * 2); // frames 6,7,8,9, 2 channels each
CHECK(tail[0] == 60.0f && tail[1] == 61.0f); // frame 6: L=60,R=61
CHECK(tail[6] == 90.0f && tail[7] == 91.0f); // frame 9: L=90,R=91
// Out-of-range start -> empty.
CHECK(extractFloatFrames(wav, L, 10, 4).empty());
CHECK(extractFloatFrames(wav, L, 99, 4).empty());
}
// --- Truncate-plan tests -----------------------------------------------------
static void testTruncatePlanKeepFewer() {
auto wav = buildFloatWav(2, 48000, 10,
[](std::size_t, std::uint16_t) { return 0.0f; });
WavLayout L = parseWavLayout(wav);
CHECK(L.valid);
// Keep 4 of 10 frames.
WavTruncatePlan p = planWavTruncate(L, 4);
CHECK(p.valid);
const std::size_t bpf = 2 * 4; // channels * 4 bytes
CHECK(p.newDataSize == 4 * bpf); // 32 bytes of PCM kept
CHECK(p.newFileByteLength == L.dataByteOffset + 4 * bpf); // 44 + 32 = 76
CHECK(p.newRiffSize == p.newFileByteLength - 8);
CHECK(p.dataSizeFieldOffset == L.dataSizeFieldOffset);
CHECK(p.riffSizeFieldOffset == 4);
// Applying the plan yields a buffer that re-parses to exactly 4 frames.
std::vector<std::uint8_t> trimmed(wav.begin(),
wav.begin() + p.newFileByteLength);
// Patch the two size fields (what the shell does before truncating on disk).
auto writeU32 = [](std::vector<std::uint8_t>& b, std::size_t off, std::uint32_t v) {
b[off + 0] = static_cast<std::uint8_t>(v & 0xFF);
b[off + 1] = static_cast<std::uint8_t>((v >> 8) & 0xFF);
b[off + 2] = static_cast<std::uint8_t>((v >> 16) & 0xFF);
b[off + 3] = static_cast<std::uint8_t>((v >> 24) & 0xFF);
};
writeU32(trimmed, p.dataSizeFieldOffset, p.newDataSize);
writeU32(trimmed, p.riffSizeFieldOffset, p.newRiffSize);
WavLayout L2 = parseWavLayout(trimmed);
CHECK(L2.valid);
CHECK(L2.frameCount() == 4);
CHECK(L2.dataByteLength == 4 * bpf);
}
static void testTruncatePlanKeepAllIsNoOp() {
auto wav = buildFloatWav(1, 48000, 6,
[](std::size_t, std::uint16_t) { return 0.0f; });
WavLayout L = parseWavLayout(wav);
WavTruncatePlan p = planWavTruncate(L, 6); // keep all
CHECK(p.valid);
CHECK(p.newFileByteLength == wav.size()); // unchanged
CHECK(p.newDataSize == L.dataByteLength);
}
// --- Extensible format tests -------------------------------------------------
// A WAVE_FORMAT_EXTENSIBLE fmt with SubFormat tag 0x0001 (PCM integer) and
// bitsPerSample==32 must be REJECTED — it is 32-bit integer, not 32-bit float.
static void testExtensiblePcmIntegerRejected() {
auto wav = buildExtensibleWav(2, 48000, 4,
[](std::size_t, std::uint16_t) { return 0.0f; },
/*subFormatTag=*/0x0001); // PCM integer
CHECK(!parseWavLayout(wav).valid);
}
// A WAVE_FORMAT_EXTENSIBLE fmt with SubFormat tag 0x0003 (IEEE float) and
// bitsPerSample==32 must be ACCEPTED and parse + trim correctly.
static void testExtensibleFloatAccepted() {
auto wav = buildExtensibleWav(2, 48000, 5,
[](std::size_t f, std::uint16_t c) {
return static_cast<float>(f) + 0.1f * c;
},
/*subFormatTag=*/0x0003); // IEEE float
WavLayout L = parseWavLayout(wav);
CHECK(L.valid);
CHECK(L.channelCount == 2);
CHECK(L.sampleRate == 48000);
CHECK(L.frameCount() == 5);
// Frame extraction works correctly.
auto pcm = extractFloatFrames(wav, L, 0, 2);
CHECK(pcm.size() == 4);
CHECK(pcm[0] == 0.0f); // frame 0, channel 0
CHECK(pcm[1] == 0.1f); // frame 0, channel 1
// Truncate plan is valid and re-parses cleanly.
WavTruncatePlan p = planWavTruncate(L, 3);
CHECK(p.valid);
CHECK(p.newDataSize == 3 * 2 * 4u);
std::vector<std::uint8_t> trimmed(wav.begin(), wav.begin() + p.newFileByteLength);
auto writeU32 = [](std::vector<std::uint8_t>& b, std::size_t off, std::uint32_t v) {
b[off + 0] = static_cast<std::uint8_t>(v & 0xFF);
b[off + 1] = static_cast<std::uint8_t>((v >> 8) & 0xFF);
b[off + 2] = static_cast<std::uint8_t>((v >> 16) & 0xFF);
b[off + 3] = static_cast<std::uint8_t>((v >> 24) & 0xFF);
};
writeU32(trimmed, p.dataSizeFieldOffset, p.newDataSize);
writeU32(trimmed, p.riffSizeFieldOffset, p.newRiffSize);
WavLayout L2 = parseWavLayout(trimmed);
CHECK(L2.valid);
CHECK(L2.frameCount() == 3);
}
static void testTruncatePlanKeepZeroAndGrowRejected() {
auto wav = buildFloatWav(2, 48000, 5,
[](std::size_t, std::uint16_t) { return 0.0f; });
WavLayout L = parseWavLayout(wav);
WavTruncatePlan zero = planWavTruncate(L, 0);
CHECK(zero.valid);
CHECK(zero.newDataSize == 0);
CHECK(zero.newFileByteLength == L.dataByteOffset); // header only
// keptFrames > total -> refused (never grow a file).
CHECK(!planWavTruncate(L, 6).valid);
// Invalid layout -> invalid plan.
WavLayout bad;
CHECK(!planWavTruncate(bad, 0).valid);
}
int main() {
testParseCanonicalStereo();
testParseMonoAndLeadingChunk();
testParseRejectsNon32BitAndNonWav();
testParseRejectsLyingDataLength();
testExtractTailWindow();
testTruncatePlanKeepFewer();
testTruncatePlanKeepAllIsNoOp();
testTruncatePlanKeepZeroAndGrowRejected();
testExtensiblePcmIntegerRejected();
testExtensibleFloatAccepted();
if (g_fail == 0) std::printf("All tests passed.\n");
return g_fail ? 1 : 0;
}