Files
LithosAnanke/kernel/tests/test_gpt.c
T
Claude 37b1527279 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
2026-11-12 17:57:04 +00:00

121 lines
5.9 KiB
C

/* SPDX-License-Identifier: LicenseRef-Starship-1.0
* Copyright (c) 2023-2026 Robert A. James
*/
/* test_gpt.c -- kernel/src/gpt.c on the host: gpt_read and gpt_write_one against a RAM disk whose
* writes can fail at chosen blocks (docs/v4.0.0/plans/2026-11-12-kernel-host-tests.md, increment 1).
* #168: gpt_write_one tells "nothing written" (GPT_EIO) from "the disk is changed" (GPT_PART).
* #180: gpt_read checks every entry and says when there are more than max (GPT_MANY).
*/
#include "harness.h"
#include "fakes.h"
#include "starkernel/gpt.h"
#include <string.h>
#define DISK_BLOCKS 8192u /* 8 MiB: 16384 sectors */
#define SECTORS (DISK_BLOCKS * 2u)
static void put_le32(uint8_t *p, uint32_t v) { p[0] = (uint8_t)v; p[1] = (uint8_t)(v >> 8); p[2] = (uint8_t)(v >> 16); p[3] = (uint8_t)(v >> 24); }
static void put_le64(uint8_t *p, uint64_t v) { put_le32(p, (uint32_t)v); put_le32(p + 4, (uint32_t)(v >> 32)); }
/* A GPT written by hand: `used` entries of 64 sectors each from LBA 2048, the first two of them
* the StarshipOS identity and Artemis types when `ours_at` is 0, or at entries ours_at and
* ours_at+1; entry `bad` (if not -1) given a range past the last usable LBA. Both CRCs good. */
static void make_table(fake_disk *d, unsigned used, unsigned ours_at, int bad)
{
static const uint8_t linux_t[16] = { 0xAF, 0x3D, 0xC6, 0x0F, 0x83, 0x84, 0x72, 0x47, 0x8E, 0x79, 0x3D, 0x69, 0xD8, 0x47, 0x7D, 0xE4 };
uint8_t *mbr = fake_disk_sector(d, 0), *h = fake_disk_sector(d, 1), *e = fake_disk_sector(d, 2);
uint64_t last_usable = SECTORS - 34u;
unsigned i;
memset(d->bytes, 0, (size_t)34 * 512u);
mbr[446 + 4] = 0xEE; put_le32(mbr + 446 + 8, 1u); put_le32(mbr + 446 + 12, SECTORS - 1u);
mbr[510] = 0x55; mbr[511] = 0xAA;
for (i = 0; i < used; i++) {
uint8_t *x = e + (size_t)i * 128u;
const uint8_t *t = (i == ours_at) ? gpt_type_starship_identity : (i == ours_at + 1u) ? gpt_type_starship_artemis : linux_t;
memcpy(x, t, 16);
x[16] = (uint8_t)(i + 1); /* a partition GUID */
put_le64(x + 32, 2048u + (uint64_t)i * 64u);
put_le64(x + 40, 2048u + (uint64_t)i * 64u + 63u);
if ((int)i == bad) { put_le64(x + 32, last_usable - 8u); put_le64(x + 40, last_usable + 64u); }
}
memcpy(h, "EFI PART", 8);
put_le32(h + 8, 0x00010000u); put_le32(h + 12, 92u);
put_le64(h + 24, 1u); put_le64(h + 32, SECTORS - 1u);
put_le64(h + 40, 34u); put_le64(h + 48, last_usable);
h[56] = 0x5A; /* a disk GUID */
put_le64(h + 72, 2u); put_le32(h + 80, 128u); put_le32(h + 84, 128u);
put_le32(h + 88, gpt_crc32(e, 128u * 128u));
put_le32(h + 16, 0u); put_le32(h + 16, gpt_crc32(h, 92u));
}
int main(void)
{
blkio_dev_t dev;
fake_disk d;
gpt_part_t parts[16];
unsigned n = 0;
uint8_t guid[16];
int rc;
fake_rng_reset();
fake_console_reset();
/* gpt_write_one, then gpt_read: the table it writes reads back */
CHECK(fake_disk_open(&dev, &d, DISK_BLOCKS) == BLKIO_OK, "a RAM disk");
rc = gpt_write_one(&dev, gpt_type_starship_identity, 4096u, guid);
CHECK(rc == GPT_OK, "gpt_write_one on a blank disk: GPT_OK (%d)", rc);
rc = gpt_read(&dev, parts, 16u, &n);
CHECK(rc == GPT_OK && n == 1u && memcmp(parts[0].type, gpt_type_starship_identity, 16) == 0
&& parts[0].first_lba == 2048u, "and reads back: one identity partition at 2048 (rc %d, n %u)", rc, n);
fake_disk_free(&d);
/* #180: every entry checked, more than max said */
fake_disk_open(&dev, &d, DISK_BLOCKS);
make_table(&d, 16u, 0u, -1);
rc = gpt_read(&dev, parts, 16u, &n);
CHECK(rc == GPT_OK && n == 16u, "16 used entries, max 16: GPT_OK, 16 (rc %d, n %u)", rc, n);
make_table(&d, 18u, 16u, -1);
rc = gpt_read(&dev, parts, 16u, &n);
CHECK(rc == GPT_MANY && n == 16u, "#180: 18 used entries, ours the 17th and 18th, max 16: GPT_MANY, not cut to 16 as if they were all (rc %d, n %u)", rc, n);
make_table(&d, 17u, 0u, 16);
rc = gpt_read(&dev, parts, 16u, &n);
CHECK(rc == GPT_BAD, "#180: the 17th entry out of range, max 16: GPT_BAD -- it is checked (rc %d)", rc);
make_table(&d, 4u, 0u, 2);
rc = gpt_read(&dev, parts, 16u, &n);
CHECK(rc == GPT_BAD, "an entry out of range within max: GPT_BAD (rc %d)", rc);
fake_disk_free(&d);
/* #168: gpt_write_one says whether it changed the disk */
fake_disk_open(&dev, &d, DISK_BLOCKS);
fake_rng_fail(1);
rc = gpt_write_one(&dev, gpt_type_starship_identity, 4096u, guid);
CHECK(rc == GPT_EIO && d.writes == 0u, "no RNG: GPT_EIO before any write (rc %d, %u writes)", rc, d.writes);
fake_rng_fail(0);
fake_disk_free(&d);
fake_disk_open(&dev, &d, DISK_BLOCKS);
make_table(&d, 2u, 0u, -1);
fake_disk_fail_write(&d, 0u); /* sector 0: its first write */
rc = gpt_write_one(&dev, gpt_type_starship_identity, 4096u, guid);
CHECK(rc == GPT_EIO && d.writes == 0u, "#168: its first write fails: GPT_EIO, nothing written (rc %d, %u writes)", rc, d.writes);
rc = gpt_read(&dev, parts, 16u, &n);
CHECK(rc == GPT_OK && n == 2u, "and the old table is still there (rc %d, n %u)", rc, n);
fake_disk_free(&d);
fake_disk_open(&dev, &d, DISK_BLOCKS);
make_table(&d, 2u, 0u, -1);
fake_disk_fail_write(&d, 1u); /* the primary entries, after the MBR is zeroed */
rc = gpt_write_one(&dev, gpt_type_starship_identity, 4096u, guid);
CHECK(rc == GPT_PART && d.writes > 0u, "#168: a later write fails: GPT_PART, the disk changed (rc %d, %u writes)", rc, d.writes);
fake_disk_free(&d);
fake_disk_open(&dev, &d, DISK_BLOCKS);
d.fail_flush = 1;
rc = gpt_write_one(&dev, gpt_type_starship_identity, 4096u, guid);
CHECK(rc == GPT_PART && d.writes == 1u, "#168: the first write made, its flush failing: GPT_PART (rc %d, %u writes)", rc, d.writes);
fake_disk_free(&d);
return DONE();
}