forked from lei.chen/myxce
feat: persistence hook functions wired to WAL write on AFTER_EVENT (P0-6)
Implements persist_job_hook_fun, persist_queue_hook_fun, and persist_worker_hook_fun — AFTER_EVENT hooks that write WAL entries whenever state mutations occur in the controller. - detail_to_wal_type() maps all 15 HOOK_DETAIL_* values to WAL types; unknown details return 0 and are silently skipped - HOOK_DETAIL_JOB_NEW uses the sync replication path (WAL_JOB_SUBMIT); all others use persist_wal_async - P0 payload: 4-byte detail value; full Cap'n Proto serialization in Task 15 - Hook signature matches hook_fn_t exactly: int fn(int type, int situation, int detail, void* cls, va_list ap) - Added <stdarg.h> to persist.h for va_list; added hook.h and xce/cluster_def.h includes to persist.c - 18 new unit tests in test_persist_hooks.c covering all 15 detail→WAL mappings, skip-on-unknown, and sequential LSN advancement
This commit is contained in:
parent
045d9e3287
commit
6f3b98c6b5
5 changed files with 500 additions and 0 deletions
|
|
@ -11,6 +11,7 @@ extern void test_persist_schema(void);
|
|||
extern void test_persist_checkpoint(void);
|
||||
extern void test_persist_recovery(void);
|
||||
extern void test_hooks(void);
|
||||
extern void test_persist_hooks(void);
|
||||
|
||||
void setUp(void) { return; }
|
||||
|
||||
|
|
@ -91,6 +92,7 @@ int main(int argc, char* argv[])
|
|||
TEST(test_persist_checkpoint);
|
||||
TEST(test_persist_recovery);
|
||||
TEST(test_hooks);
|
||||
TEST(test_persist_hooks);
|
||||
|
||||
return UNITY_END();
|
||||
}
|
||||
|
|
|
|||
391
src/controller/testsuite/test_persist_hooks.c
Normal file
391
src/controller/testsuite/test_persist_hooks.c
Normal file
|
|
@ -0,0 +1,391 @@
|
|||
#define _GNU_SOURCE
|
||||
#include <dirent.h>
|
||||
#include <fcntl.h>
|
||||
#include <inttypes.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <unity/unity.h>
|
||||
|
||||
#include "config.h"
|
||||
#include "persist.h"
|
||||
#include "xce/cluster_def.h"
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Test helpers */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
static char g_tmpdir[256];
|
||||
|
||||
static persist_t* make_hook_test_persist(void)
|
||||
{
|
||||
strcpy(g_tmpdir, "/tmp/xce_hook_test_XXXXXX");
|
||||
if (!mkdtemp(g_tmpdir))
|
||||
return NULL;
|
||||
|
||||
persist_t* p = calloc(1, sizeof(persist_t));
|
||||
if (!p)
|
||||
return NULL;
|
||||
|
||||
persist_config_t cfg = persist_config_defaults();
|
||||
cfg.wal_sync_ms = 1; /* 1 ms — fsync thread wakes up immediately on stop */
|
||||
|
||||
persist_set_workdir(p, g_tmpdir, "hook-test");
|
||||
|
||||
if (persist_init(p, &cfg, NULL) != 0) {
|
||||
free(p);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (persist_start(p) != 0) {
|
||||
persist_fini(p);
|
||||
free(p);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return p;
|
||||
}
|
||||
|
||||
static void cleanup_hook_test_persist(persist_t* p)
|
||||
{
|
||||
persist_stop(p);
|
||||
persist_fini(p);
|
||||
free(p);
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Tests — RED phase: functions not yet implemented, must fail to link */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
static void test_job_hook_new_writes_wal_entry(void)
|
||||
{
|
||||
persist_t* p = make_hook_test_persist();
|
||||
TEST_ASSERT_NOT_NULL(p);
|
||||
|
||||
uint64_t lsn_before = p->current_lsn;
|
||||
|
||||
/* Call hook directly — bypasses hook registration system for unit test.
|
||||
* Signature: int hook_fn(int type, int situation, int detail,
|
||||
* void* data, va_list ap)
|
||||
* We pass NULL for ap (no variadic args used in P0 implementation). */
|
||||
int rc = persist_job_hook_fun(HOOK_AFTER_EVENT, HOOK_AFTER_EVENT,
|
||||
HOOK_DETAIL_JOB_NEW, p, NULL);
|
||||
TEST_ASSERT_EQUAL_INT(0, rc);
|
||||
|
||||
/* LSN must have advanced — WAL entry was written */
|
||||
TEST_ASSERT_GREATER_THAN(lsn_before, p->current_lsn);
|
||||
|
||||
cleanup_hook_test_persist(p);
|
||||
printf("test_job_hook_new_writes_wal_entry: PASS\n");
|
||||
}
|
||||
|
||||
static void test_job_hook_start_writes_wal_entry(void)
|
||||
{
|
||||
persist_t* p = make_hook_test_persist();
|
||||
TEST_ASSERT_NOT_NULL(p);
|
||||
|
||||
uint64_t lsn_before = p->current_lsn;
|
||||
|
||||
int rc = persist_job_hook_fun(HOOK_AFTER_EVENT, HOOK_AFTER_EVENT,
|
||||
HOOK_DETAIL_JOB_START, p, NULL);
|
||||
TEST_ASSERT_EQUAL_INT(0, rc);
|
||||
TEST_ASSERT_GREATER_THAN(lsn_before, p->current_lsn);
|
||||
|
||||
cleanup_hook_test_persist(p);
|
||||
printf("test_job_hook_start_writes_wal_entry: PASS\n");
|
||||
}
|
||||
|
||||
static void test_job_hook_exit_writes_wal_entry(void)
|
||||
{
|
||||
persist_t* p = make_hook_test_persist();
|
||||
TEST_ASSERT_NOT_NULL(p);
|
||||
|
||||
uint64_t lsn_before = p->current_lsn;
|
||||
|
||||
int rc = persist_job_hook_fun(HOOK_AFTER_EVENT, HOOK_AFTER_EVENT,
|
||||
HOOK_DETAIL_JOB_EXIT, p, NULL);
|
||||
TEST_ASSERT_EQUAL_INT(0, rc);
|
||||
TEST_ASSERT_GREATER_THAN(lsn_before, p->current_lsn);
|
||||
|
||||
cleanup_hook_test_persist(p);
|
||||
printf("test_job_hook_exit_writes_wal_entry: PASS\n");
|
||||
}
|
||||
|
||||
static void test_job_hook_fail_writes_wal_entry(void)
|
||||
{
|
||||
persist_t* p = make_hook_test_persist();
|
||||
TEST_ASSERT_NOT_NULL(p);
|
||||
|
||||
uint64_t lsn_before = p->current_lsn;
|
||||
|
||||
int rc = persist_job_hook_fun(HOOK_AFTER_EVENT, HOOK_AFTER_EVENT,
|
||||
HOOK_DETAIL_JOB_FAIL, p, NULL);
|
||||
TEST_ASSERT_EQUAL_INT(0, rc);
|
||||
TEST_ASSERT_GREATER_THAN(lsn_before, p->current_lsn);
|
||||
|
||||
cleanup_hook_test_persist(p);
|
||||
printf("test_job_hook_fail_writes_wal_entry: PASS\n");
|
||||
}
|
||||
|
||||
static void test_job_hook_stop_writes_wal_entry(void)
|
||||
{
|
||||
persist_t* p = make_hook_test_persist();
|
||||
TEST_ASSERT_NOT_NULL(p);
|
||||
|
||||
uint64_t lsn_before = p->current_lsn;
|
||||
|
||||
int rc = persist_job_hook_fun(HOOK_AFTER_EVENT, HOOK_AFTER_EVENT,
|
||||
HOOK_DETAIL_JOB_STOP, p, NULL);
|
||||
TEST_ASSERT_EQUAL_INT(0, rc);
|
||||
TEST_ASSERT_GREATER_THAN(lsn_before, p->current_lsn);
|
||||
|
||||
cleanup_hook_test_persist(p);
|
||||
printf("test_job_hook_stop_writes_wal_entry: PASS\n");
|
||||
}
|
||||
|
||||
static void test_job_hook_resume_writes_wal_entry(void)
|
||||
{
|
||||
persist_t* p = make_hook_test_persist();
|
||||
TEST_ASSERT_NOT_NULL(p);
|
||||
|
||||
uint64_t lsn_before = p->current_lsn;
|
||||
|
||||
int rc = persist_job_hook_fun(HOOK_AFTER_EVENT, HOOK_AFTER_EVENT,
|
||||
HOOK_DETAIL_JOB_RESUME, p, NULL);
|
||||
TEST_ASSERT_EQUAL_INT(0, rc);
|
||||
TEST_ASSERT_GREATER_THAN(lsn_before, p->current_lsn);
|
||||
|
||||
cleanup_hook_test_persist(p);
|
||||
printf("test_job_hook_resume_writes_wal_entry: PASS\n");
|
||||
}
|
||||
|
||||
static void test_job_hook_update_writes_wal_entry(void)
|
||||
{
|
||||
persist_t* p = make_hook_test_persist();
|
||||
TEST_ASSERT_NOT_NULL(p);
|
||||
|
||||
uint64_t lsn_before = p->current_lsn;
|
||||
|
||||
int rc = persist_job_hook_fun(HOOK_AFTER_EVENT, HOOK_AFTER_EVENT,
|
||||
HOOK_DETAIL_JOB_UPDATE, p, NULL);
|
||||
TEST_ASSERT_EQUAL_INT(0, rc);
|
||||
TEST_ASSERT_GREATER_THAN(lsn_before, p->current_lsn);
|
||||
|
||||
cleanup_hook_test_persist(p);
|
||||
printf("test_job_hook_update_writes_wal_entry: PASS\n");
|
||||
}
|
||||
|
||||
static void test_job_hook_ckpt_writes_wal_entry(void)
|
||||
{
|
||||
persist_t* p = make_hook_test_persist();
|
||||
TEST_ASSERT_NOT_NULL(p);
|
||||
|
||||
uint64_t lsn_before = p->current_lsn;
|
||||
|
||||
int rc = persist_job_hook_fun(HOOK_AFTER_EVENT, HOOK_AFTER_EVENT,
|
||||
HOOK_DETAIL_JOB_CKPT, p, NULL);
|
||||
TEST_ASSERT_EQUAL_INT(0, rc);
|
||||
TEST_ASSERT_GREATER_THAN(lsn_before, p->current_lsn);
|
||||
|
||||
cleanup_hook_test_persist(p);
|
||||
printf("test_job_hook_ckpt_writes_wal_entry: PASS\n");
|
||||
}
|
||||
|
||||
static void test_job_hook_rusage_writes_wal_entry(void)
|
||||
{
|
||||
persist_t* p = make_hook_test_persist();
|
||||
TEST_ASSERT_NOT_NULL(p);
|
||||
|
||||
uint64_t lsn_before = p->current_lsn;
|
||||
|
||||
int rc = persist_job_hook_fun(HOOK_AFTER_EVENT, HOOK_AFTER_EVENT,
|
||||
HOOK_DETAIL_JOB_RUSAGE_UPDATE, p, NULL);
|
||||
TEST_ASSERT_EQUAL_INT(0, rc);
|
||||
TEST_ASSERT_GREATER_THAN(lsn_before, p->current_lsn);
|
||||
|
||||
cleanup_hook_test_persist(p);
|
||||
printf("test_job_hook_rusage_writes_wal_entry: PASS\n");
|
||||
}
|
||||
|
||||
static void test_job_hook_setpreason_writes_wal_entry(void)
|
||||
{
|
||||
persist_t* p = make_hook_test_persist();
|
||||
TEST_ASSERT_NOT_NULL(p);
|
||||
|
||||
uint64_t lsn_before = p->current_lsn;
|
||||
|
||||
int rc = persist_job_hook_fun(HOOK_AFTER_EVENT, HOOK_AFTER_EVENT,
|
||||
HOOK_DETAIL_SETPREASON, p, NULL);
|
||||
TEST_ASSERT_EQUAL_INT(0, rc);
|
||||
TEST_ASSERT_GREATER_THAN(lsn_before, p->current_lsn);
|
||||
|
||||
cleanup_hook_test_persist(p);
|
||||
printf("test_job_hook_setpreason_writes_wal_entry: PASS\n");
|
||||
}
|
||||
|
||||
static void test_job_hook_clrpreason_writes_wal_entry(void)
|
||||
{
|
||||
persist_t* p = make_hook_test_persist();
|
||||
TEST_ASSERT_NOT_NULL(p);
|
||||
|
||||
uint64_t lsn_before = p->current_lsn;
|
||||
|
||||
int rc = persist_job_hook_fun(HOOK_AFTER_EVENT, HOOK_AFTER_EVENT,
|
||||
HOOK_DETAIL_CLRPREASON, p, NULL);
|
||||
TEST_ASSERT_EQUAL_INT(0, rc);
|
||||
TEST_ASSERT_GREATER_THAN(lsn_before, p->current_lsn);
|
||||
|
||||
cleanup_hook_test_persist(p);
|
||||
printf("test_job_hook_clrpreason_writes_wal_entry: PASS\n");
|
||||
}
|
||||
|
||||
static void test_queue_hook_update_writes_wal_entry(void)
|
||||
{
|
||||
persist_t* p = make_hook_test_persist();
|
||||
TEST_ASSERT_NOT_NULL(p);
|
||||
|
||||
uint64_t lsn_before = p->current_lsn;
|
||||
|
||||
int rc = persist_queue_hook_fun(HOOK_AFTER_EVENT, HOOK_AFTER_EVENT,
|
||||
HOOK_DETAIL_QUEUE_UPDATE, p, NULL);
|
||||
TEST_ASSERT_EQUAL_INT(0, rc);
|
||||
TEST_ASSERT_GREATER_THAN(lsn_before, p->current_lsn);
|
||||
|
||||
cleanup_hook_test_persist(p);
|
||||
printf("test_queue_hook_update_writes_wal_entry: PASS\n");
|
||||
}
|
||||
|
||||
static void test_worker_hook_join_writes_wal_entry(void)
|
||||
{
|
||||
persist_t* p = make_hook_test_persist();
|
||||
TEST_ASSERT_NOT_NULL(p);
|
||||
|
||||
uint64_t lsn_before = p->current_lsn;
|
||||
|
||||
int rc = persist_worker_hook_fun(HOOK_AFTER_EVENT, HOOK_AFTER_EVENT,
|
||||
HOOK_DETAIL_WORKER_JOIN, p, NULL);
|
||||
TEST_ASSERT_EQUAL_INT(0, rc);
|
||||
TEST_ASSERT_GREATER_THAN(lsn_before, p->current_lsn);
|
||||
|
||||
cleanup_hook_test_persist(p);
|
||||
printf("test_worker_hook_join_writes_wal_entry: PASS\n");
|
||||
}
|
||||
|
||||
static void test_worker_hook_update_writes_wal_entry(void)
|
||||
{
|
||||
persist_t* p = make_hook_test_persist();
|
||||
TEST_ASSERT_NOT_NULL(p);
|
||||
|
||||
uint64_t lsn_before = p->current_lsn;
|
||||
|
||||
int rc = persist_worker_hook_fun(HOOK_AFTER_EVENT, HOOK_AFTER_EVENT,
|
||||
HOOK_DETAIL_WORKER_UPDATE, p, NULL);
|
||||
TEST_ASSERT_EQUAL_INT(0, rc);
|
||||
TEST_ASSERT_GREATER_THAN(lsn_before, p->current_lsn);
|
||||
|
||||
cleanup_hook_test_persist(p);
|
||||
printf("test_worker_hook_update_writes_wal_entry: PASS\n");
|
||||
}
|
||||
|
||||
static void test_worker_hook_dead_writes_wal_entry(void)
|
||||
{
|
||||
persist_t* p = make_hook_test_persist();
|
||||
TEST_ASSERT_NOT_NULL(p);
|
||||
|
||||
uint64_t lsn_before = p->current_lsn;
|
||||
|
||||
int rc = persist_worker_hook_fun(HOOK_AFTER_EVENT, HOOK_AFTER_EVENT,
|
||||
HOOK_DETAIL_WORKER_DEAD, p, NULL);
|
||||
TEST_ASSERT_EQUAL_INT(0, rc);
|
||||
TEST_ASSERT_GREATER_THAN(lsn_before, p->current_lsn);
|
||||
|
||||
cleanup_hook_test_persist(p);
|
||||
printf("test_worker_hook_dead_writes_wal_entry: PASS\n");
|
||||
}
|
||||
|
||||
static void test_job_hook_skips_unknown_detail(void)
|
||||
{
|
||||
persist_t* p = make_hook_test_persist();
|
||||
TEST_ASSERT_NOT_NULL(p);
|
||||
|
||||
uint64_t lsn_before = p->current_lsn;
|
||||
|
||||
/* HOOK_DETAIL_SCHEDULER_JOIN has no WAL mapping — must be silently skipped */
|
||||
int rc = persist_job_hook_fun(HOOK_AFTER_EVENT, HOOK_AFTER_EVENT,
|
||||
HOOK_DETAIL_SCHEDULER_JOIN, p, NULL);
|
||||
TEST_ASSERT_EQUAL_INT(0, rc);
|
||||
|
||||
/* LSN must NOT have advanced — no WAL entry written */
|
||||
TEST_ASSERT_EQUAL_UINT64(lsn_before, p->current_lsn);
|
||||
|
||||
cleanup_hook_test_persist(p);
|
||||
printf("test_job_hook_skips_unknown_detail: PASS\n");
|
||||
}
|
||||
|
||||
static void test_worker_hook_skips_unknown_detail(void)
|
||||
{
|
||||
persist_t* p = make_hook_test_persist();
|
||||
TEST_ASSERT_NOT_NULL(p);
|
||||
|
||||
uint64_t lsn_before = p->current_lsn;
|
||||
|
||||
int rc = persist_worker_hook_fun(HOOK_AFTER_EVENT, HOOK_AFTER_EVENT,
|
||||
HOOK_DETAIL_SCHEDULER_DEAD, p, NULL);
|
||||
TEST_ASSERT_EQUAL_INT(0, rc);
|
||||
TEST_ASSERT_EQUAL_UINT64(lsn_before, p->current_lsn);
|
||||
|
||||
cleanup_hook_test_persist(p);
|
||||
printf("test_worker_hook_skips_unknown_detail: PASS\n");
|
||||
}
|
||||
|
||||
static void test_job_hook_new_writes_multiple_entries_sequentially(void)
|
||||
{
|
||||
persist_t* p = make_hook_test_persist();
|
||||
TEST_ASSERT_NOT_NULL(p);
|
||||
|
||||
uint64_t lsn_before = p->current_lsn;
|
||||
|
||||
/* Fire three hook calls — each must advance LSN */
|
||||
persist_job_hook_fun(HOOK_AFTER_EVENT, HOOK_AFTER_EVENT,
|
||||
HOOK_DETAIL_JOB_NEW, p, NULL);
|
||||
persist_job_hook_fun(HOOK_AFTER_EVENT, HOOK_AFTER_EVENT,
|
||||
HOOK_DETAIL_JOB_START, p, NULL);
|
||||
persist_job_hook_fun(HOOK_AFTER_EVENT, HOOK_AFTER_EVENT,
|
||||
HOOK_DETAIL_JOB_EXIT, p, NULL);
|
||||
|
||||
TEST_ASSERT_EQUAL_UINT64(lsn_before + 3, p->current_lsn);
|
||||
|
||||
cleanup_hook_test_persist(p);
|
||||
printf("test_job_hook_new_writes_multiple_entries_sequentially: PASS\n");
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Suite entry point */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
void test_persist_hooks(void)
|
||||
{
|
||||
printf("\n>> persist hook function tests\n\n");
|
||||
|
||||
RUN_TEST(test_job_hook_new_writes_wal_entry);
|
||||
RUN_TEST(test_job_hook_start_writes_wal_entry);
|
||||
RUN_TEST(test_job_hook_exit_writes_wal_entry);
|
||||
RUN_TEST(test_job_hook_fail_writes_wal_entry);
|
||||
RUN_TEST(test_job_hook_stop_writes_wal_entry);
|
||||
RUN_TEST(test_job_hook_resume_writes_wal_entry);
|
||||
RUN_TEST(test_job_hook_update_writes_wal_entry);
|
||||
RUN_TEST(test_job_hook_ckpt_writes_wal_entry);
|
||||
RUN_TEST(test_job_hook_rusage_writes_wal_entry);
|
||||
RUN_TEST(test_job_hook_setpreason_writes_wal_entry);
|
||||
RUN_TEST(test_job_hook_clrpreason_writes_wal_entry);
|
||||
RUN_TEST(test_queue_hook_update_writes_wal_entry);
|
||||
RUN_TEST(test_worker_hook_join_writes_wal_entry);
|
||||
RUN_TEST(test_worker_hook_update_writes_wal_entry);
|
||||
RUN_TEST(test_worker_hook_dead_writes_wal_entry);
|
||||
RUN_TEST(test_job_hook_skips_unknown_detail);
|
||||
RUN_TEST(test_worker_hook_skips_unknown_detail);
|
||||
RUN_TEST(test_job_hook_new_writes_multiple_entries_sequentially);
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue