test(scheduler-snapshot-job-allocation): backward-compat for allocation-less snapshots
Task 3.2: adds test_snapshot_load_leaves_running_job_without_allocation_at_zero_resinuse, covering the specific Req 3.5 scenario the existing PENDING-job test didn't -- a RUNNING job whose allocation was never captured (simulating a pre-feature-saved snapshot). With a real worker/resource present (making the assertion non-vacuous), confirms: load succeeds with no error, job->schedobj is still valid (task 1.2, independent of allocation data), job->allocs/accounts stay NULL, and resInUse correctly reads 0.0 via the real accounting chain -- a clean degrade, no special-casing, matching today's pre-feature behavior exactly. Test-only task; no production code changed. Per this spec's own "pre-release product" scope note, no archived legacy snapshot file is needed as a fixture -- a freshly-built wire job with allocs unset exercises the identical code path. Requirements: 3.5
This commit is contained in:
parent
7481cedb3a
commit
f0fbebdeab
2 changed files with 164 additions and 1 deletions
|
|
@ -45,7 +45,7 @@
|
||||||
- _Requirements: 1.1, 1.2, 1.3, 1.4, 2.1, 2.3, 2.4_
|
- _Requirements: 1.1, 1.2, 1.3, 1.4, 2.1, 2.3, 2.4_
|
||||||
- _Depends: 1.2, 2.1, 2.2_
|
- _Depends: 1.2, 2.1, 2.2_
|
||||||
|
|
||||||
- [ ] 3.2 Verify a snapshot with no allocation data loads cleanly
|
- [x] 3.2 Verify a snapshot with no allocation data loads cleanly
|
||||||
- Extend the round-trip coverage with a saved state containing no allocation data for its jobs (the natural case for a job that was never dispatched, or a save taken before this feature existed).
|
- Extend the round-trip coverage with a saved state containing no allocation data for its jobs (the natural case for a job that was never dispatched, or a save taken before this feature existed).
|
||||||
- Assert the load succeeds with no error, and every such job's resource contribution reads as zero/unavailable, identical to today's behavior.
|
- Assert the load succeeds with no error, and every such job's resource contribution reads as zero/unavailable, identical to today's behavior.
|
||||||
- Observable completion: the backward-compatibility test passes without any special-casing visible to the caller.
|
- Observable completion: the backward-compatibility test passes without any special-casing visible to the caller.
|
||||||
|
|
|
||||||
|
|
@ -2124,6 +2124,167 @@ test_snapshot_round_trip_mixed_jobs_shared_pool_resinuse_and_teardown_independen
|
||||||
TEST_ASSERT_EQUAL_FLOAT(1.0f, restored_shared_lic);
|
TEST_ASSERT_EQUAL_FLOAT(1.0f, restored_shared_lic);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ---------------------------------------------------------------------- */
|
||||||
|
/* scheduler-snapshot-job-allocation task 3.2 (Req 3.5): the backward- */
|
||||||
|
/* compatibility case made explicit for the scenario Req 3.5's own text and */
|
||||||
|
/* the original bug report actually describe -- a job that was ALREADY */
|
||||||
|
/* RUNNING as of a PRE-feature build's snapshot save point, so its */
|
||||||
|
/* dispatch-time allocation was never captured onto the wire at all. This */
|
||||||
|
/* is distinct from task 2.2's own load-side companion test above */
|
||||||
|
/* (test_snapshot_load_leaves_allocs_and_accounts_null_for_job_without_ */
|
||||||
|
/* allocation), which only covers a PENDING job -- the "never dispatched" */
|
||||||
|
/* case, not the "dispatched before a pre-feature build could capture it" */
|
||||||
|
/* case Req 3.5 is actually about. */
|
||||||
|
/* */
|
||||||
|
/* Since the wire job's `allocs` field is a plain optional pointer, not a */
|
||||||
|
/* schema-versioned/gated blob (brief.md's "Backward compatibility note */
|
||||||
|
/* (pre-release product)"), a pre-feature build's snapshot and "this */
|
||||||
|
/* RUNNING job's allocs happens to be NULL today" are the literal same wire */
|
||||||
|
/* shape -- so this test builds the wire job directly with `allocs == NULL` */
|
||||||
|
/* and `state == JOB_STAT_RUN`, exactly what a pre-feature build would have */
|
||||||
|
/* produced for a job that was running when it saved. */
|
||||||
|
/* */
|
||||||
|
/* Asserts, all against a fresh core with NO special-casing visible to the */
|
||||||
|
/* caller: */
|
||||||
|
/* (a) load succeeds (rc == 0); */
|
||||||
|
/* (b) job->state correctly restores as RUNNING -- state round-trips */
|
||||||
|
/* independently of allocs, this feature does not touch it; */
|
||||||
|
/* (c) job->schedobj is non-NULL (task 1.2's fix -- independent of */
|
||||||
|
/* allocation data, so a RUNNING job from a legacy snapshot is just */
|
||||||
|
/* as safe to hand to the allocator/picker as any other restored */
|
||||||
|
/* job); */
|
||||||
|
/* (d) job->allocs and job->accounts are both NULL -- clean degrade, not */
|
||||||
|
/* a fabricated allocation or a crash; */
|
||||||
|
/* (e) with a REAL worker present, the REAL sched_account_jobs() chain */
|
||||||
|
/* (test_sched_account_jobs() above, task 3.1's own real */
|
||||||
|
/* reimplementation) correctly contributes ZERO from this job to the */
|
||||||
|
/* worker's resInUse -- the concrete "used=0, identical to today's */
|
||||||
|
/* behavior" claim Req 3.5 and the originally-reported "info worker" */
|
||||||
|
/* symptom are both actually about, not merely that job->accounts is */
|
||||||
|
/* NULL in isolation. */
|
||||||
|
/* ---------------------------------------------------------------------- */
|
||||||
|
|
||||||
|
static void
|
||||||
|
test_snapshot_load_leaves_running_job_without_allocation_at_zero_resinuse(void)
|
||||||
|
{
|
||||||
|
xce_sched_core_t core;
|
||||||
|
sched_snapshot_data_t in;
|
||||||
|
xce_jobs_data_t jobs_data;
|
||||||
|
xce_resource_data_t resources_data;
|
||||||
|
xce_resource_t res_slots;
|
||||||
|
xce_resource_t* resources_arr[1];
|
||||||
|
xce_workers_data_t workers_data;
|
||||||
|
xce_worker_t wire_worker;
|
||||||
|
xce_worker_t* workers_arr[1];
|
||||||
|
xce_val_t worker_slots_val;
|
||||||
|
xce_name_val_t worker_slots_nv;
|
||||||
|
xce_name_val_t* worker_resvals[1];
|
||||||
|
xce_queue_t wire_queue;
|
||||||
|
xce_queue_t* queues[1];
|
||||||
|
xce_job_t* wire_job;
|
||||||
|
xce_job_t* jobs[1];
|
||||||
|
xce_sched_job_t* restored;
|
||||||
|
xce_sched_worker_t* worker;
|
||||||
|
xce_resource_t* slots_res;
|
||||||
|
float resinuse_slots = -1.0f;
|
||||||
|
int rc;
|
||||||
|
|
||||||
|
memset(&g_stub_state_a, 0, sizeof(g_stub_state_a));
|
||||||
|
fixture_init(&core, &g_stub_plugin_a);
|
||||||
|
|
||||||
|
/* one real resource ("slots") and one real worker advertising it -- so
|
||||||
|
* assertion (e) below is a genuine "worker present, zero charged" proof,
|
||||||
|
* not vacuously true because no worker/resource exists at all. */
|
||||||
|
memset(&res_slots, 0, sizeof(res_slots));
|
||||||
|
res_slots.name = "slots";
|
||||||
|
resources_arr[0] = &res_slots;
|
||||||
|
memset(&resources_data, 0, sizeof(resources_data));
|
||||||
|
resources_data.count = 1;
|
||||||
|
resources_data.resources = resources_arr;
|
||||||
|
|
||||||
|
memset(&worker_slots_val, 0, sizeof(worker_slots_val));
|
||||||
|
worker_slots_val.which = XCE_VAL_NUMERIC;
|
||||||
|
worker_slots_val.u.ivalue32 = 16;
|
||||||
|
memset(&worker_slots_nv, 0, sizeof(worker_slots_nv));
|
||||||
|
worker_slots_nv.name = "slots";
|
||||||
|
worker_slots_nv.val = &worker_slots_val;
|
||||||
|
worker_resvals[0] = &worker_slots_nv;
|
||||||
|
|
||||||
|
memset(&wire_worker, 0, sizeof(wire_worker));
|
||||||
|
wire_worker.name = "hostA";
|
||||||
|
wire_worker.status = 0; /* available */
|
||||||
|
wire_worker.nresvals_to_sched = 1;
|
||||||
|
wire_worker.resvals_to_sched = worker_resvals;
|
||||||
|
workers_arr[0] = &wire_worker;
|
||||||
|
memset(&workers_data, 0, sizeof(workers_data));
|
||||||
|
workers_data.count = 1;
|
||||||
|
workers_data.workers = workers_arr;
|
||||||
|
|
||||||
|
memset(&wire_queue, 0, sizeof(wire_queue));
|
||||||
|
wire_queue.queue = "normal";
|
||||||
|
wire_queue.priority = 1;
|
||||||
|
wire_queue.status = QUEUE_STAT_OPEN;
|
||||||
|
queues[0] = &wire_queue;
|
||||||
|
|
||||||
|
/* the pre-feature wire shape: a job already RUNNING as of the snapshot's
|
||||||
|
* save point, whose allocs field was never populated -- make_job()
|
||||||
|
* never sets ->allocs (calloc()-zeroed NULL default, same helper task
|
||||||
|
* 2.2's own PENDING-job companion test uses above), but here with state
|
||||||
|
* forced to JOB_STAT_RUN to simulate "already running when a
|
||||||
|
* pre-feature build took the snapshot" (Req 3.5's actual described
|
||||||
|
* scenario), not "pending / never dispatched". */
|
||||||
|
wire_job = make_job(801, JOB_STAT_RUN, "normal", "alice", "proj1");
|
||||||
|
jobs[0] = wire_job;
|
||||||
|
|
||||||
|
memset(&jobs_data, 0, sizeof(jobs_data));
|
||||||
|
jobs_data.count = 1;
|
||||||
|
jobs_data.jobs = jobs;
|
||||||
|
|
||||||
|
memset(&in, 0, sizeof(in));
|
||||||
|
in.jobs = &jobs_data;
|
||||||
|
in.queues = queues;
|
||||||
|
in.n_queues = 1;
|
||||||
|
in.resources = &resources_data;
|
||||||
|
in.workers = &workers_data;
|
||||||
|
|
||||||
|
rc = xce_sched_core_load_snapshot(&core, &in);
|
||||||
|
TEST_ASSERT_EQUAL_INT(0, rc);
|
||||||
|
|
||||||
|
/* (a)+(b): load succeeded, state correctly restored as RUNNING. */
|
||||||
|
restored = sched_get_job(&core, 801);
|
||||||
|
TEST_ASSERT_NOT_NULL(restored);
|
||||||
|
TEST_ASSERT_EQUAL_UINT32(JOB_STAT_RUN, restored->state);
|
||||||
|
|
||||||
|
/* (c): task 1.2's fix -- schedobj construction is independent of
|
||||||
|
* allocation data, so a RUNNING job with no allocs still gets a valid,
|
||||||
|
* non-NULL scheduling representation. */
|
||||||
|
TEST_ASSERT_NOT_NULL(restored->schedobj);
|
||||||
|
TEST_ASSERT_EQUAL_INT(XCE_RESREQ_PRIMARY, restored->schedobj->tag);
|
||||||
|
TEST_ASSERT_NOT_NULL(restored->schedobj->u.primary);
|
||||||
|
|
||||||
|
/* (d): clean degrade, not a fabricated allocation. */
|
||||||
|
TEST_ASSERT_NULL(restored->allocs);
|
||||||
|
TEST_ASSERT_NULL(restored->accounts);
|
||||||
|
|
||||||
|
/* (e): the REAL accounting chain, run against the restored core, charges
|
||||||
|
* NOTHING for this job (job->accounts == NULL skips it, same guard live
|
||||||
|
* dispatch's own sched_account_jobs() uses) -- the worker's resInUse for
|
||||||
|
* "slots" reads back as exactly 0, matching today's pre-feature
|
||||||
|
* behavior, not an uninitialized/undefined value. */
|
||||||
|
worker = sched_get_worker(&core, "hostA");
|
||||||
|
TEST_ASSERT_NOT_NULL(worker);
|
||||||
|
|
||||||
|
rc = key_find(core.resmgr, (void*)"slots", (void**)&slots_res);
|
||||||
|
TEST_ASSERT_GREATER_THAN_INT(0, rc);
|
||||||
|
TEST_ASSERT_NOT_NULL(slots_res);
|
||||||
|
|
||||||
|
rc = test_sched_account_jobs(&core);
|
||||||
|
TEST_ASSERT_EQUAL_INT(0, rc);
|
||||||
|
|
||||||
|
get_resvals(&(worker->resInUse), 1, &(slots_res->idx), &resinuse_slots);
|
||||||
|
TEST_ASSERT_EQUAL_FLOAT(0.0f, resinuse_slots);
|
||||||
|
}
|
||||||
|
|
||||||
int main(void)
|
int main(void)
|
||||||
{
|
{
|
||||||
UNITY_BEGIN();
|
UNITY_BEGIN();
|
||||||
|
|
@ -2146,5 +2307,7 @@ int main(void)
|
||||||
test_snapshot_load_leaves_allocs_and_accounts_null_for_job_without_allocation);
|
test_snapshot_load_leaves_allocs_and_accounts_null_for_job_without_allocation);
|
||||||
RUN_TEST(
|
RUN_TEST(
|
||||||
test_snapshot_round_trip_mixed_jobs_shared_pool_resinuse_and_teardown_independence);
|
test_snapshot_round_trip_mixed_jobs_shared_pool_resinuse_and_teardown_independence);
|
||||||
|
RUN_TEST(
|
||||||
|
test_snapshot_load_leaves_running_job_without_allocation_at_zero_resinuse);
|
||||||
return UNITY_END();
|
return UNITY_END();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue