/* * test_plugin_picker_registration.c - white-box tests for the picker * plugin registration category and job_elect_fn compatibility wrapper * added to plugin.c (scheduler-core spec, task 1.2). * * RED: before task 1.2's plugin.h/plugin.c changes, xce_sched_plugin_t had * no picker_iface field and legacy_picker_wrapper_iface did not exist -- * this file failed to compile. * GREEN: plugin_register() adapts an old-style, job_elect_fn-only plugin * to xce_job_picker_iface_t via a compatibility wrapper and logs a * deprecation warning exactly once, at registration time (Req 5 * "Migration from old job_elect_fn"); a plugin that already provides * picker_iface registers as-is, with no warning; a plugin that provides * neither (today's allocator/limit categories) is unaffected. * * #include's the real plugin.c (white-box, same convention as * test_handlers.c / test_job_array_expand.c / test_job_array_wire_e2e.c) * so this test exercises the real plugin_register() and the real * legacy_picker_wrapper_iface -- not a re-implementation of either. */ #include #include #include #include #include #include #include #include #include "kvmgr.h" #include "scheduler.h" /* plugin.c's plugin_prepare_allocation() (unrelated to the picker * registration category under test here) calls sched_fill_limit_requests, * defined in scheduler.c -- not pulled into this white-box TU. Stubbed the * same way test_handlers.c stubs sched_init_config/sched_get_job: this * test never exercises plugin_prepare_allocation, so a no-op body is * sufficient to satisfy the linker. */ int sched_fill_limit_requests(xce_scheduler_t* sched, xvec_idval_t* req, xlist_t* limits) { (void)sched; (void)req; (void)limits; return 0; } #include "../src/plugin.c" void setUp(void) { return; } void tearDown(void) { return; } /* ---------------------------------------------------------------------- */ /* Early-print log capture: same pattern as test_job_array_wire_e2e.c's * wire_e2e_early_print_cb. No test binary in this suite calls tlog_init(), * so WARN() (tlog_warn -> tlog(TLOG_WARN, ...)) routes through tlog's * pre-init "early print" fallback, which tlog_reg_early_printf_callback() * lets a test intercept synchronously on the calling thread -- no * tlog_init()/tlog_exit() lifecycle, so it cannot disturb any other test * sharing this process. * ---------------------------------------------------------------------- */ static int g_warn_count = 0; static void picker_reg_early_print_cb(struct tlog_loginfo* loginfo, const char* format, va_list ap) { char buf[512]; va_list ap_copy; if (loginfo == NULL || loginfo->level != TLOG_WARN) { return; } va_copy(ap_copy, ap); vsnprintf(buf, sizeof(buf), format, ap_copy); va_end(ap_copy); if (strstr(buf, "job_elect_fn") != NULL) { g_warn_count++; } } static void start_log_capture(void) { g_warn_count = 0; tlog_set_early_printf(1, 1, 0); tlog_reg_early_printf_callback(picker_reg_early_print_cb); } static void stop_log_capture(void) { tlog_reg_early_printf_callback(NULL); } /* ---------------------------------------------------------------------- */ /* Fixture: a real sched->core.plugins kvmgr -- same setup as scheduler.c's * (static, so not reusable) sched_plugin_init(). Each test gets its own * fresh instance; nothing is torn down (suite convention, see meson.build: * ASAN_OPTIONS=detect_leaks=0). */ /* ---------------------------------------------------------------------- */ static void fixture_init(xce_scheduler_t* sched) { kvmgr_params_t params = { .key_type = KVMGR_KEY_STRING, .nslots = 16, .delta = 8, .datakiller = plugin_unregister }; memset(sched, 0, sizeof(*sched)); sched->core.plugins = makeInstance(&xkvmgr_type, ¶ms); TEST_ASSERT_NOT_NULL(sched->core.plugins); } /* ---------------------------------------------------------------------- */ /* Old-style plugin fixture: only job_elect_fn is set, no native * picker_iface -- the same shape as today's plugins/fcfs.c (out of scope * for this task; this is a minimal standalone stand-in so the test does * not depend on fcfs.c's real threading behavior). */ /* ---------------------------------------------------------------------- */ static xce_sched_job_t g_legacy_elected_job; static int g_legacy_elect_calls = 0; static void* legacy_job_elect_fn(void* arg) { TEST_ASSERT_NOT_NULL(arg); g_legacy_elect_calls++; return (void*)&g_legacy_elected_job; } static void legacy_plugin_init(xce_sched_plugin_t* plugin, char* name) { memset(plugin, 0, sizeof(*plugin)); plugin->name = name; plugin->job_elect_fn = (plugin_job_election_fn_t)legacy_job_elect_fn; } /* Req 5 "Migration from old job_elect_fn": an old-style, job_elect_fn-only * plugin registers successfully via the compatibility wrapper and logs a * deprecation warning exactly once, at registration time. */ static void test_register_legacy_job_elect_fn_only_plugin_uses_wrapper_and_warns(void) { xce_scheduler_t sched; xce_sched_plugin_t plugin; int rc; fixture_init(&sched); legacy_plugin_init(&plugin, "test_legacy_picker"); start_log_capture(); rc = plugin_register(&sched, &plugin); stop_log_capture(); TEST_ASSERT_GREATER_OR_EQUAL(0, rc); TEST_ASSERT_EQUAL_INT(1, g_warn_count); TEST_ASSERT_NOT_NULL(plugin.picker_iface); TEST_ASSERT_EQUAL_PTR(&legacy_picker_wrapper_iface, plugin.picker_iface); } /* The wrapper must actually work: next_job() genuinely forwards to the old * job_elect_fn, it does not merely register without crashing. */ static void test_legacy_wrapper_next_job_calls_job_elect_fn(void) { xce_scheduler_t sched; xce_sched_plugin_t plugin; xce_sched_job_t* got; fixture_init(&sched); legacy_plugin_init(&plugin, "test_legacy_picker_2"); g_legacy_elect_calls = 0; TEST_ASSERT_TRUE(plugin_register(&sched, &plugin) >= 0); TEST_ASSERT_NOT_NULL(plugin.picker_iface); TEST_ASSERT_NOT_NULL(plugin.picker_iface->next_job); got = plugin.picker_iface->next_job(&plugin); TEST_ASSERT_EQUAL_INT(1, g_legacy_elect_calls); TEST_ASSERT_EQUAL_PTR(&g_legacy_elected_job, got); } /* Req 1 AC 2 / Req 5: a plugin that already implements * xce_job_picker_iface_t natively registers as-is -- no wrapper * substitution, no deprecation warning. */ static xce_sched_job_t* new_style_next_job_stub(void* picker) { (void)picker; return NULL; } static xce_job_picker_iface_t g_new_style_iface = { .next_job = new_style_next_job_stub, }; static void test_register_new_style_picker_plugin_skips_wrapper_and_does_not_warn(void) { xce_scheduler_t sched; xce_sched_plugin_t plugin; int rc; fixture_init(&sched); memset(&plugin, 0, sizeof(plugin)); plugin.name = "test_new_style_picker"; plugin.picker_iface = &g_new_style_iface; start_log_capture(); rc = plugin_register(&sched, &plugin); stop_log_capture(); TEST_ASSERT_GREATER_OR_EQUAL(0, rc); TEST_ASSERT_EQUAL_INT(0, g_warn_count); TEST_ASSERT_EQUAL_PTR(&g_new_style_iface, plugin.picker_iface); } /* Regression guard: a plugin that is neither category (no job_elect_fn, no * picker_iface -- today's allocator/limit plugins) registers unaffected -- * no wrapper substitution, no warning, picker_iface stays NULL. */ static void test_register_non_picker_plugin_is_unaffected(void) { xce_scheduler_t sched; xce_sched_plugin_t plugin; int rc; fixture_init(&sched); memset(&plugin, 0, sizeof(plugin)); plugin.name = "test_non_picker_plugin"; start_log_capture(); rc = plugin_register(&sched, &plugin); stop_log_capture(); TEST_ASSERT_GREATER_OR_EQUAL(0, rc); TEST_ASSERT_EQUAL_INT(0, g_warn_count); TEST_ASSERT_NULL(plugin.picker_iface); } int main(void) { UNITY_BEGIN(); RUN_TEST( test_register_legacy_job_elect_fn_only_plugin_uses_wrapper_and_warns); RUN_TEST(test_legacy_wrapper_next_job_calls_job_elect_fn); RUN_TEST( test_register_new_style_picker_plugin_skips_wrapper_and_does_not_warn); RUN_TEST(test_register_non_picker_plugin_is_unaffected); return UNITY_END(); }