// Standalone tests for shell/package/package_io — no REAPER, no framework. Failure is // injected at the writer seam (abandonment, open failure, rename failure) rather than // simulated, and the streaming claim is asserted against PayloadBuffer::alive(). #include "../src/shell/package/package_io.h" #include "../src/shell/package/package_path.h" #include #include #include #include #include #include #include using namespace reasampler; namespace fs = std::filesystem; 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 std::vector patternBytes(std::size_t n, std::uint8_t seed) { std::vector v(n); for (std::size_t i = 0; i < n; ++i) v[i] = static_cast(seed + i * 7u); return v; } static bool sameBytes(const PayloadBuffer& p, const std::vector& want) { return p.size() == want.size() && (want.empty() || std::equal(want.begin(), want.end(), p.data())); } static void writeScratchFile(const std::string& path, const std::vector& bytes) { std::ofstream f(utf8Path(path), std::ios::binary | std::ios::trunc); f.write(reinterpret_cast(bytes.data()), static_cast(bytes.size())); } static std::vector readAll(const std::string& path) { std::ifstream f(utf8Path(path), std::ios::binary); return std::vector(std::istreambuf_iterator(f), std::istreambuf_iterator()); } static bool exists(const std::string& path) { return fs::exists(utf8Path(path)); } static void removeQuietly(const std::string& path) { std::error_code ec; fs::remove(utf8Path(path), ec); } static void testPayloadCounterTracksMovesNotCopies() { CHECK(PayloadBuffer::alive() == 0); { PayloadBuffer a(patternBytes(4, 1)); CHECK(PayloadBuffer::alive() == 1); PayloadBuffer b = std::move(a); CHECK(PayloadBuffer::alive() == 1); // the count moved with the bytes PayloadBuffer c; c = std::move(b); CHECK(PayloadBuffer::alive() == 1); CHECK(c.size() == 4); } CHECK(PayloadBuffer::alive() == 0); { PayloadBuffer empty; CHECK(PayloadBuffer::alive() == 0); // holding nothing counts as nothing } } static void testStreamingRoundTripHoldsOnePayload() { const std::string dest = "pkg_io_scratch.rsbank"; const std::vector header = patternBytes(16, 0xA0); const std::vector> entries = { patternBytes(1000, 1), patternBytes(500, 2), patternBytes(1, 3)}; std::vector srcs; for (std::size_t i = 0; i < entries.size(); ++i) { srcs.push_back("pkg_io_src" + std::to_string(i) + ".bin"); writeScratchFile(srcs[i], entries[i]); } CHECK(PayloadBuffer::alive() == 0); std::vector> layout; // offset, length { PackageFileWriter writer(dest); CHECK(writer.ok()); CHECK(writer.appendRaw(header.data(), header.size())); std::uint64_t offset = header.size(); for (std::size_t i = 0; i < entries.size(); ++i) { PayloadBuffer p = readFilePayload(srcs[i]); CHECK(sameBytes(p, entries[i])); CHECK(PayloadBuffer::alive() == 1); // exactly one entry in memory CHECK(writer.appendPayload(p)); layout.emplace_back(offset, p.size()); offset += p.size(); } CHECK(PayloadBuffer::alive() == 0); // each released before the next CHECK(writer.commit()); CHECK(!writer.commit()); // a second commit is refused } CHECK(!exists(dest + ".rsbanktmp")); CHECK(exists(dest)); { // Scoped: the reader holds the file open, and Windows refuses to delete an // open file — cleanup below needs it closed first. PackageFileReader reader(dest); CHECK(reader.ok()); CHECK(reader.fileSize() == header.size() + 1000 + 500 + 1); for (std::size_t i = 0; i < entries.size(); ++i) { PayloadBuffer p = reader.readRange(layout[i].first, layout[i].second); CHECK(PayloadBuffer::alive() == 1); // one entry per readRange, no more CHECK(sameBytes(p, entries[i])); } CHECK(PayloadBuffer::alive() == 0); } for (const std::string& s : srcs) removeQuietly(s); removeQuietly(dest); } static void testEmptyPayloadIsRefusedAndPoisonsTheWriter() { // The failure this guards: an unreadable source yields an empty payload, and a // verb that trusted a `true` here would commit framing claiming bytes nobody wrote. const std::string dest = "pkg_io_emptypayload.rsbank"; { PackageFileWriter writer(dest); const std::vector head = patternBytes(4, 1); CHECK(writer.appendRaw(head.data(), head.size())); CHECK(!writer.appendPayload(PayloadBuffer{})); CHECK(!writer.ok()); CHECK(!writer.commit()); // the short stream can never reach the destination } CHECK(!exists(dest)); CHECK(!exists(dest + ".rsbanktmp")); // A zero-length appendRaw stays tolerated — framing has legitimate empty edges. { PackageFileWriter writer(dest); CHECK(writer.appendRaw(nullptr, 0)); CHECK(writer.ok()); writer.abort(); } } static void testAbandonedWriteLeavesNoDestination() { const std::string dest = "pkg_io_abandon.rsbank"; { PackageFileWriter writer(dest); const std::vector some = patternBytes(64, 9); CHECK(writer.appendRaw(some.data(), some.size())); // no commit — destruction is the injected interruption } CHECK(!exists(dest)); CHECK(!exists(dest + ".rsbanktmp")); } static void testAbortPreservesPriorContents() { const std::string dest = "pkg_io_prior.rsbank"; const std::vector prior = patternBytes(32, 0x40); writeScratchFile(dest, prior); { PackageFileWriter writer(dest); const std::vector some = patternBytes(64, 9); CHECK(writer.appendRaw(some.data(), some.size())); writer.abort(); CHECK(!writer.appendRaw(some.data(), some.size())); // dead after abort CHECK(!writer.commit()); } CHECK(readAll(dest) == prior); CHECK(!exists(dest + ".rsbanktmp")); removeQuietly(dest); } static void testCommitReplacesAnExistingFile() { // The module's one deliberate asymmetry against LandedFileJournal's never-overwrite // rule: the save dialog's overwrite confirm is the consent, so commit() replaces. const std::string dest = "pkg_io_replace.rsbank"; const std::vector prior = patternBytes(40, 0x11); const std::vector fresh = patternBytes(7, 0x22); writeScratchFile(dest, prior); { PackageFileWriter writer(dest); CHECK(writer.ok()); CHECK(writer.appendRaw(fresh.data(), fresh.size())); CHECK(writer.commit()); } CHECK(readAll(dest) == fresh); CHECK(!exists(dest + ".rsbanktmp")); removeQuietly(dest); } static void testOpenFailureIsInert() { const std::string dest = "pkg_io_no_such_dir/x.rsbank"; PackageFileWriter writer(dest); CHECK(!writer.ok()); const std::vector some = patternBytes(8, 1); CHECK(!writer.appendRaw(some.data(), some.size())); CHECK(!writer.commit()); CHECK(!exists("pkg_io_no_such_dir")); } static void testCommitRenameFailureSelfCleans() { // A directory squatting on the destination makes the final rename fail — a real // injected commit failure, not a simulated one. const std::string dest = "pkg_io_dir.rsbank"; std::error_code ec; fs::create_directory(utf8Path(dest), ec); CHECK(!ec); { PackageFileWriter writer(dest); CHECK(writer.ok()); const std::vector some = patternBytes(8, 1); CHECK(writer.appendRaw(some.data(), some.size())); CHECK(!writer.commit()); } CHECK(fs::is_directory(utf8Path(dest))); // prior state intact CHECK(!exists(dest + ".rsbanktmp")); fs::remove(utf8Path(dest), ec); } static void testReaderEdges() { PackageFileReader missing("pkg_io_no_such_file.rsbank"); CHECK(!missing.ok()); CHECK(missing.readRange(0, 1).empty()); const std::string path = "pkg_io_edges.bin"; const std::vector bytes = patternBytes(10, 5); writeScratchFile(path, bytes); { // Scoped: the reader must be closed before the cleanup remove below. PackageFileReader reader(path); CHECK(reader.ok()); CHECK(reader.fileSize() == 10); CHECK(reader.readRange(5, 10).empty()); // past the end CHECK(reader.readRange(10, 1).empty()); // starts at the end CHECK(reader.readRange(0, 0).empty()); // zero length is failure, one branch CHECK(sameBytes(reader.readRange(2, 3), std::vector(bytes.begin() + 2, bytes.begin() + 5))); } removeQuietly(path); } static void testFileStatusSeparatesAbsentFromUnreadable() { CHECK(fileStatus("pkg_io_no_such_file.bin") == FileStatus::Absent); const std::string path = "pkg_io_status.bin"; writeScratchFile(path, patternBytes(4, 1)); CHECK(fileStatus(path) == FileStatus::Present); removeQuietly(path); // A directory in a file's place is not a readable file — the export message must // not report it as simply missing. const std::string dir = "pkg_io_status_dir"; std::error_code ec; fs::create_directory(utf8Path(dir), ec); CHECK(fileStatus(dir) == FileStatus::Unreadable); fs::remove(utf8Path(dir), ec); } static void testNonAsciiPathsRoundTripAsUtf8() { // "café" in UTF-8. Windows decodes a narrow std::filesystem path through the ANSI // code page, so an unconverted seam lands "café" or fails outright. const std::string dir = "pkg_io_caf\xC3\xA9_dir"; const std::string name = "caf\xC3\xA9.rsbank"; std::error_code ec; fs::create_directory(utf8Path(dir), ec); CHECK(!ec); const std::string dest = dir + "/" + name; const std::vector bytes = patternBytes(24, 0x5A); { PackageFileWriter writer(dest); CHECK(writer.ok()); CHECK(writer.appendRaw(bytes.data(), bytes.size())); CHECK(writer.commit()); } CHECK(exists(dest)); CHECK(fileStatus(dest) == FileStatus::Present); CHECK(sameBytes(readFilePayload(dest), bytes)); // The listing must hand the name back in the same encoding it was given. CHECK(listFolderFileNames(dir) == (std::vector{name})); fs::remove_all(utf8Path(dir), ec); } static void testWriteFileExclusiveRefusesAnOccupiedPath() { const std::string path = "pkg_io_excl.bin"; const std::vector mine = patternBytes(12, 3); CHECK(writeFileExclusive(path, PayloadBuffer(mine))); CHECK(readAll(path) == mine); // The create IS the check: a second call cannot replace the first file's bytes. CHECK(!writeFileExclusive(path, PayloadBuffer(patternBytes(9, 8)))); CHECK(readAll(path) == mine); CHECK(!writeFileExclusive("pkg_io_excl_empty.bin", PayloadBuffer{})); CHECK(!exists("pkg_io_excl_empty.bin")); removeQuietly(path); } static void testListFolderFileNames() { const std::string dir = "pkg_io_listdir"; std::error_code ec; fs::create_directory(utf8Path(dir), ec); writeScratchFile(dir + "/b.bin", patternBytes(2, 1)); writeScratchFile(dir + "/a.bin", patternBytes(2, 2)); fs::create_directory(utf8Path(dir + "/sub"), ec); writeScratchFile(dir + "/sub/c.bin", patternBytes(2, 3)); const std::vector names = listFolderFileNames(dir); CHECK(names == (std::vector{"a.bin", "b.bin"})); // sorted, bare, non-recursive CHECK(listFolderFileNames("pkg_io_no_such_dir").empty()); fs::remove_all(utf8Path(dir), ec); } int main() { testPayloadCounterTracksMovesNotCopies(); testStreamingRoundTripHoldsOnePayload(); testEmptyPayloadIsRefusedAndPoisonsTheWriter(); testAbandonedWriteLeavesNoDestination(); testAbortPreservesPriorContents(); testCommitReplacesAnExistingFile(); testOpenFailureIsInert(); testCommitRenameFailureSelfCleans(); testReaderEdges(); testFileStatusSeparatesAbsentFromUnreadable(); testNonAsciiPathsRoundTripAsUtf8(); testWriteFileExclusiveRefusesAnOccupiedPath(); testListFolderFileNames(); if (g_fail == 0) std::printf("package_io: all tests passed\n"); else std::printf("package_io: %d CHECK(s) FAILED\n", g_fail); return g_fail == 0 ? 0 : 1; }