test(scheduler-core): close replay format round-trip coverage gap (task 7.5)

Audit of all ~19 new types/enums from tasks 7.1-7.4 found one genuine
gap: ReplayEvent's own envelope union (11 members across 7.1+7.2) was
never actually constructed/encoded in any test, despite its individual
payload types each having their own round-trip test. Adds 3 tests
(jobStateChange/jobUpdate/endSession variants) proving the union
mechanism itself round-trips correctly.

No new schema/typemap changes -- audit-only. Decided against creating
design.md's originally-named test_replay_format.c: tasks 7.1-7.4
independently, consistently (4-for-4) chose the existing
test_e2e_capnp.c instead. Deliberately did not add Request.body/
RequestReply.data union-wiring tests for OPCode_schedAdmin, matching
universal codebase precedent (opcode-dispatch union members are always
tested via functional dispatch tests, never bare schema round-trips,
and the real dispatch logic doesn't exist until task 10.1).

Closes out Group 7 (Foundation: Replay Wire Format) entirely.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019dw5WnN3DXStaEaPYfACU1
This commit is contained in:
Rongsong Shen 2026-08-04 14:31:56 +00:00
parent 5cd8687b32
commit 7704710985
Signed by: rshen
SSH key fingerprint: SHA256:8Vf2pZ3h6rJa7EhGK+WaAQ9Mta/apZvteW6g9U/WKKk
2 changed files with 211 additions and 1 deletions

View file

@ -190,11 +190,12 @@
- _Boundary: ReplayFormat — not (P), same file as 7.2/7.3 (see 7.2)_
- _Verified: 2026-08-04 — SchedAdminAction enum + SchedAdminRequest/SchedAdminReply structs added matching requirements.md Req 5 AC 10 verbatim. schedAdmin@42 (OPCode), schedadminmsg@31 (Request.body), schedadminreplymsg@19 (RequestReply.data) — all three the exact next-available ordinals, verified live before dispatch and re-verified byte-identical below by review. Deliberately schema-only per this task's own text — the controller dispatch logic (cluster.c's case OPCode_schedAdmin:) is task 10.1's job, not this one; confirmed cluster.c untouched. Round-trip tests cover all 3 SchedAdminAction values and both ok=true/ok=false SchedAdminReply cases with an asymmetric ok/recording combination, ruling out a Bool-field bit-packing mixup. 1084/1084 controller_unit_test, full clean project build, confirmed independently three times, review APPROVED first pass. Closes out Group 7 (Foundation: Replay Wire Format) entirely — tasks 7.1-7.4 all complete._
- [ ] 7.5 Replay format round-trip test suite
- [x] 7.5 Replay format round-trip test suite
- Encode and decode every new message type introduced in this group, asserting field-for-field equality after round-trip
- Observable: the full round-trip test suite passes for every new struct, including the internal-state version-mismatch negative test
- _Requirements: 5.20_
- _Depends: 7.2, 7.3, 7.4_
- _Verified: 2026-08-04 — audit of all ~19 new types/enums from tasks 7.1-7.4 found one genuine gap: ReplayEvent's own envelope union (spanning 11 members across 7.1+7.2) was never actually constructed/encoded in any test, despite its individual payload types each having their own round-trip test. Fixed with 3 new tests (jobStateChange/jobUpdate/endSession variants). All other 18 types independently re-verified genuinely field-for-field, non-shallow. Decided against creating design.md's originally-named test_replay_format.c: tasks 7.1-7.4 independently, consistently (4-for-4) chose the existing test_e2e_capnp.c instead, matching task 4.3's core_events.c-vs-core.c precedent for design.md file-naming being non-binding guidance. Deliberately did not add Request.body/RequestReply.data union-wiring tests for OPCode_schedAdmin, matching universal codebase precedent (opcode-dispatch union members are always tested via functional dispatch tests, never bare schema round-trips, and the real dispatch logic doesn't exist until task 10.1). Internal-state version-mismatch negative test re-verified real and correctly scoped (schema-tag level only, matching the Observable). Zero schema/typemap changes -- audit-only, as scoped. 1087/1087 controller_unit_test, review APPROVED first pass with every claim independently re-derived. Closes out Group 7 (Foundation: Replay Wire Format) entirely -- tasks 7.1-7.5 all complete._
- [ ] 8. Core: Snapshot Serialization

View file

