// Standalone tests for reasampler::capture_paths — no REAPER, no framework. // The capture shell is DAW-bound and only verifiable in REAPER; this covers the // one genuinely pure piece: the bank-folder / unique-name / project-relative // path arithmetic that feeds BankIndex::add's relative-only invariant. #include "../src/capture_paths.h" #include #include 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) static void testNormalizeSlashes() { CHECK(normalizeSlashes("C:\\a\\b") == "C:/a/b"); CHECK(normalizeSlashes("a/b/c") == "a/b/c"); CHECK(normalizeSlashes("a/b/") == "a/b"); // trailing slash stripped CHECK(normalizeSlashes("a\\b\\") == "a/b"); // backslash + trailing CHECK(normalizeSlashes("/") == "/"); // lone root preserved CHECK(normalizeSlashes("") == ""); // empty stays empty } static void testSanitizeStem() { // Safe characters survive verbatim. CHECK(sanitizeStem("Kick_01.take-2") == "Kick_01.take-2"); // Spaces, slashes, quotes, control chars become '_'. CHECK(sanitizeStem("my mix") == "my_mix"); CHECK(sanitizeStem("a/b\\c") == "a_b_c"); CHECK(sanitizeStem("q\"uote") == "q_uote"); CHECK(sanitizeStem(std::string("nul\0byte", 8)) == "nul_byte"); // Nothing usable -> stable default. CHECK(sanitizeStem("") == "capture"); CHECK(sanitizeStem(" ") == "capture"); // All-separator (no alphanumeric) -> default, so the name is meaningful. CHECK(sanitizeStem("...") == "capture"); CHECK(sanitizeStem("-_-") == "capture"); } static void testDeriveRelativePathIsProjectRelative() { BankPaths p = deriveBankPaths("C:\\Users\\d\\proj", "master mix", "1753080000"); // Relative path is under the fixed bank subfolder, forward-slashed, .wav. CHECK(p.relativePath == "reasampler_bank/master_mix_1753080000.wav"); // It must NOT be absolute by any of BankIndex::add's rejection rules: // no leading '/', no drive letter, no backslash, no UNC prefix. CHECK(p.relativePath.find(':') == std::string::npos); CHECK(p.relativePath.find('\\') == std::string::npos); CHECK(!p.relativePath.empty() && p.relativePath[0] != '/'); CHECK(p.relativePath.rfind("\\\\", 0) != 0); } static void testDeriveAbsoluteDirJoinsProjectDir() { BankPaths p = deriveBankPaths("C:\\Users\\d\\proj", "kick", ""); // Backslashes normalized; bank subfolder appended; no trailing slash. CHECK(p.absoluteDir == "C:/Users/d/proj/reasampler_bank"); // No unique tag -> stem has no trailing "_". CHECK(p.fileName == "kick.wav"); CHECK(p.relativePath == "reasampler_bank/kick.wav"); } static void testDeriveHandlesTrailingSlashProjectDir() { // A project dir with a trailing slash must not double up in the join. BankPaths p = deriveBankPaths("/home/d/proj/", "mix", "7"); CHECK(p.absoluteDir == "/home/d/proj/reasampler_bank"); CHECK(p.fileName == "mix_7.wav"); } static void testDeriveEmptyProjectDirFallsBackToRelative() { // Defensive: with no project dir, absoluteDir is just the bank subfolder // (the shell rejects the no-path case before this, but the arithmetic must // not emit a leading slash that would read as absolute). BankPaths p = deriveBankPaths("", "mix", ""); CHECK(p.absoluteDir == "reasampler_bank"); CHECK(p.relativePath == "reasampler_bank/mix.wav"); } static void testDeterministicForSameInputs() { // Same inputs -> same derived paths (feeds deterministic file naming). BankPaths a = deriveBankPaths("C:/p", "mix", "42"); BankPaths b = deriveBankPaths("C:/p", "mix", "42"); CHECK(a.absoluteDir == b.absoluteDir); CHECK(a.relativePath == b.relativePath); CHECK(a.fileName == b.fileName); } static void testFileStem() { // fileStem is the stem component of fileName (no extension). The capture // backend passes fileStem directly to RENDER_PATTERN because REAPER appends // the format extension itself — the backend must not re-derive or re-strip it. BankPaths p = deriveBankPaths("C:/p", "master mix", "123"); CHECK(p.fileStem == "master_mix_123"); CHECK(p.fileName == "master_mix_123.wav"); // fileStem + ".wav" must equal fileName (the invariant the backend relies on). CHECK(p.fileStem + ".wav" == p.fileName); // No tag: stem only. BankPaths q = deriveBankPaths("C:/p", "kick", ""); CHECK(q.fileStem == "kick"); CHECK(q.fileName == "kick.wav"); CHECK(q.fileStem + ".wav" == q.fileName); } int main() { testNormalizeSlashes(); testSanitizeStem(); testDeriveRelativePathIsProjectRelative(); testDeriveAbsoluteDirJoinsProjectDir(); testDeriveHandlesTrailingSlashProjectDir(); testDeriveEmptyProjectDirFallsBackToRelative(); testDeterministicForSameInputs(); testFileStem(); if (g_fail == 0) std::printf("capture_paths: all tests passed\n"); else std::printf("capture_paths: %d CHECK(s) FAILED\n", g_fail); return g_fail ? 1 : 0; }