test(scheduler-controller-msg-gap): real handler/applier unit tests (bug-765 G-6)

The Task 9 tests were marked [x] but were stub/absent. Convert the
correctness-critical ones to real white-box tests, registered as meson tests
under --suite scheduler (leaks-off per convention):

- test_handlers.c (new) — #include ../src/handler.c; covers 9.2/9.3: each
  notify/dataset handler enqueues the correct op / skips NULL-empty / -EINVAL.
  Asserts the 5.6 fix (update_job -> UPDATE_JOB_FIELDS, not bulk UPDATE_JOB).
- test_job_update.c — add 9.5 (real sched_update_existing_queue, replacing the
  former local stub); already covers 9.4 (job) and 9.7 (rusage).
- meson.build — build + register all three scheduler test executables.

Suite GREEN: sched_test 15/15, sched_job_update_test 10/10,
sched_handlers_test 6/6; ASAN-clean.

Honest remaining: 9.6 dequeue worker-resvals APPLY is PARTIAL (enqueue tested;
apply is a dormant path, R-3); 9.8 is a documented compile-time absence check.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Rongsong Shen 2026-06-19 04:14:20 +00:00
parent 080ff78a80
commit 276e26c37b
4 changed files with 270 additions and 7 deletions

View file

@ -20,3 +20,21 @@ sched_job_update_test = executable('sched_job_update_test',
include_directories:[incdir],
dependencies: [scheduler_test_deps],
install: false)
# White-box test for the notify/dataset enqueue handlers. It #includes
# ../src/handler.c directly (enq_pend_update + the static handlers), so it is a
# standalone TU with its own main() — built as a separate executable.
sched_handlers_test = executable('sched_handlers_test',
['test_handlers.c'],
include_directories:[incdir],
dependencies: [scheduler_test_deps],
install: false)
# Register as meson tests so `meson test --suite scheduler` runs them all.
# detect_leaks=0 matches the suite convention (fixtures are not torn down).
test('scheduler test', scheduler_test, suite: 'scheduler',
env: ['ASAN_OPTIONS=detect_leaks=0'])
test('scheduler job-update test', sched_job_update_test, suite: 'scheduler',
env: ['ASAN_OPTIONS=detect_leaks=0'])
test('scheduler handlers test', sched_handlers_test, suite: 'scheduler',
env: ['ASAN_OPTIONS=detect_leaks=0'])

View file

