#include #include #include #include "hook.h" #include /* Shared call counter for hook tests */ static int s_call_count = 0; static int counting_fn(int type, int sit, int det, void* data, va_list ap) { (void)type; (void)sit; (void)det; (void)ap; (*(int*)data)++; return 0; } /* --- test cases --- */ static void test_disable_hook_stops_invocation(void) { hooksys_t hs; int rc; s_call_count = 0; rc = xce_hook_init(&hs, 2 /* maxtypes */); TEST_ASSERT_EQUAL_INT(0, rc); rc = xce_add_hook(&hs, HOOK_AFTER_EVENT, counting_fn, &s_call_count); TEST_ASSERT_EQUAL_INT(0, rc); /* Before disable: hook fires once */ xce_run_hooks(&hs, HOOK_AFTER_EVENT, 0, 0); TEST_ASSERT_EQUAL_INT(1, s_call_count); /* Disable the hook */ rc = xce_disable_hook(&hs, HOOK_AFTER_EVENT, counting_fn); TEST_ASSERT_EQUAL_INT(0, rc); /* After disable: hook must not fire */ xce_run_hooks(&hs, HOOK_AFTER_EVENT, 0, 0); TEST_ASSERT_EQUAL_INT(1, s_call_count); /* unchanged */ xce_hook_fini(&hs); printf("test_disable_hook_stops_invocation: PASS\n"); } static void test_disable_hook_returns_enoent_if_not_found(void) { hooksys_t hs; int rc; rc = xce_hook_init(&hs, 2); TEST_ASSERT_EQUAL_INT(0, rc); /* Disabling a hook never added must return non-zero (ENOENT) */ rc = xce_disable_hook(&hs, HOOK_AFTER_EVENT, counting_fn); TEST_ASSERT_NOT_EQUAL(0, rc); xce_hook_fini(&hs); printf("test_disable_hook_returns_enoent_if_not_found: PASS\n"); } static void test_add_hook_reuses_null_slot(void) { hooksys_t hs; int rc; int count_before_reuse; s_call_count = 0; rc = xce_hook_init(&hs, 2); TEST_ASSERT_EQUAL_INT(0, rc); rc = xce_add_hook(&hs, HOOK_AFTER_EVENT, counting_fn, &s_call_count); TEST_ASSERT_EQUAL_INT(0, rc); /* Record list length after first add */ count_before_reuse = hs.hooks[HOOK_AFTER_EVENT].count; TEST_ASSERT_EQUAL_INT(1, count_before_reuse); /* Disable the slot — fn becomes NULL atomically */ rc = xce_disable_hook(&hs, HOOK_AFTER_EVENT, counting_fn); TEST_ASSERT_EQUAL_INT(0, rc); /* Re-add the same fn — must reuse the NULL slot, not grow the list */ rc = xce_add_hook(&hs, HOOK_AFTER_EVENT, counting_fn, &s_call_count); TEST_ASSERT_EQUAL_INT(0, rc); TEST_ASSERT_EQUAL_INT(count_before_reuse, hs.hooks[HOOK_AFTER_EVENT].count); /* Fire: must get exactly 1 call (not 2 if slot was duplicated) */ xce_run_hooks(&hs, HOOK_AFTER_EVENT, 0, 0); TEST_ASSERT_EQUAL_INT(1, s_call_count); xce_hook_fini(&hs); printf("test_add_hook_reuses_null_slot: PASS\n"); } /* bug-845: re-registering the SAME (fn,data) on a type must be idempotent. * xce_disable_hook removes only ONE matching slot and xce_run_hooks fires ALL * slots, so a duplicate (fn,data) registration would (a) fire twice and (b) * survive a single disable. This is exactly how the controller double-appended * every WAL_JOB_SUBMIT: persist_start() and ha_register_persist_hooks() both * register persist_job_hook_fun with the same persist_t closure. */ static void test_add_hook_dedups_same_fn_and_data(void) { hooksys_t hs; int a = 0; TEST_ASSERT_EQUAL_INT(0, xce_hook_init(&hs, 2)); /* First add registers; the second add with the SAME fn AND data must be a * no-op (still rc=0, still exactly one slot). */ TEST_ASSERT_EQUAL_INT(0, xce_add_hook(&hs, HOOK_AFTER_EVENT, counting_fn, &a)); TEST_ASSERT_EQUAL_INT(0, xce_add_hook(&hs, HOOK_AFTER_EVENT, counting_fn, &a)); TEST_ASSERT_EQUAL_INT(1, hs.hooks[HOOK_AFTER_EVENT].count); /* Fires ONCE per event, never twice. */ xce_run_hooks(&hs, HOOK_AFTER_EVENT, 0, 0); TEST_ASSERT_EQUAL_INT(1, a); /* A single disable fully removes it — no duplicate slot lingers. */ TEST_ASSERT_EQUAL_INT(0, xce_disable_hook(&hs, HOOK_AFTER_EVENT, counting_fn)); xce_run_hooks(&hs, HOOK_AFTER_EVENT, 0, 0); TEST_ASSERT_EQUAL_INT(1, a); /* unchanged */ xce_hook_fini(&hs); printf("test_add_hook_dedups_same_fn_and_data: PASS\n"); } /* The dedup is keyed on (fn,data): the SAME fn with DISTINCT closures is a * legitimate pair of registrations and BOTH must fire. */ static void test_add_hook_allows_same_fn_distinct_data(void) { hooksys_t hs; int a = 0, b = 0; TEST_ASSERT_EQUAL_INT(0, xce_hook_init(&hs, 2)); TEST_ASSERT_EQUAL_INT(0, xce_add_hook(&hs, HOOK_AFTER_EVENT, counting_fn, &a)); TEST_ASSERT_EQUAL_INT(0, xce_add_hook(&hs, HOOK_AFTER_EVENT, counting_fn, &b)); TEST_ASSERT_EQUAL_INT(2, hs.hooks[HOOK_AFTER_EVENT].count); xce_run_hooks(&hs, HOOK_AFTER_EVENT, 0, 0); TEST_ASSERT_EQUAL_INT(1, a); TEST_ASSERT_EQUAL_INT(1, b); xce_hook_fini(&hs); printf("test_add_hook_allows_same_fn_distinct_data: PASS\n"); } /* --- suite entry point --- */ void test_hooks(void) { printf("\n>> hook system tests\n\n"); RUN_TEST(test_disable_hook_stops_invocation); RUN_TEST(test_disable_hook_returns_enoent_if_not_found); RUN_TEST(test_add_hook_reuses_null_slot); RUN_TEST(test_add_hook_dedups_same_fn_and_data); RUN_TEST(test_add_hook_allows_same_fn_distinct_data); }