/* * test_persist_repl.c — P2-4: Tests for repl_broadcast wired into WAL write paths. * * TDD RED phase: these tests must FAIL before implementation. * After implementation they must all PASS. * * Tests verify: * 1. persist_t has repl field (structural/compile-time check) * 2. persist_wal_async succeeds with zero standbys * 3. persist_wal_sync_append returns 0 (WAL success) but sets sync_rc < 0 (degraded) * 4. last_replicated_crc NOT updated by WAL_CHECKPOINT (checkpoint marker) */ #include #include #include #include #include #include #include #include "persist.h" #include "repl.h" /* ------------------------------------------------------------------ */ /* Helpers */ /* ------------------------------------------------------------------ */ static persist_t* make_repl_persist(const char* suffix) { persist_t* p = calloc(1, sizeof(persist_t)); persist_config_t cfg = persist_config_defaults(); cfg.wal_sync_ms = 5000; cfg.sync_timeout_ms = 50; /* short timeout so sync tests don't hang */ char tmpdir[256]; snprintf(tmpdir, sizeof(tmpdir), "/tmp/xce_repl_%s_XXXXXX", suffix); if (!mkdtemp(tmpdir)) { free(p); return NULL; } persist_set_workdir(p, tmpdir, "ctrl"); persist_init(p, &cfg, NULL); persist_start(p); return p; } static void teardown_repl_persist(persist_t* p) { if (!p) return; persist_stop(p); persist_fini(p); free(p); } /* ------------------------------------------------------------------ */ /* Tests */ /* ------------------------------------------------------------------ */ /* * Structural: persist_t must contain a repl field of type replicator_t. * This is a compile-time check — accessing p.repl.nstandbys must compile. */ static void test_persist_has_repl_field(void) { persist_t p; memset(&p, 0, sizeof(p)); /* Accessing repl.nstandbys is the compile-time guard */ TEST_ASSERT_EQUAL_INT(0, p.repl.nstandbys); printf("test_persist_has_repl_field: PASS\n"); } /* * WAL async must succeed when there are zero standbys. * repl_broadcast is a stub; no replication occurs, but write must not fail. */ static void test_wal_async_no_standbys_succeeds(void) { persist_t* p = make_repl_persist("async"); TEST_ASSERT_NOT_NULL(p); int rc = persist_wal_async(p, WAL_JOB_UPDATE, "payload", 7); TEST_ASSERT_EQUAL_INT(0, rc); teardown_repl_persist(p); printf("test_wal_async_no_standbys_succeeds: PASS\n"); } /* * persist_wal_sync_append must return 0 (WAL write succeeded locally) even * when there are no standbys (degraded mode). The sync_rc_out parameter * must be set to a negative value to signal degraded mode * (-ENODEV = no standbys, or -ETIMEDOUT = timeout). */ static void test_wal_sync_no_standbys_returns_enodev_in_sync_rc(void) { persist_t* p = make_repl_persist("sync"); TEST_ASSERT_NOT_NULL(p); int sync_rc = 0; int rc = persist_wal_sync_append(p, WAL_JOB_SUBMIT, "job_payload", 11, &sync_rc); /* WAL write itself must succeed */ TEST_ASSERT_EQUAL_INT(0, rc); /* sync_rc: -ENODEV (no standbys) or -ETIMEDOUT — either indicates degraded */ TEST_ASSERT_TRUE(sync_rc < 0); teardown_repl_persist(p); printf("test_wal_sync_no_standbys_returns_enodev_in_sync_rc: PASS\n"); } /* * WAL_CHECKPOINT (via persist_checkpoint) must NOT update last_replicated_crc. * Checkpoint markers are local-only; the replication CRC chain must remain * unbroken for standbys (design doc Part 2.3). */ static void test_last_replicated_crc_not_updated_for_checkpoint(void) { persist_t* p = make_repl_persist("crc"); TEST_ASSERT_NOT_NULL(p); uint32_t crc_before = p->last_replicated_crc; /* persist_checkpoint writes a WAL_CHECKPOINT marker then triggers a snap. * Neither the marker write nor snap must update last_replicated_crc. */ persist_checkpoint(p); /* last_replicated_crc must be unchanged from before the checkpoint */ TEST_ASSERT_EQUAL_UINT32(crc_before, p->last_replicated_crc); teardown_repl_persist(p); printf("test_last_replicated_crc_not_updated_for_checkpoint: PASS\n"); } /* ------------------------------------------------------------------ */ /* Entry point */ /* ------------------------------------------------------------------ */ void test_persist_repl(void) { RUN_TEST(test_persist_has_repl_field); RUN_TEST(test_wal_async_no_standbys_succeeds); RUN_TEST(test_wal_sync_no_standbys_returns_enodev_in_sync_rc); RUN_TEST(test_last_replicated_crc_not_updated_for_checkpoint); }