@ -0,0 +1,190 @@
/*
* test_handlers.c - white-box tests for the scheduler notify/dataset enqueue
* handlers (bug-765 G-6, tasks 9.2 + 9.3). Pulls the real (static) handlers in
* handler.c into this TU; enq_pend_update is local to handler.c, so we drive a
* real sched->pend_updates dataq and assert the correct op was enqueued (or
* skipped / -EINVAL) per each handler's contract.
*/
#define UNIT_TEST 1
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unity/unity.h>
#include "dataq.h"
#include "mylock.h"
#include "scheduler.h"
/* Pull the real handlers under test into this TU. handler.c is self-contained
* for the enqueue path: enq_pend_update and sched_job_state_change are both
* static within it; the dispatchers call lib helpers (linked). The only
* scheduler-internal extern handler.c references (in the config-init request
* path, not exercised by these enqueue tests) is sched_init_config — stubbed. */
#include "../src/handler.c"
int sched_init_config(xce_scheduler_t* s, xce_conf4sched_t* c)
{
(void)s; (void)c; return 0;
}
void setUp(void) { return; }
void tearDown(void)
{
printf("\n-----------------------------------------------------\n");
return;
}
static void fixture_init(xce_scheduler_t* sched)
{
memset(sched, 0, sizeof(*sched));
mylock_init(&(sched->lock));
sched->pend_updates[0] = (dataq_t*)calloc(1, sizeof(dataq_t));
dataq_init(sched->pend_updates[0], NULL);
sched->pend_updates[1] = (dataq_t*)calloc(1, sizeof(dataq_t));
dataq_init(sched->pend_updates[1], NULL);
}
/* Dequeue one pending request (NULL if the queue is empty) and return its op
* via *op_out; frees the request shell (payloads are test-owned/leaked under
* the suite's leaks-off convention). Returns 1 if an item was present. */
static int pop_op(xce_scheduler_t* sched, xce_sched_op_t* op_out)
{
data_item_t* it = dataq_deq(sched->pend_updates[0], NULL);
xce_sched_update_request_t* req;
if (it == NULL) {
return 0;
}
req = ITPTR_TO_UPDATE_REQ(it);
if (op_out != NULL) {
*op_out = req->op;
}
free(req);
return 1;
}
/* ── 9.3: notify single-item handlers (NULL -> -EINVAL, valid -> enqueue) ── */
static void test_handle_update_job_enqueues_job_fields(void)
{
xce_scheduler_t sched;
xce_sched_op_t op = (xce_sched_op_t)-1;
fixture_init(&sched);
TEST_ASSERT_EQUAL_INT(-EINVAL, sched_handle_sched_update_job(&sched, NULL));
TEST_ASSERT_EQUAL_INT(0, pop_op(&sched, NULL)); /* NULL enqueued nothing */
xce_job_t* job = (xce_job_t*)calloc(1, sizeof(*job));
job->jobid = 42;
TEST_ASSERT_EQUAL_INT(0, sched_handle_sched_update_job(&sched, job));
TEST_ASSERT_EQUAL_INT(1, pop_op(&sched, &op));
/* 5.6: notify job update takes the in-place UPDATE_JOB_FIELDS path, NOT
* the dataset bulk-create UPDATE_JOB path. */
TEST_ASSERT_EQUAL_INT(XCE_SCHED_OP_UPDATE_JOB_FIELDS, op);
free(job);
}
static void test_handle_update_queue_enqueues_queue(void)
{
xce_scheduler_t sched;
xce_sched_op_t op = (xce_sched_op_t)-1;
fixture_init(&sched);
TEST_ASSERT_EQUAL_INT(-EINVAL,
sched_handle_sched_update_queue(&sched, NULL));
xce_queue_t* q = (xce_queue_t*)calloc(1, sizeof(*q));
TEST_ASSERT_EQUAL_INT(0, sched_handle_sched_update_queue(&sched, q));
TEST_ASSERT_EQUAL_INT(1, pop_op(&sched, &op));
TEST_ASSERT_EQUAL_INT(XCE_SCHED_OP_UPDATE_QUEUE, op);
free(q);
}
static void test_handle_job_rusage_enqueues_rusage(void)
{
xce_scheduler_t sched;
xce_sched_op_t op = (xce_sched_op_t)-1;
fixture_init(&sched);
TEST_ASSERT_EQUAL_INT(-EINVAL,
sched_handle_sched_job_rusage(&sched, NULL));
xce_job_rusages_t* ru = (xce_job_rusages_t*)calloc(1, sizeof(*ru));
ru->jobid = 7;
TEST_ASSERT_EQUAL_INT(0, sched_handle_sched_job_rusage(&sched, ru));
TEST_ASSERT_EQUAL_INT(1, pop_op(&sched, &op));
TEST_ASSERT_EQUAL_INT(XCE_SCHED_OP_JOB_RUSAGE, op);
free(ru);
}
static void test_handle_update_resvals_skips_empty_enqueues_nonempty(void)
{
xce_scheduler_t sched;
xce_sched_op_t op = (xce_sched_op_t)-1;
xce_resval_list_t empty = { .count = 0, .vals = NULL };
xce_resval_list_t full = { .count = 1, .vals = NULL };
fixture_init(&sched);
/* NULL or count==0 -> skip (return 0, nothing enqueued) */
TEST_ASSERT_EQUAL_INT(0, sched_handle_sched_update_resvals(&sched, NULL));
TEST_ASSERT_EQUAL_INT(0,
sched_handle_sched_update_resvals(&sched, &empty));
TEST_ASSERT_EQUAL_INT(0, pop_op(&sched, NULL));
/* count>0 -> enqueue UPDATE_RESVALS */
TEST_ASSERT_EQUAL_INT(0,
sched_handle_sched_update_resvals(&sched, &full));
TEST_ASSERT_EQUAL_INT(1, pop_op(&sched, &op));
TEST_ASSERT_EQUAL_INT(XCE_SCHED_OP_UPDATE_RESVALS, op);
}
/* ── 9.2: dataset handlers (skip on NULL/empty, enqueue on valid) ── */
static void test_handle_resources_skips_empty_enqueues_nonempty(void)
{
xce_scheduler_t sched;
xce_sched_op_t op = (xce_sched_op_t)-1;
xce_resource_t* one[1];
fixture_init(&sched);
TEST_ASSERT_EQUAL_INT(0, sched_handle_resources(&sched, 0, NULL));
TEST_ASSERT_EQUAL_INT(0, pop_op(&sched, NULL));
one[0] = (xce_resource_t*)calloc(1, sizeof(xce_resource_t));
TEST_ASSERT_EQUAL_INT(0, sched_handle_resources(&sched, 1, one));
TEST_ASSERT_EQUAL_INT(1, pop_op(&sched, &op));
TEST_ASSERT_EQUAL_INT(XCE_SCHED_OP_UPDATE_RESOURCES, op);
free(one[0]);
}
static void test_handle_resvals_full_skips_empty_enqueues_nonempty(void)
{
xce_scheduler_t sched;
xce_sched_op_t op = (xce_sched_op_t)-1;
xce_resval_t* one[1];
fixture_init(&sched);
TEST_ASSERT_EQUAL_INT(0, sched_handle_resvals(&sched, 0, NULL));
TEST_ASSERT_EQUAL_INT(0, pop_op(&sched, NULL));
one[0] = (xce_resval_t*)calloc(1, sizeof(xce_resval_t));
TEST_ASSERT_EQUAL_INT(0, sched_handle_resvals(&sched, 1, one));
TEST_ASSERT_EQUAL_INT(1, pop_op(&sched, &op));
TEST_ASSERT_EQUAL_INT(XCE_SCHED_OP_UPDATE_RESVALS_FULL, op);
free(one[0]);
}
int main(int argc, char* argv[])
{
(void)argc; (void)argv;
UNITY_BEGIN();
RUN_TEST(test_handle_update_job_enqueues_job_fields, 0);
RUN_TEST(test_handle_update_queue_enqueues_queue, 0);
RUN_TEST(test_handle_job_rusage_enqueues_rusage, 0);
RUN_TEST(test_handle_update_resvals_skips_empty_enqueues_nonempty, 0);
RUN_TEST(test_handle_resources_skips_empty_enqueues_nonempty, 0);
RUN_TEST(test_handle_resvals_full_skips_empty_enqueues_nonempty, 0);
return UNITY_END();
}

View file

@ -295,6 +295,57 @@ test_rusage_null_returns_einval(void)
TEST_ASSERT_EQUAL_INT(-EINVAL, sched_apply_job_rusage(&sched, NULL));
}
/* Queue in-place update (Req 4 / task 9.5 — REAL, exercises the linked
* schedobj.c sched_update_existing_queue, not a local stub) */
static void
test_update_existing_queue_real(void)
{
xce_scheduler_t sched;
fixture_init(&sched);
xce_sched_queue_t* q = make_test_queue("q1", 1);
xce_queue_t upd;
memset(&upd, 0, sizeof(upd));
upd.queue = makeString("q1");
upd.status = 2;
upd.priority = 9;
upd.accept_interval = 5;
upd.sched_delay = 3;
upd.resreq = NULL; /* skip the resreq makeString branch */
int rc = sched_update_existing_queue(&sched, &upd);
TEST_ASSERT_EQUAL_INT(0, rc);
TEST_ASSERT_EQUAL_UINT(2, q->status);
TEST_ASSERT_EQUAL_INT(9, q->priority);
TEST_ASSERT_EQUAL_INT(5, q->accept_interval);
TEST_ASSERT_EQUAL_INT(3, q->sched_delay);
}
static void
test_update_unknown_queue_returns_enoent(void)
{
xce_scheduler_t sched;
fixture_init(&sched);
xce_queue_t upd;
memset(&upd, 0, sizeof(upd));
upd.queue = makeString("nope"); /* no such queue seeded */
TEST_ASSERT_EQUAL_INT(-ENOENT, sched_update_existing_queue(&sched, &upd));
}
static void
test_update_queue_null_returns_einval(void)
{
xce_scheduler_t sched;
fixture_init(&sched);
TEST_ASSERT_EQUAL_INT(-EINVAL, sched_update_existing_queue(&sched, NULL));
}
int main(int argc, char* argv[])
{
(void)argc; (void)argv;
@ -306,5 +357,8 @@ int main(int argc, char* argv[])
RUN_TEST(test_rusage_replace_frees_prior, 0);
RUN_TEST(test_rusage_unknown_job_returns_enoent, 0);
RUN_TEST(test_rusage_null_returns_einval, 0);
RUN_TEST(test_update_existing_queue_real, 0);
RUN_TEST(test_update_unknown_queue_returns_enoent, 0);
RUN_TEST(test_update_queue_null_returns_einval, 0);
return UNITY_END();
}