// Standalone tests for shell/package/package_io — no REAPER, no framework. Pins the // two properties the seam exists for: an interrupted or failed write leaves the // destination absent or holding its prior contents (failure injected at the writer // seam — abandonment, open failure, rename failure), and a multi-entry round trip // holds at most one entry's payload, asserted against PayloadBuffer::alive() — the // seam counter — rather than a memory measurement. #include "../src/shell/package/package_io.h" #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 void writeScratchFile(const std::string& path, const std::vector& bytes) { std::ofstream f(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(path, std::ios::binary); return std::vector(std::istreambuf_iterator(f), std::istreambuf_iterator()); } 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(p.bytes() == 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(!fs::exists(dest + ".rsbanktmp")); CHECK(fs::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(p.bytes() == entries[i]); } CHECK(PayloadBuffer::alive() == 0); } std::error_code ec; for (const std::string& s : srcs) fs::remove(s, ec); fs::remove(dest, ec); } 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(!fs::exists(dest)); CHECK(!fs::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(!fs::exists(dest + ".rsbanktmp")); std::error_code ec; fs::remove(dest, ec); } 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(!fs::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(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(dest)); // prior state intact CHECK(!fs::exists(dest + ".rsbanktmp")); fs::remove(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 const PayloadBuffer slice = reader.readRange(2, 3); CHECK(slice.bytes() == std::vector(bytes.begin() + 2, bytes.begin() + 5)); } std::error_code ec; fs::remove(path, ec); } static void testListFolderFileNames() { const std::string dir = "pkg_io_listdir"; std::error_code ec; fs::create_directory(dir, ec); writeScratchFile(dir + "/b.bin", patternBytes(2, 1)); writeScratchFile(dir + "/a.bin", patternBytes(2, 2)); fs::create_directory(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(dir, ec); } int main() { testPayloadCounterTracksMovesNotCopies(); testStreamingRoundTripHoldsOnePayload(); testAbandonedWriteLeavesNoDestination(); testAbortPreservesPriorContents(); testOpenFailureIsInert(); testCommitRenameFailureSelfCleans(); testReaderEdges(); 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; }