mirror of
https://github.com/ALLFATHER-BV/wadamesh.git
synced 2026-11-07 14:17:18 +00:00
The bin was written to the card chunk by chunk as it arrived, on the core-0 tile worker next to the Wi-Fi driver. A FAT write that fails there leaves the file failed for good (FatFs keeps the error on the file object), so the per-chunk retry could never recover and the stream could not be replayed. Map tiles never hit this: they are downloaded first and written after. When the image fits in PSRAM (with 512 KB to spare) it is now downloaded first, then written through an internal DMA bounce buffer, up to three whole- file attempts (helpers/BufferedFileCommit.h, host test included). Boards without the room stream as before. The SD health tick also guarded the download with s_sdfw_request, which the worker clears the moment it starts, so a probe or remount could SD.end() under the open file. The guard and sdRuntimeLifecycleBusy() now use s_sdfw_state == 1 as well. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
119 lines
4.8 KiB
C++
119 lines
4.8 KiB
C++
// Host test for helpers/BufferedFileCommit.h (#734: "Save update bin to SD").
|
|
// Build: g++ -std=c++17 -I src -o /tmp/t test/test_buffered_file_commit.cpp && /tmp/t
|
|
//
|
|
// The fake file behaves like a FAT file on a flaky card: a write can be short, and
|
|
// once a write fails the file stays failed (FatFs keeps the error on the file object)
|
|
// until it is closed and opened again.
|
|
#include <assert.h>
|
|
#include <stdio.h>
|
|
#include <vector>
|
|
|
|
#include "helpers/BufferedFileCommit.h"
|
|
|
|
struct FakeCard {
|
|
std::vector<uint8_t> file; // what is on the card
|
|
bool exists = false;
|
|
bool is_open = false;
|
|
bool sticky_error = false;
|
|
int opens = 0, discards = 0, pauses = 0;
|
|
size_t max_take = 0; // >0: each write takes at most this many bytes
|
|
std::vector<long> fail_at; // per open: byte offset whose write fails (-1 = never)
|
|
std::vector<int> open_fails; // per open: 1 = open() fails
|
|
size_t last_progress = 0;
|
|
bool progress_monotonic = true;
|
|
|
|
bool open() {
|
|
const int k = opens++;
|
|
if (k < (int)open_fails.size() && open_fails[k]) return false;
|
|
file.clear(); exists = true; is_open = true; sticky_error = false;
|
|
return true;
|
|
}
|
|
size_t write(const uint8_t* p, size_t n) {
|
|
assert(is_open);
|
|
if (sticky_error) return 0;
|
|
const int k = opens - 1;
|
|
const long fa = k < (int)fail_at.size() ? fail_at[k] : -1;
|
|
size_t take = n;
|
|
if (max_take && take > max_take) take = max_take;
|
|
if (fa >= 0 && (long)file.size() <= fa && (long)(file.size() + take) > fa) {
|
|
sticky_error = true;
|
|
return 0;
|
|
}
|
|
file.insert(file.end(), p, p + take);
|
|
return take;
|
|
}
|
|
bool close() { is_open = false; return !sticky_error; }
|
|
void discard() { assert(!is_open); file.clear(); exists = false; ++discards; }
|
|
void pause(int) { ++pauses; }
|
|
void progress(size_t done, size_t total) {
|
|
assert(total > 0 && done <= total);
|
|
if (done < last_progress) progress_monotonic = false;
|
|
last_progress = done;
|
|
}
|
|
};
|
|
|
|
static std::vector<uint8_t> payload(size_t n) {
|
|
std::vector<uint8_t> v(n);
|
|
for (size_t i = 0; i < n; ++i) v[i] = (uint8_t)(i * 31u + 7u);
|
|
return v;
|
|
}
|
|
|
|
int main() {
|
|
const std::vector<uint8_t> img = payload(10000);
|
|
uint8_t bounce[1024];
|
|
|
|
{ // clean card: one open, exact bytes, no retries
|
|
FakeCard c;
|
|
assert(BufferedFileCommit::commit(c, img.data(), img.size(), bounce, sizeof bounce, 3));
|
|
assert(c.opens == 1 && c.discards == 0 && c.pauses == 0);
|
|
assert(c.file == img);
|
|
assert(c.progress_monotonic && c.last_progress == img.size());
|
|
}
|
|
{ // short writes are pushed through, not treated as failure
|
|
FakeCard c; c.max_take = 100;
|
|
assert(BufferedFileCommit::commit(c, img.data(), img.size(), bounce, sizeof bounce, 3));
|
|
assert(c.opens == 1 && c.file == img);
|
|
}
|
|
{ // the #734 case: a write fails mid-file and the file stays failed; the retry
|
|
// starts over from byte 0 and the card ends up with the whole image
|
|
FakeCard c; c.fail_at = {4500, -1};
|
|
assert(BufferedFileCommit::commit(c, img.data(), img.size(), bounce, sizeof bounce, 3));
|
|
assert(c.opens == 2 && c.discards == 1 && c.pauses == 1);
|
|
assert(c.exists && c.file == img);
|
|
}
|
|
{ // fails twice, third attempt succeeds
|
|
FakeCard c; c.fail_at = {0, 9999, -1};
|
|
assert(BufferedFileCommit::commit(c, img.data(), img.size(), bounce, sizeof bounce, 3));
|
|
assert(c.opens == 3 && c.discards == 2 && c.file == img);
|
|
}
|
|
{ // fails every time: false, and no partial file is left for the Launcher
|
|
FakeCard c; c.fail_at = {100, 200, 300};
|
|
assert(!BufferedFileCommit::commit(c, img.data(), img.size(), bounce, sizeof bounce, 3));
|
|
assert(c.opens == 3 && c.discards == 3 && !c.exists && c.file.empty());
|
|
}
|
|
{ // open fails once, then works
|
|
FakeCard c; c.open_fails = {1, 0};
|
|
assert(BufferedFileCommit::commit(c, img.data(), img.size(), bounce, sizeof bounce, 3));
|
|
assert(c.opens == 2 && c.discards == 0 && c.file == img);
|
|
}
|
|
{ // a payload that is an exact multiple of the bounce size, and an empty one
|
|
const std::vector<uint8_t> even = payload(4096);
|
|
FakeCard c;
|
|
assert(BufferedFileCommit::commit(c, even.data(), even.size(), bounce, sizeof bounce, 1));
|
|
assert(c.file == even);
|
|
FakeCard e;
|
|
assert(BufferedFileCommit::commit(e, even.data(), 0, bounce, sizeof bounce, 1));
|
|
assert(e.exists && e.file.empty());
|
|
}
|
|
{ // bad arguments never touch the card
|
|
FakeCard c;
|
|
assert(!BufferedFileCommit::commit(c, nullptr, 10, bounce, sizeof bounce, 3));
|
|
assert(!BufferedFileCommit::commit(c, img.data(), img.size(), nullptr, 16, 3));
|
|
assert(!BufferedFileCommit::commit(c, img.data(), img.size(), bounce, 0, 3));
|
|
assert(!BufferedFileCommit::commit(c, img.data(), img.size(), bounce, sizeof bounce, 0));
|
|
assert(c.opens == 0);
|
|
}
|
|
printf("buffered_file_commit: all passed\n");
|
|
return 0;
|
|
}
|