@ -1495,6 +1495,212 @@ static void test_sched_admin_reply_round_trip(void)
printf("test_sched_admin_reply_round_trip: PASS\n");
}
/* ------------------------------------------------------------------ */
/* Test 18: ReplayEvent's `body` union round-trips through the */
/* jobStateChange variant (scheduler-core task 7.5 gap-fill, Req 5 */
/* AC 20) -- GENUINE COVERAGE GAP found during the 7.5 completeness */
/* audit: ReplayEvent (task 7.1's envelope struct, extended with the */
/* jobStateChange/jobUpdate union members by task 7.2) has a real C */
/* struct (replay_event_t, typemap.h) and a working encode_ReplayEvent_ */
/* ptr/decode_ReplayEvent/free_ReplayEvent codec bridge -- exactly the */
/* same shape as ReplayDecision, which DID get a union-wrapping test */
/* (Test 14) -- yet no test anywhere ever constructed a ReplayEvent or */
/* exercised its `body` union. This mirrors Test 14's precedent: the */
/* payload type itself (JobStateChangeEvent) is already proven in */
/* isolation by Test 9, so this test's job is narrower -- prove the */
/* union GROUP mechanism on ReplayEvent itself (ts alongside body_which */
/* alongside the payload) actually round-trips end to end. */
/* ------------------------------------------------------------------ */
static void test_replay_event_job_state_change_round_trip(void)
{
job_state_change_event_t change;
memset(&change, 0, sizeof(change));
change.jobid = 61616ULL;
change.old_state = 1u;
change.new_state = 5u;
change.exit_code = 0;
replay_event_t src;
memset(&src, 0, sizeof(src));
src.ts = 70001ULL;
src.body_which = ReplayEvent_body_jobStateChange;
src.body.job_state_change = &change;
struct capn c;
capn_init_malloc(&c);
struct capn_segment *cs = capn_root(&c).seg;
ReplayEvent_ptr p;
encode_ReplayEvent_ptr(cs, &p, &src);
capn_setp(capn_root(&c), 0, p.p);
int64_t sz = capn_size(&c);
TEST_ASSERT_TRUE(sz > 0);
uint8_t *buf = (uint8_t *)malloc((size_t)sz);
TEST_ASSERT_NOT_NULL(buf);
capn_write_mem(&c, buf, (size_t)sz, 0);
capn_free(&c);
struct capn d;
TEST_ASSERT_EQUAL_INT(0, capn_init_mem(&d, buf, (size_t)sz, 0));
ReplayEvent_ptr dp;
dp.p = capn_getp(capn_root(&d), 0, 1);
struct ReplayEvent wire;
read_ReplayEvent(&wire, dp);
replay_event_t dst;
memset(&dst, 0, sizeof(dst));
decode_ReplayEvent(&dst, &wire);
TEST_ASSERT_EQUAL_UINT64(70001ULL, dst.ts);
TEST_ASSERT_EQUAL_INT((int)ReplayEvent_body_jobStateChange, (int)dst.body_which);
TEST_ASSERT_NOT_NULL(dst.body.job_state_change);
TEST_ASSERT_EQUAL_UINT64(61616ULL, dst.body.job_state_change->jobid);
TEST_ASSERT_EQUAL_UINT32(1u, dst.body.job_state_change->old_state);
TEST_ASSERT_EQUAL_UINT32(5u, dst.body.job_state_change->new_state);
TEST_ASSERT_EQUAL_INT32(0, dst.body.job_state_change->exit_code);
free_ReplayEvent(&dst);
capn_free(&d);
free(buf);
printf("test_replay_event_job_state_change_round_trip: PASS\n");
}
/* ------------------------------------------------------------------ */
/* Test 19: ReplayEvent's `body` union round-trips through the */
/* jobUpdate variant (scheduler-core task 7.5 gap-fill, Req 5 AC 20) -- */
/* same rationale as Test 18. JobUpdateEvent's own deep field tree */
/* (submit/allocs/pidinfo/pend_reasons) is already proven field-for- */
/* field by Test 10 -- this test only needs a light population to prove */
/* the UNION WRAPPING (ts + body_which discriminant alongside the */
/* pointer payload) survives the wire, not to re-prove JobUpdateEvent's */
/* own internals again. */
/* ------------------------------------------------------------------ */
static void test_replay_event_job_update_round_trip(void)
{
job_submit_data_t submit;
memset(&submit, 0, sizeof(submit));
submit.queue = "batch";
submit.user = "bob";
submit.job_name = "replay-event-update-job";
xce_job_t job;
memset(&job, 0, sizeof(job));
job.jobid = 9101ULL;
job.submit = &submit;
job.state = 3u;
job.run_time = 45;
job_update_event_t update;
memset(&update, 0, sizeof(update));
update.jobid = 9101ULL;
update.changed_fields = 0x1u;
update.job = &job;
replay_event_t src;
memset(&src, 0, sizeof(src));
src.ts = 70002ULL;
src.body_which = ReplayEvent_body_jobUpdate;
src.body.job_update = &update;
struct capn c;
capn_init_malloc(&c);
struct capn_segment *cs = capn_root(&c).seg;
ReplayEvent_ptr p;
encode_ReplayEvent_ptr(cs, &p, &src);
capn_setp(capn_root(&c), 0, p.p);
int64_t sz = capn_size(&c);
TEST_ASSERT_TRUE(sz > 0);
uint8_t *buf = (uint8_t *)malloc((size_t)sz);
TEST_ASSERT_NOT_NULL(buf);
capn_write_mem(&c, buf, (size_t)sz, 0);
capn_free(&c);
struct capn d;
TEST_ASSERT_EQUAL_INT(0, capn_init_mem(&d, buf, (size_t)sz, 0));
ReplayEvent_ptr dp;
dp.p = capn_getp(capn_root(&d), 0, 1);
struct ReplayEvent wire;
read_ReplayEvent(&wire, dp);
replay_event_t dst;
memset(&dst, 0, sizeof(dst));
decode_ReplayEvent(&dst, &wire);
TEST_ASSERT_EQUAL_UINT64(70002ULL, dst.ts);
TEST_ASSERT_EQUAL_INT((int)ReplayEvent_body_jobUpdate, (int)dst.body_which);
TEST_ASSERT_NOT_NULL(dst.body.job_update);
TEST_ASSERT_EQUAL_UINT64(9101ULL, dst.body.job_update->jobid);
TEST_ASSERT_EQUAL_UINT32(0x1u, dst.body.job_update->changed_fields);
TEST_ASSERT_NOT_NULL(dst.body.job_update->job);
TEST_ASSERT_EQUAL_UINT64(9101ULL, dst.body.job_update->job->jobid);
TEST_ASSERT_EQUAL_UINT32(3u, dst.body.job_update->job->state);
TEST_ASSERT_NOT_NULL(dst.body.job_update->job->submit);
TEST_ASSERT_EQUAL_STRING("batch", dst.body.job_update->job->submit->queue);
TEST_ASSERT_EQUAL_STRING("bob", dst.body.job_update->job->submit->user);
free_ReplayEvent(&dst);
capn_free(&d);
free(buf);
printf("test_replay_event_job_update_round_trip: PASS\n");
}
/* ------------------------------------------------------------------ */
/* Test 20: ReplayEvent's `body` union round-trips through the */
/* endSession variant (scheduler-core task 7.5 gap-fill, Req 5 AC 20) -- */
/* the Void, no-payload marker arm. Structurally distinct from every */
/* other ReplayEvent.body case (no pointer payload to encode/decode -- */
/* the generated encode_/decode_/free_ switch hits its `default: break;` */
/* branch instead of a per-type codec call), so it is a genuinely */
/* separate code path worth its own round trip: proves the discriminant */
/* alone (without a payload) still survives the wire faithfully, per the */
/* schema's documented "recording stop marker" framing role (Req 5 */
/* AC 20 final-message-of-stream framing). */
/* ------------------------------------------------------------------ */
static void test_replay_event_end_session_round_trip(void)
{
replay_event_t src;
memset(&src, 0, sizeof(src));
src.ts = 70003ULL;
src.body_which = ReplayEvent_body_endSession;
struct capn c;
capn_init_malloc(&c);
struct capn_segment *cs = capn_root(&c).seg;
ReplayEvent_ptr p;
encode_ReplayEvent_ptr(cs, &p, &src);
capn_setp(capn_root(&c), 0, p.p);
int64_t sz = capn_size(&c);
TEST_ASSERT_TRUE(sz > 0);
uint8_t *buf = (uint8_t *)malloc((size_t)sz);
TEST_ASSERT_NOT_NULL(buf);
capn_write_mem(&c, buf, (size_t)sz, 0);
capn_free(&c);
struct capn d;
TEST_ASSERT_EQUAL_INT(0, capn_init_mem(&d, buf, (size_t)sz, 0));
ReplayEvent_ptr dp;
dp.p = capn_getp(capn_root(&d), 0, 1);
struct ReplayEvent wire;
read_ReplayEvent(&wire, dp);
replay_event_t dst;
memset(&dst, 0, sizeof(dst));
decode_ReplayEvent(&dst, &wire);
TEST_ASSERT_EQUAL_UINT64(70003ULL, dst.ts);
TEST_ASSERT_EQUAL_INT((int)ReplayEvent_body_endSession, (int)dst.body_which);
free_ReplayEvent(&dst);
capn_free(&d);
free(buf);
printf("test_replay_event_end_session_round_trip: PASS\n");
}
/* ------------------------------------------------------------------ */
/* Suite entry point */
/* ------------------------------------------------------------------ */
@ -1521,4 +1727,7 @@ void test_e2e_capnp(void)
RUN_TEST(test_snapshot_data_round_trip);
RUN_TEST(test_sched_admin_request_round_trip);
RUN_TEST(test_sched_admin_reply_round_trip);
RUN_TEST(test_replay_event_job_state_change_round_trip);
RUN_TEST(test_replay_event_job_update_round_trip);
RUN_TEST(test_replay_event_end_session_round_trip);
}