test(raft-ha-core-safety): machine-checkable walcompare report (task 1.1)

Per-member (lsn,term,pcrc,present) report + committed-vs-uncommitted
divergence classification via walcompare_report()/--report. Soak signal
(OK/exit-code) preserved. 4 new unit tests, all GREEN.
This commit is contained in:
Rongsong Shen 2026-06-27 04:53:32 +00:00
parent 227f592250
commit b028d25dbd
5 changed files with 453 additions and 120 deletions

View file

@ -354,6 +354,160 @@ static void test_too_few_members_errors(void)
TEST_ASSERT_LESS_THAN_INT(0, rc);
}
/* ------------------------------------------------------------------ */
/* Machine-checkable report: a committed-prefix divergence must emit a */
/* parseable per-member line set and a committed-vs-uncommitted */
/* classification (task 1.1, R7.1/R7.5). Captured via open_memstream so */
/* the exact greppable tokens are asserted, not just the verdict. */
/* ------------------------------------------------------------------ */
static void test_machine_report_committed_divergence(void)
{
wal_spec_t a_specs[] = {
{ 1, WAL_NOOP, NULL },
{ 1, WAL_JOB_SUBMIT, "jobA" },
{ 2, WAL_JOB_SUBMIT, "jobB" }, /* LSN 3, term 2 */
};
wal_spec_t b_specs[] = {
{ 1, WAL_NOOP, NULL },
{ 1, WAL_JOB_SUBMIT, "jobA" },
{ 3, WAL_JOB_SUBMIT, "jobB" }, /* LSN 3, term 3 — divergent */
};
char a[256], b[256];
build_member(a, sizeof(a), a_specs, 3);
build_member(b, sizeof(b), b_specs, 3);
char* buf = NULL;
size_t len = 0;
FILE* out = open_memstream(&buf, &len);
TEST_ASSERT_NOT_NULL(out);
walcompare_member_t m[2] = { { a, 0 }, { b, 0 } };
walcompare_divergence_t div;
int rc = walcompare(m, 2, out, &div);
fclose(out);
TEST_ASSERT_EQUAL_INT(WALCMP_DIVERGENT, rc);
/* committed-vs-uncommitted classification line. */
TEST_ASSERT_NOT_NULL(strstr(buf, "WALCMP_DIVERGENCE"));
TEST_ASSERT_NOT_NULL(strstr(buf, "class=committed"));
/* parseable per-member lines with fixed field order. */
TEST_ASSERT_NOT_NULL(strstr(buf, "WALCMP member=0 lsn=3"));
TEST_ASSERT_NOT_NULL(strstr(buf, "WALCMP member=1 lsn=3"));
TEST_ASSERT_NOT_NULL(strstr(buf, "status=present"));
/* divergence struct carries the committed classification flag. */
TEST_ASSERT_EQUAL_INT(1, div.committed);
free(buf);
}
/* ------------------------------------------------------------------ */
/* walcompare_report emits a full per-member, per-committed-LSN table */
/* (lsn, term, pcrc, status) for identical members and a clean verdict. */
/* ------------------------------------------------------------------ */
static void test_report_full_member_table(void)
{
wal_spec_t specs[] = {
{ 1, WAL_NOOP, NULL },
{ 1, WAL_JOB_SUBMIT, "jobA" },
{ 2, WAL_JOB_SUBMIT, "jobB" },
};
char a[256], b[256], c[256];
build_member(a, sizeof(a), specs, 3);
build_member(b, sizeof(b), specs, 3);
build_member(c, sizeof(c), specs, 3);
char* buf = NULL;
size_t len = 0;
FILE* out = open_memstream(&buf, &len);
TEST_ASSERT_NOT_NULL(out);
walcompare_member_t m[3] = { { a, 0 }, { b, 0 }, { c, 0 } };
walcompare_divergence_t div;
int rc = walcompare_report(m, 3, out, &div);
fclose(out);
TEST_ASSERT_EQUAL_INT(WALCMP_OK, rc);
/* every member appears at every committed LSN with committed=1. */
TEST_ASSERT_NOT_NULL(strstr(buf, "WALCMP member=0 lsn=1"));
TEST_ASSERT_NOT_NULL(strstr(buf, "WALCMP member=2 lsn=3"));
TEST_ASSERT_NOT_NULL(strstr(buf, "committed=1"));
TEST_ASSERT_NOT_NULL(strstr(buf, "status=present"));
free(buf);
}
/* ------------------------------------------------------------------ */
/* A MISSING committed entry is tagged status=MISSING in the report, */
/* and classified class=committed (a committed-prefix hole, R4.2/R6.4). */
/* ------------------------------------------------------------------ */
static void test_report_missing_tagged(void)
{
wal_spec_t a_specs[] = {
{ 1, WAL_NOOP, NULL },
{ 1, WAL_JOB_SUBMIT, "jobA" },
{ 2, WAL_JOB_SUBMIT, "jobB" },
};
wal_spec_t b_specs[] = {
{ 1, WAL_NOOP, NULL },
{ 1, WAL_JOB_SUBMIT, "jobA" },
};
char a[256], b[256];
build_member(a, sizeof(a), a_specs, 3);
build_member(b, sizeof(b), b_specs, 2);
char* buf = NULL;
size_t len = 0;
FILE* out = open_memstream(&buf, &len);
TEST_ASSERT_NOT_NULL(out);
walcompare_member_t m[2] = { { a, 3 }, { b, 3 } };
walcompare_divergence_t div;
int rc = walcompare_report(m, 2, out, &div);
fclose(out);
TEST_ASSERT_EQUAL_INT(WALCMP_DIVERGENT, rc);
TEST_ASSERT_NOT_NULL(strstr(buf, "status=MISSING"));
TEST_ASSERT_NOT_NULL(strstr(buf, "class=committed"));
TEST_ASSERT_EQUAL_INT(1, div.committed);
free(buf);
}
/* ------------------------------------------------------------------ */
/* A divergence in the UNCOMMITTED tail (above the committed boundary) is */
/* reported class=uncommitted and is NON-fatal: the committed verdict is */
/* still WALCMP_OK (task 1.1 classification; §6.1 committed-prefix only). */
/* ------------------------------------------------------------------ */
static void test_report_uncommitted_tail_classified(void)
{
wal_spec_t a_specs[] = {
{ 1, WAL_NOOP, NULL },
{ 1, WAL_JOB_SUBMIT, "jobA" }, /* committed LSN 2 */
{ 2, WAL_JOB_SUBMIT, "uncA" }, /* LSN 3 uncommitted, divergent */
};
wal_spec_t b_specs[] = {
{ 1, WAL_NOOP, NULL },
{ 1, WAL_JOB_SUBMIT, "jobA" },
{ 9, WAL_JOB_SUBMIT, "uncB" }, /* LSN 3, different term+bytes */
};
char a[256], b[256];
build_member(a, sizeof(a), a_specs, 3);
build_member(b, sizeof(b), b_specs, 3);
char* buf = NULL;
size_t len = 0;
FILE* out = open_memstream(&buf, &len);
TEST_ASSERT_NOT_NULL(out);
/* committed boundary pinned at 2 → LSN-3 divergence is the tail. */
walcompare_member_t m[2] = { { a, 2 }, { b, 2 } };
walcompare_divergence_t div;
int rc = walcompare_report(m, 2, out, &div);
fclose(out);
TEST_ASSERT_EQUAL_INT(WALCMP_OK, rc); /* tail divergence is non-fatal */
TEST_ASSERT_NOT_NULL(strstr(buf, "class=uncommitted"));
TEST_ASSERT_NOT_NULL(strstr(buf, "committed=0"));
free(buf);
}
void test_walcompare(void)
{
RUN_TEST(test_identical_members_ok);
@ -367,4 +521,8 @@ void test_walcompare(void)
RUN_TEST(test_three_members_divergent_config_marker);
RUN_TEST(test_missing_member_errors);
RUN_TEST(test_too_few_members_errors);
RUN_TEST(test_machine_report_committed_divergence);
RUN_TEST(test_report_full_member_table);
RUN_TEST(test_report_missing_tagged);
RUN_TEST(test_report_uncommitted_tail_classified);
}