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:
@@ -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; }
|
||||
Reference in New Issue
Block a user