137 lines
4.2 KiB
C
137 lines
4.2 KiB
C
// saveHost.c - Host-cc harness for the save-file core (src/core/save.c).
|
|
//
|
|
// Built by scripts/check-save.sh on the serialHost.c pattern: compiled with
|
|
// -DJOEYLIB_PLATFORM_BLANK plus the JL_HAS_SAVE_* flags so port.h routes
|
|
// jlpSave*/jlpDiskFree to the POSIX HAL below. Unlike the serial mock, this
|
|
// exercises the REAL core end to end -- jlSaveWrite/Read/Open/Exists/Delete do
|
|
// actual stdio into a SAVES/ directory in the current working directory, which
|
|
// check-save.sh points at a throwaway temp dir. Prints one line per check and
|
|
// a final tally; exits nonzero on any failure, so it doubles as a CI gate.
|
|
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/statvfs.h>
|
|
|
|
#include "joey/file.h"
|
|
#include "port.h"
|
|
|
|
|
|
// ----- POSIX save HAL (satisfies the jlpSave* dispatch under -DJL_HAS_SAVE_*) -----
|
|
|
|
uint32_t jlpDiskFree(void) {
|
|
struct statvfs vfs;
|
|
unsigned long long bytes;
|
|
|
|
if (statvfs(".", &vfs) != 0) {
|
|
return 0u;
|
|
}
|
|
bytes = (unsigned long long)vfs.f_bavail * (unsigned long long)vfs.f_frsize;
|
|
if (bytes > 0xFFFFFFFFull) {
|
|
return 0xFFFFFFFFu;
|
|
}
|
|
return (uint32_t)bytes;
|
|
}
|
|
|
|
|
|
bool jlpSaveDelete(const char *path) {
|
|
return remove(path) == 0;
|
|
}
|
|
|
|
|
|
bool jlpSaveDirEnsure(const char *dir) {
|
|
struct stat st;
|
|
|
|
if (mkdir(dir, 0777) == 0) {
|
|
return true;
|
|
}
|
|
return stat(dir, &st) == 0 && S_ISDIR(st.st_mode);
|
|
}
|
|
|
|
|
|
// ----- Test harness -----
|
|
|
|
static int gPass = 0;
|
|
static int gFail = 0;
|
|
|
|
|
|
static void check(const char *name, bool ok) {
|
|
if (ok) {
|
|
gPass++;
|
|
printf(" ok %s\n", name);
|
|
} else {
|
|
gFail++;
|
|
printf(" FAIL %s\n", name);
|
|
}
|
|
}
|
|
|
|
|
|
int main(void) {
|
|
uint8_t out[64];
|
|
uint8_t in[64];
|
|
uint32_t n;
|
|
FILE *fp;
|
|
int i;
|
|
|
|
printf("save-host: core save/read/exists/delete/diskfree (real files)\n");
|
|
|
|
for (i = 0; i < 64; i++) {
|
|
out[i] = (uint8_t)(i * 7 + 1);
|
|
}
|
|
|
|
// 1. Clean slate + a plausible free-space number.
|
|
check("fresh: hero.sav absent", jlSaveExists("hero.sav") == false);
|
|
check("diskFree > 0", jlDiskFree() > 0u);
|
|
|
|
// 2. Blob write creates SAVES/ on demand and round-trips.
|
|
check("write blob (64)", jlSaveWrite("hero.sav", out, 64u) == true);
|
|
check("exists after write", jlSaveExists("hero.sav") == true);
|
|
memset(in, 0, sizeof(in));
|
|
n = jlSaveRead("hero.sav", in, sizeof(in));
|
|
check("read full length", n == 64u);
|
|
check("payload round-trips", memcmp(in, out, 64) == 0);
|
|
|
|
// 3. Read honors the caller's max.
|
|
memset(in, 0, sizeof(in));
|
|
n = jlSaveRead("hero.sav", in, 16u);
|
|
check("read capped at max", n == 16u);
|
|
check("capped payload matches", memcmp(in, out, 16) == 0);
|
|
|
|
// 4. Streaming open for incremental writes.
|
|
fp = jlSaveOpen("stream.sav", "wb");
|
|
check("stream open (wb)", fp != NULL);
|
|
if (fp != NULL) {
|
|
fputc('J', fp);
|
|
fputc('L', fp);
|
|
fclose(fp);
|
|
}
|
|
memset(in, 0, sizeof(in));
|
|
n = jlSaveRead("stream.sav", in, sizeof(in));
|
|
check("stream read length 2", n == 2u);
|
|
check("stream payload 'JL'", in[0] == (uint8_t)'J' && in[1] == (uint8_t)'L');
|
|
|
|
// 5. Zero-length write makes an empty file.
|
|
check("write empty", jlSaveWrite("empty.sav", NULL, 0u) == true);
|
|
check("empty exists", jlSaveExists("empty.sav") == true);
|
|
check("empty reads 0", jlSaveRead("empty.sav", in, sizeof(in)) == 0u);
|
|
|
|
// 6. Delete removes it; deleting a missing file reports false.
|
|
check("delete hero.sav", jlSaveDelete("hero.sav") == true);
|
|
check("hero.sav gone", jlSaveExists("hero.sav") == false);
|
|
check("delete missing -> false", jlSaveDelete("nope.sav") == false);
|
|
|
|
// 7. NULL / bad-argument guards.
|
|
check("write NULL name", jlSaveWrite(NULL, out, 4u) == false);
|
|
check("read NULL name", jlSaveRead(NULL, in, 4u) == 0u);
|
|
check("read NULL buf", jlSaveRead("empty.sav", NULL, 4u) == 0u);
|
|
check("open NULL name", jlSaveOpen(NULL, "rb") == NULL);
|
|
check("exists NULL name", jlSaveExists(NULL) == false);
|
|
check("delete NULL name", jlSaveDelete(NULL) == false);
|
|
|
|
// Best-effort cleanup of the files this run created.
|
|
jlSaveDelete("stream.sav");
|
|
jlSaveDelete("empty.sav");
|
|
|
|
printf("save-host: %d passed, %d failed\n", gPass, gFail);
|
|
return (gFail == 0) ? 0 : 1;
|
|
}
|