feat(xce-harness-component): relocate xce_test into standalone harness component
Move xce_test's 15 production sources + headers from src/controller/testsuite/ into a new src/harness/ component with its own meson.build, and strip every now-dangling controller-side reference (11 controller_unit_test_src entries, 4 standalone test-binary blocks, 11 controller_test.c extern/RUN_TEST lines) so the controller build is never left broken. Fixes a broken relative-symlink bridge (xce_test_controller_link) added to keep two controller-embedded tests (test_test_cli.c, test_xcetest_process_entry.c) finding the relocated xce_test binary until task 2.1 moves them too. Discovered and documented in design.md: four controller-domain regression tests call functions directly from four of xce_test's own files, requiring controller_unit_test to permanently recompile them from src/harness/ — a real dependency neither the original discovery nor design audit caught. controller_unit_test: 1228 Tests 0 Failures 0 Ignored (verified via the Unity binary's own printed summary line, not just meson's aggregate count). Task 1.1 of xce-harness-component. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RLSNJhNoULvVcUhXEFafQC
This commit is contained in:
parent
b8ef82c1b2
commit
cf55d691de
36 changed files with 550 additions and 180 deletions
|
|
@ -1,417 +0,0 @@
|
|||
#define _GNU_SOURCE
|
||||
#include "snapverify.h"
|
||||
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <inttypes.h>
|
||||
#include <string.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
|
||||
/* Mirrors persist.c's SNAPSHOT_FORMAT_VERSION / header layout exactly — see
|
||||
* persist.c write_snapshot() (~line 1758) and load_snapshot() (~line 2325).
|
||||
* config-upgrade-snapshot task 1 bumped persist.c's SNAPSHOT_FORMAT_VERSION
|
||||
* 1->2 (host/queue admin-state frames migrated to capnp, Req 1.9/5.7) --
|
||||
* this MUST track that bump, or this tool hard-rejects every real
|
||||
* checkpoint the current codebase writes (task 8.4 finding). */
|
||||
#define SNAP_FORMAT_VERSION 2u
|
||||
#define SNAP_HEADER_MAX (16 + 1 + 8 + 32) /* magic+lsn + optional cfgref tail */
|
||||
|
||||
static void fail(snapverify_report_t* rep, int rc, off_t off, const char* why)
|
||||
{
|
||||
rep->ok = 0;
|
||||
rep->fail_offset = off;
|
||||
snprintf(rep->fail_reason, sizeof(rep->fail_reason), "%s", why);
|
||||
(void)rc;
|
||||
}
|
||||
|
||||
int snapverify_path(const char* path, FILE* out, snapverify_report_t* rep)
|
||||
{
|
||||
if (!path)
|
||||
return -EINVAL;
|
||||
|
||||
snapverify_report_t local;
|
||||
memset(&local, 0, sizeof(local));
|
||||
local.ok = 1;
|
||||
local.fail_offset = -1;
|
||||
|
||||
int fd = open(path, O_RDONLY);
|
||||
if (fd < 0) {
|
||||
int saved = errno;
|
||||
fail(&local, -saved, -1, strerror(saved));
|
||||
if (rep)
|
||||
*rep = local;
|
||||
if (out)
|
||||
fprintf(out, "snapverify: %s: %s\n", path, strerror(saved));
|
||||
return -saved;
|
||||
}
|
||||
|
||||
struct stat st;
|
||||
if (fstat(fd, &st) != 0) {
|
||||
int saved = errno;
|
||||
close(fd);
|
||||
fail(&local, -saved, -1, strerror(saved));
|
||||
if (rep)
|
||||
*rep = local;
|
||||
if (out)
|
||||
fprintf(out, "snapverify: %s: fstat: %s\n", path, strerror(saved));
|
||||
return -saved;
|
||||
}
|
||||
local.file_size = st.st_size;
|
||||
|
||||
char header[SNAP_HEADER_MAX];
|
||||
ssize_t n = read(fd, header, sizeof(header));
|
||||
if (n < 16) {
|
||||
close(fd);
|
||||
fail(&local, -EIO, 0, "short read on header (need >= 16 bytes)");
|
||||
if (rep)
|
||||
*rep = local;
|
||||
if (out)
|
||||
fprintf(out, "snapverify: %s: %s\n", path, local.fail_reason);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
if (memcmp(header, "XCESNAP\0", 8) != 0) {
|
||||
close(fd);
|
||||
fail(&local, -EINVAL, 0, "bad magic (not XCESNAP)");
|
||||
if (rep)
|
||||
*rep = local;
|
||||
if (out)
|
||||
fprintf(out, "snapverify: %s: %s\n", path, local.fail_reason);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
memcpy(&local.checkpoint_lsn, header + 8, 8);
|
||||
if (n >= SNAP_HEADER_MAX) {
|
||||
/* Decode the tag/hash UNCONDITIONALLY whenever the header extends
|
||||
* this far, regardless of the presence byte -- so a caller can
|
||||
* inspect the raw bytes even in edge cases. has_cfgref keeps its
|
||||
* existing meaning (whether those bytes are semantically
|
||||
* meaningful) untouched. */
|
||||
memcpy(&local.cfgref_tag, header + 17, 8);
|
||||
memcpy(local.cfgref_sha, header + 25, 32);
|
||||
if (header[16] != 0)
|
||||
local.has_cfgref = 1;
|
||||
}
|
||||
|
||||
off_t consumed = (off_t)n;
|
||||
|
||||
if (st.st_size <= consumed) {
|
||||
/* Legacy, position-only checkpoint — not a defect (design.md). */
|
||||
close(fd);
|
||||
local.consumed = consumed;
|
||||
if (rep)
|
||||
*rep = local;
|
||||
if (out)
|
||||
fprintf(out,
|
||||
"snapverify: %s: OK (legacy position-only, lsn=%" PRIu64
|
||||
", size=%jd)\n",
|
||||
path, local.checkpoint_lsn, (intmax_t)local.file_size);
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint32_t fmt = 0;
|
||||
if (read(fd, &fmt, sizeof(fmt)) != (ssize_t)sizeof(fmt)) {
|
||||
close(fd);
|
||||
fail(&local, -EIO, consumed, "short read on format_version");
|
||||
if (rep)
|
||||
*rep = local;
|
||||
if (out)
|
||||
fprintf(out, "snapverify: %s: %s (offset %jd)\n", path,
|
||||
local.fail_reason, (intmax_t)consumed);
|
||||
return -EIO;
|
||||
}
|
||||
consumed += (off_t)sizeof(fmt);
|
||||
local.has_entity_section = 1;
|
||||
local.format_version = fmt;
|
||||
|
||||
if (fmt != SNAP_FORMAT_VERSION) {
|
||||
close(fd);
|
||||
fail(&local, -EINVAL, consumed - (off_t)sizeof(fmt),
|
||||
"unrecognized format_version");
|
||||
if (rep)
|
||||
*rep = local;
|
||||
if (out)
|
||||
fprintf(out, "snapverify: %s: %s (got %u, expected %u, offset %jd)\n",
|
||||
path, local.fail_reason, fmt, SNAP_FORMAT_VERSION,
|
||||
(intmax_t)local.fail_offset);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
uint32_t job_count = 0;
|
||||
if (read(fd, &job_count, sizeof(job_count)) != (ssize_t)sizeof(job_count)) {
|
||||
close(fd);
|
||||
fail(&local, -EIO, consumed, "short read on job_count");
|
||||
if (rep)
|
||||
*rep = local;
|
||||
if (out)
|
||||
fprintf(out, "snapverify: %s: %s (offset %jd)\n", path,
|
||||
local.fail_reason, (intmax_t)consumed);
|
||||
return -EIO;
|
||||
}
|
||||
consumed += (off_t)sizeof(job_count);
|
||||
local.job_count = job_count;
|
||||
|
||||
for (uint32_t i = 0; i < job_count; i++) {
|
||||
off_t len_off = consumed;
|
||||
uint32_t plen = 0;
|
||||
if (read(fd, &plen, sizeof(plen)) != (ssize_t)sizeof(plen)) {
|
||||
close(fd);
|
||||
fail(&local, -EIO, len_off, "short read on job payload_len");
|
||||
if (rep)
|
||||
*rep = local;
|
||||
if (out)
|
||||
fprintf(out, "snapverify: %s: job %u: %s (offset %jd)\n", path,
|
||||
i, local.fail_reason, (intmax_t)len_off);
|
||||
return -EIO;
|
||||
}
|
||||
consumed += (off_t)sizeof(plen);
|
||||
|
||||
if (plen == 0 || (off_t)plen > st.st_size - consumed) {
|
||||
close(fd);
|
||||
char why[160];
|
||||
snprintf(why, sizeof(why),
|
||||
"bad payload_len=%u (remaining=%jd)", plen,
|
||||
(intmax_t)(st.st_size - consumed));
|
||||
fail(&local, -EIO, len_off, why);
|
||||
if (rep)
|
||||
*rep = local;
|
||||
if (out)
|
||||
fprintf(out, "snapverify: %s: job %u: %s (length field at "
|
||||
"offset %jd)\n",
|
||||
path, i, local.fail_reason, (intmax_t)len_off);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
if (lseek(fd, plen, SEEK_CUR) < 0) {
|
||||
close(fd);
|
||||
fail(&local, -EIO, consumed, "seek past job payload failed");
|
||||
if (rep)
|
||||
*rep = local;
|
||||
if (out)
|
||||
fprintf(out, "snapverify: %s: job %u: %s\n", path, i,
|
||||
local.fail_reason);
|
||||
return -EIO;
|
||||
}
|
||||
consumed += (off_t)plen;
|
||||
local.jobs_ok++;
|
||||
}
|
||||
|
||||
uint32_t queue_count = 0;
|
||||
if (read(fd, &queue_count, sizeof(queue_count)) !=
|
||||
(ssize_t)sizeof(queue_count)) {
|
||||
close(fd);
|
||||
fail(&local, -EIO, consumed, "short read on queue_count");
|
||||
if (rep)
|
||||
*rep = local;
|
||||
if (out)
|
||||
fprintf(out, "snapverify: %s: %s (offset %jd)\n", path,
|
||||
local.fail_reason, (intmax_t)consumed);
|
||||
return -EIO;
|
||||
}
|
||||
consumed += (off_t)sizeof(queue_count);
|
||||
local.queue_count = queue_count;
|
||||
|
||||
for (uint32_t i = 0; i < queue_count; i++) {
|
||||
off_t len_off = consumed;
|
||||
uint32_t plen = 0;
|
||||
if (read(fd, &plen, sizeof(plen)) != (ssize_t)sizeof(plen)) {
|
||||
close(fd);
|
||||
fail(&local, -EIO, len_off, "short read on queue payload_len");
|
||||
if (rep)
|
||||
*rep = local;
|
||||
if (out)
|
||||
fprintf(out, "snapverify: %s: queue %u: %s (offset %jd)\n",
|
||||
path, i, local.fail_reason, (intmax_t)len_off);
|
||||
return -EIO;
|
||||
}
|
||||
consumed += (off_t)sizeof(plen);
|
||||
|
||||
if (plen == 0 || (off_t)plen > st.st_size - consumed) {
|
||||
close(fd);
|
||||
char why[160];
|
||||
snprintf(why, sizeof(why),
|
||||
"bad payload_len=%u (remaining=%jd)", plen,
|
||||
(intmax_t)(st.st_size - consumed));
|
||||
fail(&local, -EIO, len_off, why);
|
||||
if (rep)
|
||||
*rep = local;
|
||||
if (out)
|
||||
fprintf(out, "snapverify: %s: queue %u: %s (length field at "
|
||||
"offset %jd)\n",
|
||||
path, i, local.fail_reason, (intmax_t)len_off);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
if (lseek(fd, plen, SEEK_CUR) < 0) {
|
||||
close(fd);
|
||||
fail(&local, -EIO, consumed, "seek past queue payload failed");
|
||||
if (rep)
|
||||
*rep = local;
|
||||
if (out)
|
||||
fprintf(out, "snapverify: %s: queue %u: %s\n", path, i,
|
||||
local.fail_reason);
|
||||
return -EIO;
|
||||
}
|
||||
consumed += (off_t)plen;
|
||||
local.queues_ok++;
|
||||
}
|
||||
|
||||
/* Host section (config-upgrade-snapshot task 4/8.4, SNAPSHOT_FORMAT_
|
||||
* VERSION=2): new after the queue section (design.md "Checkpoint File
|
||||
* Layout", persist.c load_snapshot_entities()/write_snapshot()). Same
|
||||
* [payload_len][bytes] per-frame structural shape as the job/queue
|
||||
* loops above -- this tool validates structure only, never decodes the
|
||||
* capnp payload. */
|
||||
uint32_t host_count = 0;
|
||||
if (read(fd, &host_count, sizeof(host_count)) !=
|
||||
(ssize_t)sizeof(host_count)) {
|
||||
close(fd);
|
||||
fail(&local, -EIO, consumed, "short read on host_count");
|
||||
if (rep)
|
||||
*rep = local;
|
||||
if (out)
|
||||
fprintf(out, "snapverify: %s: %s (offset %jd)\n", path,
|
||||
local.fail_reason, (intmax_t)consumed);
|
||||
return -EIO;
|
||||
}
|
||||
consumed += (off_t)sizeof(host_count);
|
||||
local.host_count = host_count;
|
||||
|
||||
for (uint32_t i = 0; i < host_count; i++) {
|
||||
off_t len_off = consumed;
|
||||
uint32_t plen = 0;
|
||||
if (read(fd, &plen, sizeof(plen)) != (ssize_t)sizeof(plen)) {
|
||||
close(fd);
|
||||
fail(&local, -EIO, len_off, "short read on host payload_len");
|
||||
if (rep)
|
||||
*rep = local;
|
||||
if (out)
|
||||
fprintf(out, "snapverify: %s: host %u: %s (offset %jd)\n",
|
||||
path, i, local.fail_reason, (intmax_t)len_off);
|
||||
return -EIO;
|
||||
}
|
||||
consumed += (off_t)sizeof(plen);
|
||||
|
||||
if (plen == 0 || (off_t)plen > st.st_size - consumed) {
|
||||
close(fd);
|
||||
char why[160];
|
||||
snprintf(why, sizeof(why),
|
||||
"bad payload_len=%u (remaining=%jd)", plen,
|
||||
(intmax_t)(st.st_size - consumed));
|
||||
fail(&local, -EIO, len_off, why);
|
||||
if (rep)
|
||||
*rep = local;
|
||||
if (out)
|
||||
fprintf(out, "snapverify: %s: host %u: %s (length field at "
|
||||
"offset %jd)\n",
|
||||
path, i, local.fail_reason, (intmax_t)len_off);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
if (lseek(fd, plen, SEEK_CUR) < 0) {
|
||||
close(fd);
|
||||
fail(&local, -EIO, consumed, "seek past host payload failed");
|
||||
if (rep)
|
||||
*rep = local;
|
||||
if (out)
|
||||
fprintf(out, "snapverify: %s: host %u: %s\n", path, i,
|
||||
local.fail_reason);
|
||||
return -EIO;
|
||||
}
|
||||
consumed += (off_t)plen;
|
||||
local.hosts_ok++;
|
||||
}
|
||||
|
||||
/* Config-bytes section (config-upgrade-snapshot task 2.3/8.4): a
|
||||
* single flat length-prefixed blob appended LAST, after the host
|
||||
* section -- NOT a [count][frame...] shape like job/queue/host above,
|
||||
* since there is exactly one active configuration, never a set
|
||||
* (persist.c write_snapshot()). The length field itself may be
|
||||
* legitimately ABSENT (a file that ends right after the host section
|
||||
* is still a valid, if slightly older, fmt=2-consistent shape) --
|
||||
* distinguish that from the length field being present-but-zero
|
||||
* (persist.c explicitly writes an empty-but-present section when no
|
||||
* local config bytes exist yet) and from a genuinely truncated length
|
||||
* field (a short, non-zero partial read -- real corruption). */
|
||||
uint32_t cfg_bytes_len = 0;
|
||||
ssize_t cfg_len_n = read(fd, &cfg_bytes_len, sizeof(cfg_bytes_len));
|
||||
if (cfg_len_n == 0) {
|
||||
/* Clean EOF right after the host section -- no config-bytes
|
||||
* section present at all. Not a defect. */
|
||||
local.has_config_bytes_section = 0;
|
||||
} else if (cfg_len_n != (ssize_t)sizeof(cfg_bytes_len)) {
|
||||
close(fd);
|
||||
fail(&local, -EIO, consumed, "short read on config_bytes_len");
|
||||
if (rep)
|
||||
*rep = local;
|
||||
if (out)
|
||||
fprintf(out, "snapverify: %s: %s (offset %jd)\n", path,
|
||||
local.fail_reason, (intmax_t)consumed);
|
||||
return -EIO;
|
||||
} else {
|
||||
off_t len_off = consumed;
|
||||
consumed += (off_t)sizeof(cfg_bytes_len);
|
||||
local.has_config_bytes_section = 1;
|
||||
local.config_bytes_len = cfg_bytes_len;
|
||||
|
||||
if ((off_t)cfg_bytes_len > st.st_size - consumed) {
|
||||
close(fd);
|
||||
char why[160];
|
||||
snprintf(why, sizeof(why),
|
||||
"bad config_bytes_len=%u (remaining=%jd)", cfg_bytes_len,
|
||||
(intmax_t)(st.st_size - consumed));
|
||||
fail(&local, -EIO, len_off, why);
|
||||
if (rep)
|
||||
*rep = local;
|
||||
if (out)
|
||||
fprintf(out, "snapverify: %s: %s (length field at offset "
|
||||
"%jd)\n",
|
||||
path, local.fail_reason, (intmax_t)len_off);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
if (cfg_bytes_len > 0) {
|
||||
if (lseek(fd, cfg_bytes_len, SEEK_CUR) < 0) {
|
||||
close(fd);
|
||||
fail(&local, -EIO, consumed,
|
||||
"seek past config bytes failed");
|
||||
if (rep)
|
||||
*rep = local;
|
||||
if (out)
|
||||
fprintf(out, "snapverify: %s: %s\n", path,
|
||||
local.fail_reason);
|
||||
return -EIO;
|
||||
}
|
||||
consumed += (off_t)cfg_bytes_len;
|
||||
}
|
||||
}
|
||||
|
||||
close(fd);
|
||||
local.consumed = consumed;
|
||||
if (consumed != st.st_size) {
|
||||
char why[160];
|
||||
snprintf(why, sizeof(why),
|
||||
"trailing garbage: consumed=%jd but file_size=%jd",
|
||||
(intmax_t)consumed, (intmax_t)st.st_size);
|
||||
fail(&local, -EIO, consumed, why);
|
||||
if (rep)
|
||||
*rep = local;
|
||||
if (out)
|
||||
fprintf(out, "snapverify: %s: %s\n", path, local.fail_reason);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
if (rep)
|
||||
*rep = local;
|
||||
if (out)
|
||||
fprintf(out,
|
||||
"snapverify: %s: OK (lsn=%" PRIu64 " fmt=%u jobs=%u queues=%u "
|
||||
"hosts=%u cfgref_tag=%016" PRIx64 " cfg_bytes_len=%u "
|
||||
"has_cfg_bytes=%d size=%jd)\n",
|
||||
path, local.checkpoint_lsn, local.format_version,
|
||||
local.job_count, local.queue_count, local.host_count,
|
||||
local.cfgref_tag, local.config_bytes_len,
|
||||
local.has_config_bytes_section, (intmax_t)local.file_size);
|
||||
return 0;
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue