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
121 lines
5.9 KiB
C
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();
|
|
}
|