test: kernel host tests -- the harness, and gpt.c tested on the host

Captain Bob, 2026-11-12: "Build an appropriate test harness! Love the idea."
The kernel had no tests below the boots, so defects QEMU cannot provoke could
not be tested first. Plan: docs/v4.0.0/plans/2026-11-12-kernel-host-tests.md.

kernel/tests/: one host program per kernel source, built with that source as
it is, the kernel's flags (-std=c99 -Wall -Wextra -Werror), and fakes --
the console captured line by line, a deterministic RNG that can fail, and a RAM
disk whose reads, writes or flushes fail at chosen 1 KiB blocks.
make -C kernel/tests test runs them; SRC_<test>= runs a test against other
code, which is how a test is seen to fail against the code before a fix.

test_gpt.c, 12 checks: a table written and read back; 16, 17 and 18 used
entries against max 16 and an entry out of range after the 16th (#180); the
RNG, the first write, a later write and a flush failing (#168). Against gpt.c
before #168 and #180: 4 failures, each defect caught. Now: 0. Both runs in
logs/.../kernel-tests/. Not yet a protocol stage: to rule (the plan, step 5).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017RzZgrf7V7Zk2iczBXDRbm
This commit is contained in:
Claude
2026-11-12 17:57:04 +00:00
parent 8a0dab7f48
commit 37b1527279
7 changed files with 368 additions and 0 deletions
+104
View File
@@ -0,0 +1,104 @@
/* SPDX-License-Identifier: LicenseRef-Starship-1.0
* Copyright (c) 2023-2026 Robert A. James
*/
/* fakes.c -- see fakes.h. */
#include "fakes.h"
#include <stdlib.h>
#include <string.h>
/* ---- the console ---- */
#define FAKE_LINES 256
#define FAKE_LINE 256
static char lines[FAKE_LINES][FAKE_LINE];
static unsigned n_lines;
void fake_console_reset(void) { n_lines = 0; }
unsigned fake_console_lines(void) { return n_lines; }
const char *fake_console_line(unsigned i) { return i < n_lines ? lines[i] : ""; }
int fake_console_said(const char *text)
{
unsigned i;
for (i = 0; i < n_lines; i++) if (strstr(lines[i], text)) return 1;
return 0;
}
static void take(const char *s)
{
if (n_lines < FAKE_LINES) {
strncpy(lines[n_lines], s, FAKE_LINE - 1);
lines[n_lines][FAKE_LINE - 1] = '\0';
n_lines++;
}
}
void console_println(const char *s) { take(s); }
void console_puts(const char *s) { take(s); }
void console_putc(char c) { (void)c; }
/* ---- the RNG ---- */
static uint32_t rng_state = 1u;
static int rng_fails;
void fake_rng_reset(void) { rng_state = 1u; rng_fails = 0; }
void fake_rng_fail(int fail) { rng_fails = fail; }
int rng_get_bytes(uint8_t *buf, size_t n)
{
size_t i;
if (rng_fails) return -1;
for (i = 0; i < n; i++) { rng_state = rng_state * 1103515245u + 12345u; buf[i] = (uint8_t)(rng_state >> 16); }
return 0;
}
/* ---- the disk ---- */
static int listed(const uint32_t *list, uint32_t n, uint32_t block)
{
uint32_t i;
for (i = 0; i < n; i++) if (list[i] == block) return 1;
return 0;
}
static int d_open(blkio_dev_t *dev, const blkio_params_t *p) { dev->state = p->opaque; return BLKIO_OK; }
static int d_close(blkio_dev_t *dev) { (void)dev; return BLKIO_OK; }
static int d_read(blkio_dev_t *dev, uint32_t fblock, void *dst)
{
fake_disk *d = (fake_disk *)dev->state;
if (fblock >= d->blocks) return BLKIO_EINVAL;
if (listed(d->fail_read, d->n_fail_read, fblock)) return BLKIO_EIO;
memcpy(dst, d->bytes + (size_t)fblock * BLKIO_FORTH_BLOCK_SIZE, BLKIO_FORTH_BLOCK_SIZE);
return BLKIO_OK;
}
static int d_write(blkio_dev_t *dev, uint32_t fblock, const void *src)
{
fake_disk *d = (fake_disk *)dev->state;
if (fblock >= d->blocks) return BLKIO_EINVAL;
if (listed(d->fail_write, d->n_fail_write, fblock)) return BLKIO_EIO;
memcpy(d->bytes + (size_t)fblock * BLKIO_FORTH_BLOCK_SIZE, src, BLKIO_FORTH_BLOCK_SIZE);
d->writes++;
return BLKIO_OK;
}
static int d_flush(blkio_dev_t *dev) { fake_disk *d = (fake_disk *)dev->state; d->flushes++; return d->fail_flush ? BLKIO_EIO : BLKIO_OK; }
static int d_info(blkio_dev_t *dev, blkio_info_t *out)
{
fake_disk *d = (fake_disk *)dev->state;
memset(out, 0, sizeof *out);
out->forth_block_size = BLKIO_FORTH_BLOCK_SIZE;
out->total_blocks = d->blocks;
out->phys_sector_size = 512u;
out->phys_size_bytes = (uint64_t)d->blocks * BLKIO_FORTH_BLOCK_SIZE;
return BLKIO_OK;
}
static const blkio_vtable_t fake_vt = { d_open, d_close, d_read, d_write, d_flush, d_info };
int fake_disk_open(blkio_dev_t *dev, fake_disk *d, uint32_t blocks)
{
blkio_params_t p;
memset(d, 0, sizeof *d);
d->bytes = (uint8_t *)calloc(blocks, BLKIO_FORTH_BLOCK_SIZE);
d->blocks = blocks;
if (!d->bytes) return BLKIO_EIO;
memset(&p, 0, sizeof p);
p.forth_block_size = BLKIO_FORTH_BLOCK_SIZE;
p.total_blocks = blocks;
p.opaque = d;
return blkio_open(dev, &fake_vt, &p);
}
void fake_disk_free(fake_disk *d) { free(d->bytes); d->bytes = 0; }
void fake_disk_fail_read(fake_disk *d, uint32_t block) { if (d->n_fail_read < FAKE_DISK_MAX_FAULTS) d->fail_read[d->n_fail_read++] = block; }
void fake_disk_fail_write(fake_disk *d, uint32_t block) { if (d->n_fail_write < FAKE_DISK_MAX_FAULTS) d->fail_write[d->n_fail_write++] = block; }
uint8_t *fake_disk_sector(fake_disk *d, uint64_t lba) { return d->bytes + (size_t)lba * 512u; }