Splits xce_scheduler_t along the controller-I/O seam: xce_sched_core_t
(new core.h/core.c) now holds all scheduling state (jobs/queues/workers/
users/projects/resource managers/plugins/three-thread-cbuf pipeline),
while xce_scheduler_t shrinks to {core; apictx; cluster_cfg}. Proven via
an nm-based link-time isolation test (zero netio/xcecli/idgen/delaymgr
symbols reachable from core.c) plus a reproducible wall-clock-call guard.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019dw5WnN3DXStaEaPYfACU1
299 lines
12 KiB
C
299 lines
12 KiB
C
/*
|
|
* test_picker_config_fields.c - white-box tests for the picker
|
|
* configuration fields added to xce_scheduler_t, xce_sched_queue_t, and
|
|
* xce_sched_project_t (scheduler-core spec, task 1.3, Req 2 AC 1-4).
|
|
*
|
|
* RED: before task 1.3, xce_scheduler_t had no job_pick_plugin field,
|
|
* xce_sched_queue_t had no project_name/job_pick_plugin fields,
|
|
* xce_sched_project_t had no priority field, and
|
|
* sched_set_default_job_pick_plugin() did not exist -- this file failed to
|
|
* compile.
|
|
* GREEN: the fields exist with the documented types/defaults, and
|
|
* make_sched_queue/make_sched_project (schedobj.c) leave the new fields at
|
|
* their documented default values.
|
|
*
|
|
* This task is scaffolding only -- no picker-name lookup/resolution
|
|
* (plugin_register/key_find against the plugin registry) exists yet, that
|
|
* is task 2.4's job. The tasks.md "Observable" for this task is:
|
|
* "a queue config with no override inherits the global picker name;
|
|
* a queue config with an override reports that override, not the
|
|
* global default"
|
|
* Read literally that would require the lookup logic this task explicitly
|
|
* excludes, so it is exercised here at the structural level instead: for
|
|
* a NULL-override queue, the field values a resolver would read are
|
|
* exactly sched->job_pick_plugin; for an overridden queue, the queue's own
|
|
* field is a different, non-NULL string, distinct from the scheduler's
|
|
* global default -- proving the two fields are independently readable and
|
|
* correctly defaulted, without this test (or production code) performing
|
|
* the resolution itself.
|
|
*
|
|
* Strategy: pull the REAL make_sched_queue/make_sched_project/
|
|
* sched_set_default_job_pick_plugin into this TU by including schedobj.c
|
|
* (same white-box convention as test_job_update.c /
|
|
* test_job_array_expand.c / test_plugin_picker_registration.c). The
|
|
* scheduler-internal externs schedobj.c references but which are defined
|
|
* in other .c files not linked here are stubbed below -- identical stub
|
|
* set to test_job_update.c, since this task added no new external calls
|
|
* to schedobj.c (only field assignments plus one new self-contained
|
|
* function).
|
|
*/
|
|
#include <errno.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#include <unity/unity.h>
|
|
|
|
#include "kvmgr.h"
|
|
#include "scheduler.h"
|
|
#include "xstring.h"
|
|
|
|
/* Pull the real implementations under test into this TU. sched_get_user/
|
|
* sched_get_project/sched_get_queue/sched_get_job/sched_add_user/
|
|
* sched_add_project/make_sched_user/make_sched_project relocated to core.c
|
|
* (scheduler-core spec, task 4.1) -- pulling core.c in alongside schedobj.c
|
|
* (rather than re-stubbing them) is what lets this file keep exercising the
|
|
* REAL make_sched_project, per this file's own header comment. */
|
|
#include "../src/core.c"
|
|
#include "../src/schedobj.c"
|
|
|
|
/* Stubs for scheduler-internal externs schedobj.c/core.c reference but
|
|
* which are defined in other .c files not linked into this test. Not
|
|
* exercised by the tests below -- present only to satisfy the linker for
|
|
* this whole-file #include. plugin_unregister is core.c's kvmgr datakiller
|
|
* for the (never-called-in-this-test) plugins kvmgr inside
|
|
* xce_sched_core_init() -- still needs a resolvable address even though
|
|
* that code path never runs here. */
|
|
int plugin_job_update(void* s, xce_sched_job_t* j) { (void)s; (void)j; return 0; }
|
|
int plugin_queue_update(void* s, xce_sched_queue_t* q) { (void)s; (void)q; return 0; }
|
|
int plugin_job_rusage_update(void* s, xce_sched_job_t* j, xce_job_rusages_t* r)
|
|
{
|
|
(void)s; (void)j; (void)r; return 0;
|
|
}
|
|
xce_job_account_t* make_job_account(xce_job_alloc_t* a) { (void)a; return NULL; }
|
|
void free_job_account(xce_job_account_t* a) { (void)a; }
|
|
void sched_worker_release_host_ctx(xce_sched_worker_t* w) { (void)w; }
|
|
int plugin_prepare_allocation(void* s, xce_sched_ctx_t* c) { (void)s; (void)c; return 0; }
|
|
void plugin_unregister(void* arg) { (void)arg; }
|
|
|
|
void setUp(void) { return; }
|
|
void tearDown(void) { return; }
|
|
|
|
/* Per-test fixture: a zeroed scheduler, with the xstring intern table
|
|
* (needed by makeString(), which every constructor under test calls) and
|
|
* the CSE intern table live. Mirrors test_job_update.c's fixture_init --
|
|
* called once per test so each test is self-contained. */
|
|
static void fixture_init(xce_scheduler_t* sched)
|
|
{
|
|
memset(sched, 0, sizeof(*sched));
|
|
resreq_init(1);
|
|
xstring_table_init(1024 * 16);
|
|
}
|
|
|
|
static xce_queue_t make_test_raw_queue(const char* name)
|
|
{
|
|
xce_queue_t q;
|
|
memset(&q, 0, sizeof(q));
|
|
q.queue = (char*)name;
|
|
return q;
|
|
}
|
|
|
|
/* ---------------------------------------------------------------------- */
|
|
/* Req 2 AC 1: xce_scheduler_t.job_pick_plugin */
|
|
/* ---------------------------------------------------------------------- */
|
|
|
|
/* Field exists with the right type (char*) and is NULL ("unset") on a
|
|
* freshly zeroed scheduler, before any default is applied. */
|
|
static void test_scheduler_job_pick_plugin_field_is_char_star_and_unset_by_default(void)
|
|
{
|
|
xce_scheduler_t sched;
|
|
|
|
fixture_init(&sched);
|
|
TEST_ASSERT_NULL(sched.core.job_pick_plugin);
|
|
|
|
/* Type check: assigning a char* literal must compile without a cast --
|
|
* this is a compile-time proof of the declared field type. */
|
|
sched.core.job_pick_plugin = "some_name";
|
|
TEST_ASSERT_EQUAL_STRING("some_name", sched.core.job_pick_plugin);
|
|
}
|
|
|
|
/* Req 2 AC 1's literal wording: "defaulting to builtin_fcfs when unset". */
|
|
static void test_sched_set_default_job_pick_plugin_applies_builtin_fcfs_when_unset(void)
|
|
{
|
|
xce_scheduler_t sched;
|
|
|
|
fixture_init(&sched);
|
|
TEST_ASSERT_NULL(sched.core.job_pick_plugin);
|
|
|
|
sched_set_default_job_pick_plugin(&sched);
|
|
|
|
TEST_ASSERT_NOT_NULL(sched.core.job_pick_plugin);
|
|
TEST_ASSERT_EQUAL_STRING("builtin_fcfs", sched.core.job_pick_plugin);
|
|
}
|
|
|
|
/* "...when unset" implies idempotence: an already-configured name (however
|
|
* it got there) must not be clobbered by the default. */
|
|
static void
|
|
test_sched_set_default_job_pick_plugin_does_not_clobber_configured_value(void)
|
|
{
|
|
xce_scheduler_t sched;
|
|
|
|
fixture_init(&sched);
|
|
sched.core.job_pick_plugin = makeString("priority_picker");
|
|
|
|
sched_set_default_job_pick_plugin(&sched);
|
|
|
|
TEST_ASSERT_EQUAL_STRING("priority_picker", sched.core.job_pick_plugin);
|
|
}
|
|
|
|
static void test_sched_set_default_job_pick_plugin_is_null_safe(void)
|
|
{
|
|
sched_set_default_job_pick_plugin(NULL); /* must not crash */
|
|
TEST_PASS();
|
|
}
|
|
|
|
/* ---------------------------------------------------------------------- */
|
|
/* Req 2 AC 2/3: xce_sched_queue_t.job_pick_plugin / .project_name */
|
|
/* ---------------------------------------------------------------------- */
|
|
|
|
/* A queue built from real config (make_sched_queue) with no picker/project
|
|
* info in the raw config struct leaves both new fields NULL -- the
|
|
* documented "unset"/"inherit" sentinel, not garbage or an empty string. */
|
|
static void
|
|
test_make_sched_queue_defaults_project_name_and_override_to_null(void)
|
|
{
|
|
xce_scheduler_t sched;
|
|
xce_queue_t q = make_test_raw_queue("normal");
|
|
xce_sched_queue_t* queue;
|
|
|
|
fixture_init(&sched);
|
|
|
|
queue = make_sched_queue(&sched, &q);
|
|
|
|
TEST_ASSERT_NOT_NULL(queue);
|
|
TEST_ASSERT_EQUAL_STRING("normal", queue->name);
|
|
TEST_ASSERT_NULL(queue->job_pick_plugin);
|
|
TEST_ASSERT_NULL(queue->project_name);
|
|
}
|
|
|
|
/* tasks.md Observable, half 1: "a queue config with no override inherits
|
|
* the global picker name." Structural proof: the queue's own override
|
|
* field is NULL, and the value any resolver would fall back to
|
|
* (sched->job_pick_plugin) is the defaulted global name. */
|
|
static void test_queue_with_no_override_inherits_global_picker_name(void)
|
|
{
|
|
xce_scheduler_t sched;
|
|
xce_queue_t q = make_test_raw_queue("batch");
|
|
xce_sched_queue_t* queue;
|
|
|
|
fixture_init(&sched);
|
|
sched_set_default_job_pick_plugin(&sched); /* global = "builtin_fcfs" */
|
|
|
|
queue = make_sched_queue(&sched, &q);
|
|
|
|
TEST_ASSERT_NOT_NULL(queue);
|
|
TEST_ASSERT_NULL(queue->job_pick_plugin);
|
|
TEST_ASSERT_EQUAL_STRING("builtin_fcfs", sched.core.job_pick_plugin);
|
|
}
|
|
|
|
/* tasks.md Observable, half 2: "a queue config with an override reports
|
|
* that override, not the global default." Config-driven wiring of this
|
|
* field lands in a later task (xce_queue_t has no job_pick_plugin source
|
|
* yet), so the override is applied via direct struct assignment here --
|
|
* exactly the "structurally representable" test style this task calls
|
|
* for -- and then read back. */
|
|
static void
|
|
test_queue_with_override_reports_its_own_override_not_global_default(void)
|
|
{
|
|
xce_scheduler_t sched;
|
|
xce_queue_t q = make_test_raw_queue("gpu");
|
|
xce_sched_queue_t* queue;
|
|
|
|
fixture_init(&sched);
|
|
sched_set_default_job_pick_plugin(&sched); /* global = "builtin_fcfs" */
|
|
|
|
queue = make_sched_queue(&sched, &q);
|
|
TEST_ASSERT_NOT_NULL(queue);
|
|
|
|
queue->job_pick_plugin = makeString("priority_picker");
|
|
|
|
TEST_ASSERT_NOT_NULL(queue->job_pick_plugin);
|
|
TEST_ASSERT_EQUAL_STRING("priority_picker", queue->job_pick_plugin);
|
|
/* The override is reported, NOT the global default. */
|
|
TEST_ASSERT_TRUE(strcmp(queue->job_pick_plugin, sched.core.job_pick_plugin)
|
|
!= 0);
|
|
TEST_ASSERT_EQUAL_STRING("builtin_fcfs", sched.core.job_pick_plugin);
|
|
}
|
|
|
|
/* project_name is independently settable/readable (structural field check
|
|
* -- config-source wiring is a later task, same rationale as above). */
|
|
static void test_queue_project_name_is_independently_settable(void)
|
|
{
|
|
xce_scheduler_t sched;
|
|
xce_queue_t q = make_test_raw_queue("gpu");
|
|
xce_sched_queue_t* queue;
|
|
|
|
fixture_init(&sched);
|
|
queue = make_sched_queue(&sched, &q);
|
|
TEST_ASSERT_NOT_NULL(queue);
|
|
|
|
queue->project_name = makeString("acct-42");
|
|
|
|
TEST_ASSERT_EQUAL_STRING("acct-42", queue->project_name);
|
|
/* Setting project_name must not disturb the independent picker-override
|
|
* field (still inheriting -- NULL). */
|
|
TEST_ASSERT_NULL(queue->job_pick_plugin);
|
|
}
|
|
|
|
/* ---------------------------------------------------------------------- */
|
|
/* Req 2 AC: xce_sched_project_t.priority (scaffolding) */
|
|
/* ---------------------------------------------------------------------- */
|
|
|
|
static void test_make_sched_project_priority_field_defaults_to_zero(void)
|
|
{
|
|
xce_scheduler_t sched;
|
|
xce_sched_project_t* project;
|
|
|
|
fixture_init(&sched);
|
|
|
|
project = make_sched_project(&sched.core, "acct-42");
|
|
|
|
TEST_ASSERT_NOT_NULL(project);
|
|
TEST_ASSERT_EQUAL_STRING("acct-42", project->name);
|
|
/* Scaffolding only -- no quota/hierarchy logic exists yet
|
|
* (project-resource-ownership spec). */
|
|
TEST_ASSERT_EQUAL_INT32(0, project->priority);
|
|
}
|
|
|
|
/* Type check: field accepts a plain int32_t assignment/comparison --
|
|
* compile-time proof of the declared type. */
|
|
static void test_project_priority_field_is_int32_and_independently_settable(void)
|
|
{
|
|
xce_scheduler_t sched;
|
|
xce_sched_project_t* project;
|
|
|
|
fixture_init(&sched);
|
|
|
|
project = make_sched_project(&sched.core, "acct-7");
|
|
project->priority = 7;
|
|
|
|
TEST_ASSERT_EQUAL_INT32(7, project->priority);
|
|
/* Setting priority must not disturb the project's name. */
|
|
TEST_ASSERT_EQUAL_STRING("acct-7", project->name);
|
|
}
|
|
|
|
int main(void)
|
|
{
|
|
UNITY_BEGIN();
|
|
RUN_TEST(test_scheduler_job_pick_plugin_field_is_char_star_and_unset_by_default);
|
|
RUN_TEST(test_sched_set_default_job_pick_plugin_applies_builtin_fcfs_when_unset);
|
|
RUN_TEST(test_sched_set_default_job_pick_plugin_does_not_clobber_configured_value);
|
|
RUN_TEST(test_sched_set_default_job_pick_plugin_is_null_safe);
|
|
RUN_TEST(test_make_sched_queue_defaults_project_name_and_override_to_null);
|
|
RUN_TEST(test_queue_with_no_override_inherits_global_picker_name);
|
|
RUN_TEST(test_queue_with_override_reports_its_own_override_not_global_default);
|
|
RUN_TEST(test_queue_project_name_is_independently_settable);
|
|
RUN_TEST(test_make_sched_project_priority_field_defaults_to_zero);
|
|
RUN_TEST(test_project_priority_field_is_int32_and_independently_settable);
|
|
return UNITY_END();
|
|
}
|