Files
UniXNU/drivers/NativeRamBlock/Test/persist.cpp
T
2026-10-19 18:49:31 +08:00

77 lines
5.9 KiB
C++

// Host test for the persistent overlay core: a model medium with fault injection (torn writes, lost cache, write refusal).
#include <cassert>
#include <cstdio>
#include <vector>
#include "../PersistOverlay.h"
using namespace persist;
struct Medium {
std::vector<uint8_t> durable, cache; // cache holds writes not yet flushed
std::vector<std::pair<uint64_t, std::vector<uint8_t>>> pending;
unsigned writes = 0, syncs = 0, failWriteAt = ~0u, failSyncAt = ~0u, reads = 0;
explicit Medium(uint64_t blocks) : durable(blocks * kBlock, 0xA5) {} // never-written medium holds junk, not zeros
bool read(uint64_t b, uint32_t n, void *d) {
++reads;
for (uint32_t i = 0; i < n; ++i) { std::vector<uint8_t> v(durable.begin() + (b + i) * kBlock, durable.begin() + (b + i + 1) * kBlock);
for (auto &p : pending) if (p.first == b + i) v = p.second; memcpy((uint8_t *)d + i * kBlock, v.data(), kBlock); }
return true; }
bool write(uint64_t b, uint32_t n, const void *s) {
if (writes++ == failWriteAt) return false;
for (uint32_t i = 0; i < n; ++i) pending.push_back({b + i, std::vector<uint8_t>((const uint8_t *)s + i * kBlock, (const uint8_t *)s + (i + 1) * kBlock)});
return true; }
bool sync() { if (syncs++ == failSyncAt) return false;
for (auto &p : pending) memcpy(&durable[p.first * kBlock], p.second.data(), kBlock);
pending.clear(); return true; }
void powerLoss() { pending.clear(); } // unflushed writes vanish
};
static std::vector<uint8_t> pageOf(uint8_t v) { return std::vector<uint8_t>(kBlock, v); }
int main() {
const uint64_t pages = 65536 + 7; // forces two bitmap blocks and a ragged last byte
uint8_t seedA[32], seedB[32]; memset(seedA, 1, 32); memset(seedB, 2, 32);
assert(bitmapBlocks(pages) == 3);
assert(bitmapBlocks(32768) == 1 && bitmapBlocks(32769) == 2 && bitmapBlocks(524288) == 16);
assert(windowBlocks(524288) == 1 + 16 + 524288);
Medium m(windowBlocks(pages));
std::vector<uint8_t> bits(bitmapBlocks(pages) * kBlock), dirty(bitmapBlocks(pages));
// Blank medium: read-only open must not write and reports Blank; read-write initialises it.
{ Overlay o; assert(o.open(m, pages, seedA, true, bits.data(), dirty.data()) == Open::Blank && m.writes == 0 && !o.active && o.header == 1); }
Overlay o; assert(o.open(m, pages, seedA, false, bits.data(), dirty.data()) == Open::Fresh && o.active && o.present == 0 && o.generation == 1);
// Write pages, sync, reopen: resumed with exactly those pages.
assert(o.writePage(m, 0, pageOf(0x11).data()) && o.writePage(m, 40000, pageOf(0x22).data()) && o.writePage(m, pages - 1, pageOf(0x33).data()));
assert(!o.writePage(m, pages, pageOf(0).data()));
assert(o.sync(m));
{ Medium copy = m; Overlay r; std::vector<uint8_t> b2(bits.size()), d2(dirty.size());
assert(r.open(copy, pages, seedA, true, b2.data(), d2.data()) == Open::Resumed && r.present == 3 && r.has(0) && r.has(40000) && r.has(pages - 1) && !r.has(1));
unsigned seen = 0; struct L { unsigned *seen; bool operator()(uint64_t i, const uint8_t *p) { ++*seen; return p[0] == (i == 0 ? 0x11 : i == 40000 ? 0x22 : 0x33) && p[4095] == p[0]; } } load{&seen};
assert(r.forEachPresent(copy, load) && seen == 3);
assert(!r.writePage(copy, 1, pageOf(9).data()) && !r.sync(copy)); // read-only really is read-only
}
// A different seed (the seed was rebuilt) makes the overlay stale: it is replaced, not applied.
{ Medium copy = m; Overlay r; std::vector<uint8_t> b2(bits.size()), d2(dirty.size());
assert(r.open(copy, pages, seedB, true, b2.data(), d2.data()) == Open::Blank && r.header == 3 && r.present == 0);
assert(r.open(copy, pages, seedB, false, b2.data(), d2.data()) == Open::Fresh && r.generation == 2 && r.present == 0); }
// Power loss before sync: pages written after the last sync are gone, and no bit promises them.
assert(o.writePage(m, 5, pageOf(0x44).data())); m.powerLoss();
{ Overlay r; std::vector<uint8_t> b2(bits.size()), d2(dirty.size());
assert(r.open(m, pages, seedA, true, b2.data(), d2.data()) == Open::Resumed && !r.has(5) && r.present == 3); }
// Torn sync: pages flushed, bitmap write fails: the next open sees the old bitmap (page 5 absent), never a bit without its page.
{ Medium t = m; Overlay w; std::vector<uint8_t> b2(bits.size()), d2(dirty.size());
assert(w.open(t, pages, seedA, false, b2.data(), d2.data()) == Open::Resumed);
assert(w.writePage(t, 6, pageOf(0x55).data())); t.failWriteAt = t.writes; // the data write already happened; the bitmap write that sync() issues next fails
assert(!w.sync(t)); t.powerLoss();
Overlay r; std::vector<uint8_t> b3(bits.size()), d3(dirty.size());
assert(r.open(t, pages, seedA, true, b3.data(), d3.data()) == Open::Resumed && !r.has(6)); }
// Damaged header (flip a bit): reported as damaged, read-write open reinitialises with a higher generation.
{ Medium t = m; t.durable[20] ^= 1; Overlay r; std::vector<uint8_t> b2(bits.size()), d2(dirty.size());
assert(r.open(t, pages, seedA, true, b2.data(), d2.data()) == Open::Blank && r.header == 2);
assert(r.open(t, pages, seedA, false, b2.data(), d2.data()) == Open::Fresh && r.present == 0); }
// Bits beyond the last page are corruption.
{ Medium t = m; Overlay r; std::vector<uint8_t> b2(bits.size()), d2(dirty.size());
t.durable[1 * kBlock + 2 * kBlock + 100] |= 0x80; // inside bitmap block 3 (last), page index >= pages
assert(r.open(t, pages, seedA, true, b2.data(), d2.data()) == Open::Failed); }
// Failed initialisation leaves no valid header.
{ Medium t(windowBlocks(pages)); t.failSyncAt = 0; Overlay r; std::vector<uint8_t> b2(bits.size()), d2(dirty.size());
assert(r.open(t, pages, seedA, false, b2.data(), d2.data()) == Open::Failed); t.powerLoss();
Overlay q; assert(q.open(t, pages, seedA, true, b2.data(), d2.data()) == Open::Blank && q.header == 1); }
puts("PASS: overlay header, bitmap, ordering, stale seed, torn writes, power loss");
}