Land the package filesystem shell: streaming atomic package_io, journaled rollback carve-out, asymmetric platform pickers

This commit is contained in:
2026-08-02 07:36:25 -04:00
parent 09a9ef838f
commit 41a3016e63
11 changed files with 914 additions and 0 deletions
+228
View File
@@ -0,0 +1,228 @@
// 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 <cstdio>
#include <filesystem>
#include <fstream>
#include <string>
#include <utility>
#include <vector>
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<std::uint8_t> patternBytes(std::size_t n, std::uint8_t seed) {
std::vector<std::uint8_t> v(n);
for (std::size_t i = 0; i < n; ++i)
v[i] = static_cast<std::uint8_t>(seed + i * 7u);
return v;
}
static void writeScratchFile(const std::string& path,
const std::vector<std::uint8_t>& bytes) {
std::ofstream f(path, std::ios::binary | std::ios::trunc);
f.write(reinterpret_cast<const char*>(bytes.data()),
static_cast<std::streamsize>(bytes.size()));
}
static std::vector<std::uint8_t> readAll(const std::string& path) {
std::ifstream f(path, std::ios::binary);
return std::vector<std::uint8_t>(std::istreambuf_iterator<char>(f),
std::istreambuf_iterator<char>());
}
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<std::uint8_t> header = patternBytes(16, 0xA0);
const std::vector<std::vector<std::uint8_t>> entries = {
patternBytes(1000, 1), patternBytes(500, 2), patternBytes(1, 3)};
std::vector<std::string> 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<std::pair<std::uint64_t, std::uint64_t>> 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<std::uint8_t> 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<std::uint8_t> prior = patternBytes(32, 0x40);
writeScratchFile(dest, prior);
{
PackageFileWriter writer(dest);
const std::vector<std::uint8_t> 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<std::uint8_t> 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<std::uint8_t> 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<std::uint8_t> 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<std::uint8_t>(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<std::string> names = listFolderFileNames(dir);
CHECK(names == (std::vector<std::string>{"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;
}
+123
View File
@@ -0,0 +1,123 @@
// Standalone tests for shell/package/package_rollback — no REAPER, no framework.
// Pins the discriminator's mechanics: a file is recorded only when this journal's
// own write landed it, a pre-existing destination is refused untouched, and
// rollback deletes exactly the recorded set — a bystander file beside them stays,
// and a vanished file is tolerated rather than failed.
#include "../src/shell/package/package_rollback.h"
#include <cstdio>
#include <filesystem>
#include <fstream>
#include <string>
#include <vector>
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<std::uint8_t> patternBytes(std::size_t n, std::uint8_t seed) {
std::vector<std::uint8_t> v(n);
for (std::size_t i = 0; i < n; ++i)
v[i] = static_cast<std::uint8_t>(seed + i * 7u);
return v;
}
static void writeScratchFile(const std::string& path,
const std::vector<std::uint8_t>& bytes) {
std::ofstream f(path, std::ios::binary | std::ios::trunc);
f.write(reinterpret_cast<const char*>(bytes.data()),
static_cast<std::streamsize>(bytes.size()));
}
static std::vector<std::uint8_t> readAll(const std::string& path) {
std::ifstream f(path, std::ios::binary);
return std::vector<std::uint8_t>(std::istreambuf_iterator<char>(f),
std::istreambuf_iterator<char>());
}
static void testLandRecordsOnSuccessOnly() {
LandedFileJournal journal;
const std::string path = "rb_land.bin";
const std::vector<std::uint8_t> bytes = patternBytes(32, 1);
CHECK(journal.writeLandedFile(path, PayloadBuffer(bytes)));
CHECK(readAll(path) == bytes);
CHECK(journal.landedPaths() == (std::vector<std::string>{path}));
CHECK(!fs::exists(path + ".rsbanktmp"));
journal.rollback();
CHECK(!fs::exists(path));
}
static void testExistingDestinationRefusedUntouched() {
LandedFileJournal journal;
const std::string path = "rb_existing.bin";
const std::vector<std::uint8_t> original = patternBytes(16, 0x60);
writeScratchFile(path, original);
CHECK(!journal.writeLandedFile(path, PayloadBuffer(patternBytes(8, 1))));
CHECK(readAll(path) == original); // never overwritten
CHECK(journal.empty()); // a refused write is not recorded
const RollbackResult result = journal.rollback();
CHECK(result.deletedCount == 0);
CHECK(fs::exists(path)); // rollback cannot touch a file it did not write
std::error_code ec;
fs::remove(path, ec);
}
static void testEmptyPayloadRefused() {
LandedFileJournal journal;
CHECK(!journal.writeLandedFile("rb_empty.bin", PayloadBuffer{}));
CHECK(!fs::exists("rb_empty.bin"));
CHECK(journal.empty());
}
static void testRollbackDeletesExactlyTheRecordedSet() {
LandedFileJournal journal;
CHECK(journal.writeLandedFile("rb_a.bin", PayloadBuffer(patternBytes(8, 1))));
CHECK(journal.writeLandedFile("rb_c.bin", PayloadBuffer(patternBytes(8, 2))));
writeScratchFile("rb_bystander.bin", patternBytes(8, 3)); // not journal-written
const RollbackResult result = journal.rollback();
CHECK(result.deletedCount == 2);
CHECK(result.alreadyAbsentCount == 0);
CHECK(result.failedCount == 0);
CHECK(!fs::exists("rb_a.bin"));
CHECK(!fs::exists("rb_c.bin"));
CHECK(fs::exists("rb_bystander.bin")); // exactly the given files, nothing else
CHECK(journal.empty());
const RollbackResult second = journal.rollback(); // cleared: a no-op
CHECK(second.deletedCount == 0);
CHECK(fs::exists("rb_bystander.bin"));
std::error_code ec;
fs::remove("rb_bystander.bin", ec);
}
static void testVanishedFileIsToleratedNotFailed() {
LandedFileJournal journal;
CHECK(journal.writeLandedFile("rb_gone.bin", PayloadBuffer(patternBytes(8, 1))));
std::error_code ec;
fs::remove("rb_gone.bin", ec); // vanished between land and rollback
CHECK(!ec);
const RollbackResult result = journal.rollback();
CHECK(result.deletedCount == 0);
CHECK(result.alreadyAbsentCount == 1);
CHECK(result.failedCount == 0);
}
int main() {
testLandRecordsOnSuccessOnly();
testExistingDestinationRefusedUntouched();
testEmptyPayloadRefused();
testRollbackDeletesExactlyTheRecordedSet();
testVanishedFileIsToleratedNotFailed();
if (g_fail == 0) std::printf("package_rollback: all tests passed\n");
else std::printf("package_rollback: %d CHECK(s) FAILED\n", g_fail);
return g_fail == 0 ? 0 : 1;
}