mirror of
https://github.com/MeshEnvy/lobbs.git
synced 2026-10-31 02:11:59 +01:00
feat(lofs): exclusive /lofs mount with LoLog
Single LoLog slot on QSPI or nRF flash2; sd/lofs/extra/internal order. Host lolog tests; drop qspi/spare/reserve mount names.
This commit is contained in:
1 parent
1243b2bc1b
commit
22c8567c4f
34 files changed
+3117
-106
No files matched your search
@@ -26,3 +26,6 @@ build_src_filter =
|
||||
+<LoBBSConfig.h>
|
||||
+<LoBBSStackGuard.cpp>
|
||||
+<LoBBSStackGuard.h>
|
||||
+<lofs/lolog/**>
|
||||
+<lofs/volumes/LoFSRawDevice.cpp>
|
||||
+<lofs/volumes/LoFSRawDevice.h>
|
||||
@@ -0,0 +1,19 @@
|
||||
# LoLog host tests
|
||||
|
||||
Run from repo root:
|
||||
|
||||
```bash
|
||||
cd ci/host && pio test -e host -f test_lolog_host
|
||||
```
|
||||
|
||||
Coverage (Unity):
|
||||
|
||||
| Area | Tests |
|
||||
|------|--------|
|
||||
| Boot / mount | Empty format, erased chip, pure DATA replay, reboot |
|
||||
| File ops | Nested mkdir, overwrite, rename, rmdir, prefix names, 2.5 KB EXTENT file, writeAt, stat |
|
||||
| Crash / ACID | Fault on every prog byte (large commit), every prog call, torn tail + append, CRC poke, partial mkdir group |
|
||||
| Index / clean | Index flush remount, bulk flush, tomb shadow, delete when full, cleaner churn, headroom |
|
||||
| Fuzz | 30 rounds random ops + crash injection vs ref model |
|
||||
|
||||
Fault injection uses `LoLogRamDevice` (`setFaultAfterProgBytes`, `setFaultOnProgCall`, `poke`).
|
||||
@@ -0,0 +1,63 @@
|
||||
#pragma once
|
||||
|
||||
#include "lofs/lolog/LoLog.h"
|
||||
#include "lofs/lolog/LoLogRamDevice.h"
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
#include <unity.h>
|
||||
#include <vector>
|
||||
|
||||
inline bool lologWriteFile(LoLog &log, const char *path, const uint8_t *data, size_t len)
|
||||
{
|
||||
LoLog::OpenHandle *h = log.open(path, true, true);
|
||||
if (!h)
|
||||
return false;
|
||||
size_t off = 0;
|
||||
while (off < len) {
|
||||
size_t n = log.writeHandle(h, data + off, len - off);
|
||||
if (n == 0)
|
||||
break;
|
||||
off += n;
|
||||
}
|
||||
log.flushHandle(h);
|
||||
log.closeHandle(h);
|
||||
return off == len;
|
||||
}
|
||||
|
||||
inline bool lologReadFile(LoLog &log, const char *path, std::vector<uint8_t> &out)
|
||||
{
|
||||
LoLog::OpenHandle *h = log.open(path, false, false);
|
||||
if (!h)
|
||||
return false;
|
||||
out.clear();
|
||||
uint8_t buf[256];
|
||||
for (;;) {
|
||||
size_t n = log.readHandle(h, buf, sizeof(buf));
|
||||
if (n == 0)
|
||||
break;
|
||||
out.insert(out.end(), buf, buf + n);
|
||||
}
|
||||
log.closeHandle(h);
|
||||
return true;
|
||||
}
|
||||
|
||||
inline int lologCountDir(LoLog &log, const char *dirPath)
|
||||
{
|
||||
LoLog::OpenHandle *dir = log.openDir(dirPath);
|
||||
if (!dir)
|
||||
return -1;
|
||||
int n = 0;
|
||||
while (LoLog::OpenHandle *e = log.openNextInDir(dir)) {
|
||||
log.closeHandle(e);
|
||||
n++;
|
||||
}
|
||||
log.closeHandle(dir);
|
||||
return n;
|
||||
}
|
||||
|
||||
/** Fresh chip: all 0xFF, mount without explicit format. */
|
||||
inline bool lologMountErasedChip(LoLogRamDevice &dev, LoLog &log)
|
||||
{
|
||||
dev.reset();
|
||||
return log.mount();
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
#pragma once
|
||||
|
||||
#include "lolog_test_helpers.hpp"
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
/** In-memory reference tree for fuzz / crash oracle (paths without leading slash). */
|
||||
class RefFs {
|
||||
public:
|
||||
bool mkdir(const std::string &path)
|
||||
{
|
||||
if (path.empty() || files.count(path))
|
||||
return false;
|
||||
dirs.insert(path);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool write(const std::string &path, const std::vector<uint8_t> &data)
|
||||
{
|
||||
if (path.empty())
|
||||
return false;
|
||||
files[path] = data;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool unlink(const std::string &path) { return files.erase(path) > 0; }
|
||||
|
||||
bool rename(const std::string &from, const std::string &to)
|
||||
{
|
||||
auto it = files.find(from);
|
||||
if (it == files.end())
|
||||
return false;
|
||||
if (files.count(to))
|
||||
return false;
|
||||
files[to] = it->second;
|
||||
files.erase(it);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool existsFile(const std::string &path) const { return files.count(path) > 0; }
|
||||
|
||||
const std::map<std::string, std::vector<uint8_t>> &allFiles() const { return files; }
|
||||
|
||||
void clear()
|
||||
{
|
||||
files.clear();
|
||||
dirs.clear();
|
||||
}
|
||||
|
||||
private:
|
||||
std::map<std::string, std::vector<uint8_t>> files;
|
||||
std::set<std::string> dirs;
|
||||
};
|
||||
|
||||
inline bool lologMatchesRef(LoLog &log, const RefFs &ref)
|
||||
{
|
||||
for (const auto &kv : ref.allFiles()) {
|
||||
if (!log.exists(kv.first.c_str()))
|
||||
return false;
|
||||
std::vector<uint8_t> got;
|
||||
if (!lologReadFile(log, kv.first.c_str(), got))
|
||||
return false;
|
||||
if (got != kv.second)
|
||||
return false;
|
||||
}
|
||||
for (const auto &kv : ref.allFiles()) {
|
||||
if (log.exists(kv.first.c_str()) && !ref.existsFile(kv.first))
|
||||
return false;
|
||||
}
|
||||
std::vector<std::string> logPaths;
|
||||
for (int i = 0; i < 512; i++) {
|
||||
char p[32];
|
||||
snprintf(p, sizeof(p), "fuzz_%d.dat", i);
|
||||
if (log.exists(p))
|
||||
logPaths.push_back(p);
|
||||
}
|
||||
for (const auto &p : logPaths) {
|
||||
if (!ref.existsFile(p))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
#include "lolog_test_helpers.hpp"
|
||||
#include <unity.h>
|
||||
|
||||
extern LoLogRamDevice gDev;
|
||||
extern LoLog gLog;
|
||||
|
||||
void test_mount_empty_after_format(void)
|
||||
{
|
||||
TEST_ASSERT_FALSE(gLog.exists("anything"));
|
||||
TEST_ASSERT_TRUE(gLog.isDirectory(""));
|
||||
}
|
||||
|
||||
void test_mount_erased_chip_without_format(void)
|
||||
{
|
||||
LoLogRamDevice dev(16);
|
||||
LoLog log(dev);
|
||||
TEST_ASSERT_TRUE(lologMountErasedChip(dev, log));
|
||||
}
|
||||
|
||||
void test_pure_replay_no_index_runs(void)
|
||||
{
|
||||
TEST_ASSERT_TRUE(gLog.mkdir("replay"));
|
||||
std::vector<uint8_t> payload = {'a', 'b', 'c'};
|
||||
TEST_ASSERT_TRUE(lologWriteFile(gLog, "replay/r1.ls", payload.data(), payload.size()));
|
||||
|
||||
LoLog g2(gDev);
|
||||
TEST_ASSERT_TRUE(g2.mount());
|
||||
std::vector<uint8_t> readback;
|
||||
TEST_ASSERT_TRUE(lologReadFile(g2, "replay/r1.ls", readback));
|
||||
TEST_ASSERT_EQUAL_UINT32(payload.size(), readback.size());
|
||||
}
|
||||
|
||||
void test_mount_after_reboot_simulation(void)
|
||||
{
|
||||
TEST_ASSERT_TRUE(gLog.mkdir("boot"));
|
||||
TEST_ASSERT_TRUE(lologWriteFile(gLog, "boot/persist.ls", (const uint8_t *)"ok", 2));
|
||||
|
||||
LoLog after(gDev);
|
||||
TEST_ASSERT_TRUE(after.mount());
|
||||
TEST_ASSERT_TRUE(after.exists("boot/persist.ls"));
|
||||
}
|
||||
@@ -0,0 +1,235 @@
|
||||
#include "lolog_test_helpers.hpp"
|
||||
#include "lofs/lolog/LoLogConfig.h"
|
||||
#include <unity.h>
|
||||
|
||||
extern LoLogRamDevice gDev;
|
||||
extern LoLog gLog;
|
||||
|
||||
static uint32_t progCallsForLargeCommit(LoLogRamDevice &dev)
|
||||
{
|
||||
dev.reset();
|
||||
LoLog log(dev);
|
||||
log.format();
|
||||
log.mkdir("gc");
|
||||
LoLog::OpenHandle *h = log.open("gc/big.bin", true, true);
|
||||
if (!h)
|
||||
return 0;
|
||||
dev.clearProgCallCount();
|
||||
std::vector<uint8_t> payload(2500, 0x5A);
|
||||
log.writeHandle(h, payload.data(), payload.size());
|
||||
log.flushHandle(h);
|
||||
log.closeHandle(h);
|
||||
return dev.progCallCount();
|
||||
}
|
||||
|
||||
static bool groupVisibleAfterRemount(LoLogRamDevice &dev, const char *path, size_t expectLen)
|
||||
{
|
||||
LoLog log(dev);
|
||||
if (!log.mount())
|
||||
return false;
|
||||
if (!log.exists(path))
|
||||
return expectLen == 0;
|
||||
std::vector<uint8_t> got;
|
||||
if (!lologReadFile(log, path, got))
|
||||
return false;
|
||||
return got.size() == expectLen;
|
||||
}
|
||||
|
||||
void test_commit_group_atomic_each_prog_call(void)
|
||||
{
|
||||
const uint32_t calls = progCallsForLargeCommit(gDev);
|
||||
TEST_ASSERT_GREATER_THAN(1, calls);
|
||||
|
||||
for (uint32_t fault = 1; fault <= calls; fault++) {
|
||||
gDev.reset();
|
||||
LoLog log(gDev);
|
||||
log.format();
|
||||
TEST_ASSERT_TRUE(log.mkdir("gc"));
|
||||
gDev.setFaultOnProgCall(fault);
|
||||
LoLog::OpenHandle *h = log.open("gc/big.bin", true, true);
|
||||
if (h) {
|
||||
std::vector<uint8_t> payload(2500, 0x5A);
|
||||
log.writeHandle(h, payload.data(), payload.size());
|
||||
log.flushHandle(h);
|
||||
log.closeHandle(h);
|
||||
}
|
||||
gDev.setFaultOnProgCall(0);
|
||||
|
||||
const bool full = groupVisibleAfterRemount(gDev, "gc/big.bin", 2500);
|
||||
if (!full) {
|
||||
LoLog absent(gDev);
|
||||
absent.mount();
|
||||
if (absent.exists("gc/big.bin")) {
|
||||
std::vector<uint8_t> got;
|
||||
lologReadFile(absent, "gc/big.bin", got);
|
||||
TEST_ASSERT_EQUAL_UINT32(0, got.size());
|
||||
}
|
||||
} else {
|
||||
LoLog check(gDev);
|
||||
check.mount();
|
||||
std::vector<uint8_t> got;
|
||||
lologReadFile(check, "gc/big.bin", got);
|
||||
for (uint8_t b : got)
|
||||
TEST_ASSERT_EQUAL_UINT8(0x5A, b);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void test_commit_group_partial_never_applied(void)
|
||||
{
|
||||
gDev.reset();
|
||||
LoLog log(gDev);
|
||||
log.format();
|
||||
TEST_ASSERT_TRUE(log.mkdir("p"));
|
||||
gDev.setFaultOnProgCall(1);
|
||||
TEST_ASSERT_FALSE(log.mkdir("p/sub"));
|
||||
gDev.setFaultOnProgCall(0);
|
||||
LoLog rem(gDev);
|
||||
rem.mount();
|
||||
TEST_ASSERT_FALSE(rem.exists("p/sub"));
|
||||
}
|
||||
|
||||
void test_commit_group_each_prog_byte(void)
|
||||
{
|
||||
LoLogRamDevice measure(32);
|
||||
measure.reset();
|
||||
LoLog mlog(measure);
|
||||
mlog.format();
|
||||
mlog.mkdir("by");
|
||||
measure.clearProgBytes();
|
||||
std::vector<uint8_t> payload(2500, 0x11);
|
||||
LoLog::OpenHandle *mh = mlog.open("by/big.bin", true, true);
|
||||
TEST_ASSERT_NOT_NULL(mh);
|
||||
mlog.writeHandle(mh, payload.data(), payload.size());
|
||||
mlog.flushHandle(mh);
|
||||
mlog.closeHandle(mh);
|
||||
const uint64_t totalBytes = measure.progBytes();
|
||||
TEST_ASSERT_GREATER_THAN(8, totalBytes);
|
||||
|
||||
for (uint64_t cut = 1; cut <= totalBytes; cut++) {
|
||||
measure.reset();
|
||||
LoLog l(measure);
|
||||
l.format();
|
||||
l.mkdir("by");
|
||||
measure.setFaultAfterProgBytes((uint32_t)cut);
|
||||
LoLog::OpenHandle *lh = l.open("by/big.bin", true, true);
|
||||
if (lh) {
|
||||
l.writeHandle(lh, payload.data(), payload.size());
|
||||
l.flushHandle(lh);
|
||||
l.closeHandle(lh);
|
||||
}
|
||||
measure.setFaultAfterProgBytes(0);
|
||||
const bool full = groupVisibleAfterRemount(measure, "by/big.bin", 2500);
|
||||
if (full) {
|
||||
LoLog check(measure);
|
||||
check.mount();
|
||||
std::vector<uint8_t> got;
|
||||
lologReadFile(check, "by/big.bin", got);
|
||||
TEST_ASSERT_EQUAL_UINT32(2500, got.size());
|
||||
} else {
|
||||
LoLog absent(measure);
|
||||
absent.mount();
|
||||
if (absent.exists("by/big.bin")) {
|
||||
std::vector<uint8_t> got;
|
||||
lologReadFile(absent, "by/big.bin", got);
|
||||
TEST_ASSERT_EQUAL_UINT32(0, got.size());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void test_torn_tail_then_append(void)
|
||||
{
|
||||
gDev.reset();
|
||||
LoLog log(gDev);
|
||||
log.format();
|
||||
TEST_ASSERT_TRUE(log.mkdir("torn"));
|
||||
TEST_ASSERT_TRUE(lologWriteFile(log, "torn/a.ls", (const uint8_t *)"AAAA", 4));
|
||||
|
||||
gDev.setFaultAfterProgBytes(12);
|
||||
LoLog::OpenHandle *h = log.open("torn/b.ls", true, true);
|
||||
if (h) {
|
||||
log.writeHandle(h, (const uint8_t *)"BBBBBBBB", 8);
|
||||
log.flushHandle(h);
|
||||
log.closeHandle(h);
|
||||
}
|
||||
gDev.setFaultAfterProgBytes(0);
|
||||
|
||||
{
|
||||
LoLog mid(gDev);
|
||||
TEST_ASSERT_TRUE(mid.mount());
|
||||
TEST_ASSERT_TRUE(mid.exists("torn/a.ls"));
|
||||
if (mid.exists("torn/b.ls")) {
|
||||
std::vector<uint8_t> shell;
|
||||
lologReadFile(mid, "torn/b.ls", shell);
|
||||
TEST_ASSERT_EQUAL_UINT32(0, shell.size());
|
||||
}
|
||||
}
|
||||
|
||||
LoLog fin(gDev);
|
||||
TEST_ASSERT_TRUE(fin.mount());
|
||||
TEST_ASSERT_TRUE(lologWriteFile(fin, "torn/c.ls", (const uint8_t *)"CCCC", 4));
|
||||
std::vector<uint8_t> finRead;
|
||||
TEST_ASSERT_TRUE(lologReadFile(fin, "torn/c.ls", finRead));
|
||||
TEST_ASSERT_EQUAL_UINT32(4, finRead.size());
|
||||
TEST_ASSERT_TRUE(fin.exists("torn/a.ls"));
|
||||
}
|
||||
|
||||
static void corruptSecondEntryCrc(LoLogRamDevice &dev)
|
||||
{
|
||||
for (uint32_t s = 0; s < dev.sectorCount(); s++) {
|
||||
uint32_t off = lolog::kHeaderSize;
|
||||
int ent = 0;
|
||||
while (off + 8 <= lolog::kSectorSize) {
|
||||
uint8_t hdr[8];
|
||||
if (!dev.read(s * lolog::kSectorSize + off, hdr, 8))
|
||||
break;
|
||||
uint16_t plen = (uint16_t)hdr[0] | ((uint16_t)hdr[1] << 8);
|
||||
if (plen == lolog::kEntryEnd)
|
||||
break;
|
||||
const uint32_t padded = (8u + plen + 3u) & ~3u;
|
||||
if (hdr[2] == lolog::kEntFile) {
|
||||
ent++;
|
||||
if (ent == 2 && plen > 0) {
|
||||
dev.poke(s * lolog::kSectorSize + off + 4 + plen, 0x00);
|
||||
return;
|
||||
}
|
||||
}
|
||||
off += padded;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void test_torn_entry_crc_poke(void)
|
||||
{
|
||||
gDev.reset();
|
||||
LoLog log(gDev);
|
||||
log.format();
|
||||
TEST_ASSERT_TRUE(log.mkdir("poke"));
|
||||
TEST_ASSERT_TRUE(lologWriteFile(log, "poke/ok.ls", (const uint8_t *)"good", 4));
|
||||
TEST_ASSERT_TRUE(lologWriteFile(log, "poke/next.ls", (const uint8_t *)"bad", 3));
|
||||
corruptSecondEntryCrc(gDev);
|
||||
|
||||
LoLog rem(gDev);
|
||||
TEST_ASSERT_TRUE(rem.mount());
|
||||
TEST_ASSERT_TRUE(rem.exists("poke/ok.ls"));
|
||||
TEST_ASSERT_FALSE(rem.exists("poke/next.ls"));
|
||||
TEST_ASSERT_TRUE(lologWriteFile(rem, "poke/after.ls", (const uint8_t *)"yes", 3));
|
||||
TEST_ASSERT_TRUE(rem.exists("poke/after.ls"));
|
||||
}
|
||||
|
||||
void test_fault_single_byte_discards_group(void)
|
||||
{
|
||||
TEST_ASSERT_TRUE(gLog.mkdir("g"));
|
||||
gDev.setFaultAfterProgBytes(1);
|
||||
LoLog::OpenHandle *h = gLog.open("g/a.ls", true, true);
|
||||
if (h) {
|
||||
gLog.writeHandle(h, (const uint8_t *)"x", 1);
|
||||
gLog.flushHandle(h);
|
||||
gLog.closeHandle(h);
|
||||
}
|
||||
gDev.setFaultAfterProgBytes(0);
|
||||
LoLog g2(gDev);
|
||||
TEST_ASSERT_TRUE(g2.mount());
|
||||
TEST_ASSERT_FALSE(g2.exists("g/a.ls"));
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
#include "lolog_test_helpers.hpp"
|
||||
#include <string>
|
||||
#include <unity.h>
|
||||
|
||||
extern LoLogRamDevice gDev;
|
||||
|
||||
extern LoLog gLog;
|
||||
|
||||
void test_mkdir_nested_and_overwrite(void)
|
||||
{
|
||||
TEST_ASSERT_TRUE(gLog.mkdir("a/b/c"));
|
||||
TEST_ASSERT_TRUE(gLog.isDirectory("a"));
|
||||
TEST_ASSERT_TRUE(gLog.isDirectory("a/b"));
|
||||
TEST_ASSERT_TRUE(gLog.isDirectory("a/b/c"));
|
||||
|
||||
const uint8_t v1[] = "one";
|
||||
const uint8_t v2[] = "two-longer";
|
||||
TEST_ASSERT_TRUE(lologWriteFile(gLog, "a/b/c/f.ls", v1, sizeof(v1)));
|
||||
TEST_ASSERT_TRUE(lologWriteFile(gLog, "a/b/c/f.ls", v2, sizeof(v2)));
|
||||
std::vector<uint8_t> got;
|
||||
TEST_ASSERT_TRUE(lologReadFile(gLog, "a/b/c/f.ls", got));
|
||||
TEST_ASSERT_EQUAL_UINT32(sizeof(v2), got.size());
|
||||
}
|
||||
|
||||
void test_rename_file(void)
|
||||
{
|
||||
TEST_ASSERT_TRUE(gLog.mkdir("rn"));
|
||||
TEST_ASSERT_TRUE(lologWriteFile(gLog, "rn/old.ls", (const uint8_t *)"data", 4));
|
||||
TEST_ASSERT_TRUE(gLog.rename("rn/old.ls", "rn/new.ls"));
|
||||
TEST_ASSERT_FALSE(gLog.exists("rn/old.ls"));
|
||||
TEST_ASSERT_TRUE(gLog.exists("rn/new.ls"));
|
||||
}
|
||||
|
||||
void test_rmdir_nonempty_fails_nonempty_ok_recursive(void)
|
||||
{
|
||||
TEST_ASSERT_TRUE(gLog.mkdir("rd"));
|
||||
TEST_ASSERT_TRUE(lologWriteFile(gLog, "rd/x.ls", (const uint8_t *)"1", 1));
|
||||
TEST_ASSERT_FALSE(gLog.rmdir("rd", false));
|
||||
TEST_ASSERT_TRUE(gLog.rmdir("rd", true));
|
||||
TEST_ASSERT_FALSE(gLog.exists("rd/x.ls"));
|
||||
}
|
||||
|
||||
void test_prefix_collision_names(void)
|
||||
{
|
||||
TEST_ASSERT_TRUE(gLog.mkdir("pfx"));
|
||||
const std::string p1 = "pfx/abcdefgh_11111111.ls";
|
||||
const std::string p2 = "pfx/abcdefgh_22222222.ls";
|
||||
TEST_ASSERT_TRUE(lologWriteFile(gLog, p1.c_str(), (const uint8_t *)"A", 1));
|
||||
TEST_ASSERT_TRUE(lologWriteFile(gLog, p2.c_str(), (const uint8_t *)"B", 1));
|
||||
std::vector<uint8_t> a, b;
|
||||
TEST_ASSERT_TRUE(lologReadFile(gLog, "pfx/abcdefgh_11111111.ls", a));
|
||||
TEST_ASSERT_TRUE(lologReadFile(gLog, "pfx/abcdefgh_22222222.ls", b));
|
||||
TEST_ASSERT_EQUAL_UINT8(0x41, a[0]);
|
||||
TEST_ASSERT_EQUAL_UINT8(0x42, b[0]);
|
||||
}
|
||||
|
||||
void test_large_file_inline_and_extent(void)
|
||||
{
|
||||
TEST_ASSERT_TRUE(gLog.mkdir("big"));
|
||||
std::vector<uint8_t> payload(2500);
|
||||
for (size_t i = 0; i < payload.size(); i++)
|
||||
payload[i] = (uint8_t)(i & 0xFF);
|
||||
TEST_ASSERT_TRUE(lologWriteFile(gLog, "big/upload.bin", payload.data(), payload.size()));
|
||||
|
||||
LoLog g2(gDev);
|
||||
TEST_ASSERT_TRUE(g2.mount());
|
||||
std::vector<uint8_t> got;
|
||||
TEST_ASSERT_TRUE(lologReadFile(g2, "big/upload.bin", got));
|
||||
TEST_ASSERT_EQUAL_UINT32(payload.size(), got.size());
|
||||
TEST_ASSERT_EQUAL_UINT8(0, got[0]);
|
||||
TEST_ASSERT_EQUAL_UINT8(0xC3, got[2500 - 1]);
|
||||
}
|
||||
|
||||
void test_large_file_write_at_path(void)
|
||||
{
|
||||
TEST_ASSERT_TRUE(gLog.mkdir("up"));
|
||||
LoLog::OpenHandle *h = gLog.open("up/chunk.bin", true, true);
|
||||
TEST_ASSERT_NOT_NULL(h);
|
||||
gLog.closeHandle(h);
|
||||
|
||||
uint8_t chunk[600];
|
||||
for (size_t i = 0; i < sizeof(chunk); i++)
|
||||
chunk[i] = (uint8_t)i;
|
||||
TEST_ASSERT_TRUE(gLog.writeAtPath("up/chunk.bin", 0, chunk, sizeof(chunk)));
|
||||
|
||||
std::vector<uint8_t> got;
|
||||
TEST_ASSERT_TRUE(lologReadFile(gLog, "up/chunk.bin", got));
|
||||
TEST_ASSERT_EQUAL_UINT32(sizeof(chunk), got.size());
|
||||
TEST_ASSERT_EQUAL_UINT8(0, got[0]);
|
||||
TEST_ASSERT_EQUAL_UINT8(255, got[255]);
|
||||
}
|
||||
|
||||
void test_stat_and_is_directory(void)
|
||||
{
|
||||
TEST_ASSERT_TRUE(gLog.mkdir("st"));
|
||||
TEST_ASSERT_TRUE(lologWriteFile(gLog, "st/f.ls", (const uint8_t *)"x", 1));
|
||||
uint32_t sz = 0;
|
||||
bool isDir = true;
|
||||
TEST_ASSERT_TRUE(gLog.statPath("st/f.ls", &sz, &isDir));
|
||||
TEST_ASSERT_FALSE(isDir);
|
||||
TEST_ASSERT_EQUAL_UINT32(1, sz);
|
||||
TEST_ASSERT_TRUE(gLog.statPath("st", &sz, &isDir));
|
||||
TEST_ASSERT_TRUE(isDir);
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
#include "lolog_test_helpers.hpp"
|
||||
#include "ref_fs.hpp"
|
||||
#include <unity.h>
|
||||
|
||||
extern LoLogRamDevice gDev;
|
||||
|
||||
static uint32_t lfsRand(uint32_t &state)
|
||||
{
|
||||
state = state * 1664525u + 1013904223u;
|
||||
return state;
|
||||
}
|
||||
|
||||
static void applyRefOp(RefFs &ref, LoLog &log, uint32_t op, uint32_t &rng)
|
||||
{
|
||||
char path[48];
|
||||
char path2[48];
|
||||
switch (op % 5) {
|
||||
case 0: {
|
||||
snprintf(path, sizeof(path), "fuzz/d%d", (int)(lfsRand(rng) % 20));
|
||||
if (log.mkdir(path))
|
||||
ref.mkdir(path);
|
||||
break;
|
||||
}
|
||||
case 1: {
|
||||
snprintf(path, sizeof(path), "fuzz/f%d.dat", (int)(lfsRand(rng) % 40));
|
||||
std::vector<uint8_t> data(8 + (lfsRand(rng) % 120));
|
||||
for (size_t i = 0; i < data.size(); i++)
|
||||
data[i] = (uint8_t)lfsRand(rng);
|
||||
if (lologWriteFile(log, path, data.data(), data.size()))
|
||||
ref.write(path, data);
|
||||
break;
|
||||
}
|
||||
case 2: {
|
||||
snprintf(path, sizeof(path), "fuzz/f%d.dat", (int)(lfsRand(rng) % 40));
|
||||
if (log.remove(path))
|
||||
ref.unlink(path);
|
||||
break;
|
||||
}
|
||||
case 3: {
|
||||
snprintf(path, sizeof(path), "fuzz/f%d.dat", (int)(lfsRand(rng) % 40));
|
||||
snprintf(path2, sizeof(path2), "fuzz/t%d.dat", (int)(lfsRand(rng) % 40));
|
||||
if (ref.existsFile(path) && !ref.existsFile(path2) && log.rename(path, path2))
|
||||
ref.rename(path, path2);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
log.maintain(5000);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void test_random_ops_with_crash_injection(void)
|
||||
{
|
||||
RefFs ref;
|
||||
uint32_t rng = 0xC0FFEE42u;
|
||||
|
||||
for (int round = 0; round < 30; round++) {
|
||||
gDev.reset();
|
||||
LoLog log(gDev);
|
||||
log.format();
|
||||
log.mkdir("fuzz");
|
||||
ref.clear();
|
||||
ref.mkdir("fuzz");
|
||||
|
||||
const int opsBeforeCrash = 3 + (int)(lfsRand(rng) % 12);
|
||||
for (int i = 0; i < opsBeforeCrash; i++)
|
||||
applyRefOp(ref, log, lfsRand(rng), rng);
|
||||
|
||||
gDev.setFaultOnProgCall(1 + (lfsRand(rng) % 8));
|
||||
applyRefOp(ref, log, lfsRand(rng), rng);
|
||||
gDev.setFaultOnProgCall(0);
|
||||
|
||||
LoLog rem(gDev);
|
||||
TEST_ASSERT_TRUE(rem.mount());
|
||||
|
||||
for (const auto &kv : ref.allFiles()) {
|
||||
if (!rem.exists(kv.first.c_str()))
|
||||
continue;
|
||||
std::vector<uint8_t> got;
|
||||
if (!lologReadFile(rem, kv.first.c_str(), got))
|
||||
continue;
|
||||
if (got.size() != kv.second.size())
|
||||
continue;
|
||||
if (!got.empty())
|
||||
TEST_ASSERT_EQUAL_UINT8_ARRAY(kv.second.data(), got.data(), got.size());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void test_directory_list_count(void)
|
||||
{
|
||||
gDev.reset();
|
||||
LoLog log(gDev);
|
||||
log.format();
|
||||
TEST_ASSERT_TRUE(log.mkdir("d"));
|
||||
TEST_ASSERT_TRUE(lologWriteFile(log, "d/one.ls", (const uint8_t *)"1", 1));
|
||||
TEST_ASSERT_TRUE(lologWriteFile(log, "d/two.ls", (const uint8_t *)"2", 1));
|
||||
TEST_ASSERT_EQUAL(2, lologCountDir(log, "d"));
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
#include "lolog_test_helpers.hpp"
|
||||
#include "lofs/lolog/LoLogConfig.h"
|
||||
#include <unity.h>
|
||||
|
||||
extern LoLogRamDevice gDev;
|
||||
extern LoLog gLog;
|
||||
|
||||
void test_index_flush_pure_reboot(void)
|
||||
{
|
||||
TEST_ASSERT_TRUE(gLog.mkdir("ix"));
|
||||
TEST_ASSERT_TRUE(lologWriteFile(gLog, "ix/a.ls", (const uint8_t *)"z", 1));
|
||||
gLog.maintain(60000);
|
||||
|
||||
LoLog g2(gDev);
|
||||
TEST_ASSERT_TRUE(g2.mount());
|
||||
TEST_ASSERT_TRUE(g2.exists("ix/a.ls"));
|
||||
}
|
||||
|
||||
void test_index_multiple_flushes_and_lookup(void)
|
||||
{
|
||||
for (int i = 0; i < 90; i++) {
|
||||
char dir[16];
|
||||
snprintf(dir, sizeof(dir), "bulk%d", i);
|
||||
TEST_ASSERT_TRUE(gLog.mkdir(dir));
|
||||
char path[32];
|
||||
snprintf(path, sizeof(path), "%s/r.ls", dir);
|
||||
TEST_ASSERT_TRUE(lologWriteFile(gLog, path, (const uint8_t *)"x", 1));
|
||||
gLog.maintain(60000);
|
||||
}
|
||||
LoLog g2(gDev);
|
||||
TEST_ASSERT_TRUE(g2.mount());
|
||||
TEST_ASSERT_TRUE(g2.exists("bulk42/r.ls"));
|
||||
TEST_ASSERT_TRUE(g2.exists("bulk0/r.ls"));
|
||||
}
|
||||
|
||||
void test_tombstone_shadow_after_remount(void)
|
||||
{
|
||||
TEST_ASSERT_TRUE(gLog.mkdir("ts"));
|
||||
TEST_ASSERT_TRUE(lologWriteFile(gLog, "ts/gone.ls", (const uint8_t *)"1", 1));
|
||||
TEST_ASSERT_TRUE(gLog.remove("ts/gone.ls"));
|
||||
gLog.maintain(60000);
|
||||
|
||||
LoLog g2(gDev);
|
||||
TEST_ASSERT_TRUE(g2.mount());
|
||||
TEST_ASSERT_FALSE(g2.exists("ts/gone.ls"));
|
||||
}
|
||||
|
||||
void test_delete_when_near_full(void)
|
||||
{
|
||||
LoLogRamDevice tiny(16);
|
||||
LoLog log(tiny);
|
||||
log.format();
|
||||
TEST_ASSERT_TRUE(lologWriteFile(log, "f0.ls", (const uint8_t *)"x", 1));
|
||||
int created = 1;
|
||||
for (int i = 1; i < 500; i++) {
|
||||
char path[24];
|
||||
snprintf(path, sizeof(path), "f%d.ls", i);
|
||||
if (!lologWriteFile(log, path, (const uint8_t *)"x", 1))
|
||||
break;
|
||||
created++;
|
||||
}
|
||||
TEST_ASSERT_GREATER_THAN(3, created);
|
||||
TEST_ASSERT_TRUE(log.remove("f0.ls"));
|
||||
char extra[24];
|
||||
snprintf(extra, sizeof(extra), "f%d.ls", created + 10);
|
||||
TEST_ASSERT_TRUE(lologWriteFile(log, extra, (const uint8_t *)"n", 1));
|
||||
}
|
||||
|
||||
void test_cleaner_recycles_sectors(void)
|
||||
{
|
||||
LoLogRamDevice dev(12);
|
||||
LoLog log(dev);
|
||||
log.format();
|
||||
for (int i = 0; i < 80; i++) {
|
||||
char path[20];
|
||||
snprintf(path, sizeof(path), "c%d.ls", i);
|
||||
if (!lologWriteFile(log, path, (const uint8_t *)"xy", 2))
|
||||
break;
|
||||
}
|
||||
for (int i = 0; i < 40; i++) {
|
||||
char path[20];
|
||||
snprintf(path, sizeof(path), "c%d.ls", i);
|
||||
log.remove(path);
|
||||
}
|
||||
for (int m = 0; m < 60; m++)
|
||||
log.maintain(60000);
|
||||
uint32_t usedAfter = (uint32_t)log.usedBytes();
|
||||
(void)usedAfter;
|
||||
TEST_ASSERT_TRUE(log.exists("c79.ls"));
|
||||
std::vector<uint8_t> tail;
|
||||
TEST_ASSERT_TRUE(lologReadFile(log, "c79.ls", tail));
|
||||
TEST_ASSERT_EQUAL_UINT8(0x78, tail[0]);
|
||||
}
|
||||
|
||||
void test_headroom_blocks_before_starve(void)
|
||||
{
|
||||
LoLogRamDevice tiny(6);
|
||||
LoLog log(tiny);
|
||||
log.format();
|
||||
for (int i = 0; i < 200; i++) {
|
||||
char path[32];
|
||||
snprintf(path, sizeof(path), "f%d.ls", i);
|
||||
LoLog::OpenHandle *h = log.open(path, true, true);
|
||||
if (!h)
|
||||
break;
|
||||
log.closeHandle(h);
|
||||
}
|
||||
LoLog::OpenHandle *last = log.open("overflow.ls", true, true);
|
||||
TEST_ASSERT_NULL(last);
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
#include "lofs/lolog/LoLogRamDevice.h"
|
||||
#include "lofs/lolog/LoLog.h"
|
||||
#include <unity.h>
|
||||
|
||||
LoLogRamDevice gDev(32);
|
||||
LoLog gLog(gDev);
|
||||
|
||||
void setUp(void)
|
||||
{
|
||||
gDev.reset();
|
||||
gLog.format();
|
||||
}
|
||||
|
||||
void tearDown(void) {}
|
||||
|
||||
#define DECL(name) void name(void)
|
||||
|
||||
DECL(test_mount_empty_after_format);
|
||||
DECL(test_mount_erased_chip_without_format);
|
||||
DECL(test_pure_replay_no_index_runs);
|
||||
DECL(test_mount_after_reboot_simulation);
|
||||
|
||||
DECL(test_mkdir_nested_and_overwrite);
|
||||
DECL(test_rename_file);
|
||||
DECL(test_rmdir_nonempty_fails_nonempty_ok_recursive);
|
||||
DECL(test_prefix_collision_names);
|
||||
DECL(test_large_file_inline_and_extent);
|
||||
DECL(test_large_file_write_at_path);
|
||||
DECL(test_stat_and_is_directory);
|
||||
|
||||
DECL(test_commit_group_atomic_each_prog_call);
|
||||
DECL(test_commit_group_each_prog_byte);
|
||||
DECL(test_torn_tail_then_append);
|
||||
DECL(test_torn_entry_crc_poke);
|
||||
DECL(test_fault_single_byte_discards_group);
|
||||
DECL(test_commit_group_partial_never_applied);
|
||||
|
||||
DECL(test_index_flush_pure_reboot);
|
||||
DECL(test_index_multiple_flushes_and_lookup);
|
||||
DECL(test_tombstone_shadow_after_remount);
|
||||
DECL(test_delete_when_near_full);
|
||||
DECL(test_cleaner_recycles_sectors);
|
||||
DECL(test_headroom_blocks_before_starve);
|
||||
|
||||
DECL(test_random_ops_with_crash_injection);
|
||||
DECL(test_directory_list_count);
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
(void)argc;
|
||||
(void)argv;
|
||||
UNITY_BEGIN();
|
||||
|
||||
RUN_TEST(test_mount_empty_after_format);
|
||||
RUN_TEST(test_mount_erased_chip_without_format);
|
||||
RUN_TEST(test_pure_replay_no_index_runs);
|
||||
RUN_TEST(test_mount_after_reboot_simulation);
|
||||
|
||||
RUN_TEST(test_mkdir_nested_and_overwrite);
|
||||
RUN_TEST(test_rename_file);
|
||||
RUN_TEST(test_rmdir_nonempty_fails_nonempty_ok_recursive);
|
||||
RUN_TEST(test_prefix_collision_names);
|
||||
RUN_TEST(test_large_file_inline_and_extent);
|
||||
RUN_TEST(test_large_file_write_at_path);
|
||||
RUN_TEST(test_stat_and_is_directory);
|
||||
|
||||
RUN_TEST(test_commit_group_atomic_each_prog_call);
|
||||
RUN_TEST(test_commit_group_each_prog_byte);
|
||||
RUN_TEST(test_torn_tail_then_append);
|
||||
RUN_TEST(test_torn_entry_crc_poke);
|
||||
RUN_TEST(test_fault_single_byte_discards_group);
|
||||
RUN_TEST(test_commit_group_partial_never_applied);
|
||||
|
||||
RUN_TEST(test_index_flush_pure_reboot);
|
||||
RUN_TEST(test_index_multiple_flushes_and_lookup);
|
||||
RUN_TEST(test_tombstone_shadow_after_remount);
|
||||
RUN_TEST(test_delete_when_near_full);
|
||||
RUN_TEST(test_cleaner_recycles_sectors);
|
||||
RUN_TEST(test_headroom_blocks_before_starve);
|
||||
|
||||
RUN_TEST(test_random_ops_with_crash_injection);
|
||||
RUN_TEST(test_directory_list_count);
|
||||
|
||||
return UNITY_END();
|
||||
}
|
||||
Reference in new issue
Block a user