feat(scheduler-sim-decision-parity): relocate resource accounting to core-level (task 1.1)

Relocates sched_account_jobs()/sched_charge_account()/sched_uncharge_account()/
sched_update_account()/sched_update_res_accounts()/sched_commit_job_alloc()
from xce_scheduler_t* to xce_sched_core_t*, mirroring the sched_resolve_job_picker()
precedent, so the simulation driver can call the same accounting service the
normal-mode driver already uses. Pure signature relocation -- every body
reference was already sched->core.*, no logic change. Updates the 3 real call
sites (driver.c's normal-mode round, scheduler.c's sched_begin_session and
sched_commit_decision) and 7 existing tests' link-satisfying stub signatures.

Adds test_resource_accounting.c proving Requirement 1.4: the relocated
accounting produces identical resInUse/sharedInUse output whether called
against a core embedded in xce_scheduler_t or xce_sched_sim_driver_t.

43/44 scheduler tests pass; the 1 failure is pre-existing bug-1560
(unrelated recording.c sandbox-permission ordering issue).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Rongsong Shen 2026-08-22 06:04:03 +00:00
parent 0b2c856d8b
commit 3106c82ed0
No known key found for this signature in database
GPG key ID: 5BCDAAE0123E9FE2
15 changed files with 498 additions and 75 deletions

View file

@ -246,6 +246,47 @@ extern int sched_queue_is_ready(xce_sched_queue_t*);
*/
extern int sched_resolve_job_picker(xce_sched_core_t*, xce_sched_plugin_t**);
/*
* sched_account_jobs/sched_charge_account/sched_uncharge_account/
* sched_commit_job_alloc - resource-in-use accounting (scheduler-sim-
* decision-parity spec, task 1.1). Relocated from scheduler.c/resource.c's
* xce_scheduler_t* signature to this xce_sched_core_t* signature, following
* the exact same precedent sched_resolve_job_picker established above (task
* 4.3): pure signature relocation, body unchanged -- every reference in
* these functions' bodies already only touched sched->core.* before this
* move. Moved so the simulation driver (which has no xce_scheduler_t* of
* its own, only xce_sched_core_t) can call the same accounting service the
* normal-mode driver already uses, restoring the resource-in-use accounting
* parity `.kiro/steering/scheduler-driver-equivalence.md` documents.
*
* sched_account_jobs() - re-derives resInUse/sharedInUse from every
* currently-running job's known resource allocation (job->accounts != NULL)
* by re-charging each one via sched_charge_account(). Skips a RUN-state job
* whose allocation is not yet known -- not an error; see
* scheduler-sim-decision-parity design.md's Recorded-Decision Reconciliation
* component for why that is the expected state before this call runs.
* Callers (both drivers) must call this strictly after the session's resval
* reset and strictly before the picker/allocator are unblocked -- see
* xce_core_begin_session()/xce_core_signal_go()'s own doc comment below for
* the full ordering contract this depends on. Was exported (no longer
* `static`) as of scheduler-core spec task 5.2 for the identical reason;
* still defined in scheduler.c, only its declaration/signature move here.
*
* sched_charge_account()/sched_uncharge_account() - add/remove one job's
* xce_job_account_t (shared + per-host resource usage) to/from
* core->sharedInUse and each worker's resInUse.
*
* sched_commit_job_alloc() - charges a job's already-resolved allocation
* (job->accounts) via sched_charge_account(); a no-op (returns 0) if the
* job's allocation is not yet known. Called by sched_commit_decision()
* (scheduler.c) immediately after a fresh DISPATCH decision populates
* job->allocs/job->accounts.
*/
extern int sched_account_jobs(xce_sched_core_t*);
extern int sched_charge_account(xce_sched_core_t*, xce_job_account_t*);
extern int sched_uncharge_account(xce_sched_core_t*, xce_job_account_t*);
extern int sched_commit_job_alloc(xce_sched_core_t*, xce_sched_job_t*);
/*
* xce_core_event_type_t / xce_core_event_t - the core's typed input-event
* representation (scheduler-core spec, task 4.3, Req 5 AC 4). Exactly the 9

View file

@ -252,17 +252,14 @@ extern int sched_update(xce_scheduler_t*);
extern void sched_go(xce_scheduler_t*);
extern void sched_begin_session(xce_scheduler_t*);
extern void sched_end_session(xce_scheduler_t*);
/* sched_account_jobs - per-running-job resource-in-use accounting
* (re-charges every RUN-state job's resources via sched_charge_account()
* after sched_begin_session()'s resval reset zeroes them). Exported as of
* scheduler-core spec task 5.2 so the new normal-mode driver loop
* (driver.c's xce_sched_driver_run_normal_mode_round()) can call it
* directly at the exact
* point xce_core_begin_session()'s caller must call it: after the resval
* reset, strictly before xce_core_signal_go() unblocks the picker thread.
* Was `static`; body unchanged, still also called from
* sched_begin_session() below (unchanged legacy call site). */
extern int sched_account_jobs(xce_scheduler_t*);
/* sched_account_jobs/sched_charge_account/sched_uncharge_account/
* sched_commit_job_alloc relocated to core.h (scheduler-sim-decision-parity
* spec, task 1.1) -- same rationale as sched_resolve_job_picker above: they
* now operate on xce_sched_core_t*, not xce_scheduler_t*, so the simulation
* driver (mode-agnostic, no xce_scheduler_t* of its own) gains the same
* accounting capability the normal-mode driver already had. Call sites in
* this file (sched_begin_session() below, sched_commit_decision()) now pass
* &sched->core. See core.h for the full contract; body unchanged. */
/* sched_get_job/sched_add_job/sched_get_queue/sched_add_queue/
* sched_get_worker/sched_add_worker/sched_get_user/sched_add_user/
* sched_get_project/sched_add_project/sched_job_is_ready/
@ -284,9 +281,6 @@ extern int sched_check_host_satisfy(xce_scheduler_t*, xce_sched_worker_t*,
extern int sched_collect_workers(xce_scheduler_t*, xlist_t*,
zarray_iter_skip_fn_t,
sched_group_worker_fn_t);
extern int sched_charge_account(xce_scheduler_t*, xce_job_account_t*);
extern int sched_uncharge_account(xce_scheduler_t*, xce_job_account_t*);
extern int sched_commit_job_alloc(xce_scheduler_t*, xce_sched_job_t*);
extern uint64_t sched_get_id(xce_scheduler_t*);
extern xce_alloc_t** sched_make_allocs(xce_scheduler_t*, char*, int, int*,
float*, int*);

View file

@ -692,10 +692,12 @@ int xce_sched_driver_on_decision(void* driver_ctx, xce_decision_t* decision)
* builtin_fcfs) and sched_account_jobs() (re-charges every RUNNING job's
* resources after the resval reset zeroes them) BEFORE sched_go() signals
* the picker thread. xce_core_begin_session()/xce_core_end_session()
* (core_session.c, task 4.4) deliberately exclude both -- they need the
* full xce_scheduler_t*, not just xce_sched_core_t* -- so this function
* calls them itself, in the same slot: strictly AFTER
* xce_core_begin_session()'s resval reset, strictly BEFORE
* (core_session.c, task 4.4) deliberately exclude both -- plugin_begin_session()
* needs the full xce_scheduler_t* (sched_account_jobs() no longer does, as
* of scheduler-sim-decision-parity task 1.1's relocation to xce_sched_core_t*,
* but this call site still holds an xce_scheduler_t* and passes &(sched->core)
* for it) -- so this function calls them itself, in the same slot: strictly
* AFTER xce_core_begin_session()'s resval reset, strictly BEFORE
* xce_core_signal_go() unblocks the picker. Neither plugin_begin_session()
* nor sched_account_jobs() has any data dependency on WHERE within that
* window it runs (verified: plugin_begin_session() only touches
@ -746,7 +748,7 @@ void xce_sched_driver_run_normal_mode_round(void* driver_ctx)
* xce_core_begin_session() just performed, MUST complete before
* xce_core_signal_go() below. */
plugin_begin_session(sched);
sched_account_jobs(sched);
sched_account_jobs(&(sched->core));
/* Req 5 AC 10 (scheduler-core spec, task 9.2): "On core_go(): begin
* building a ReplaySession message with beginRound = xce_core_now(core)"

View file

@ -398,7 +398,7 @@ int sched_check_host_satisfy(xce_scheduler_t* sched, xce_sched_worker_t* w,
return rc;
}
static int sched_update_res_accounts(xce_scheduler_t* sched, zarray_t* array,
static int sched_update_res_accounts(xce_sched_core_t* core, zarray_t* array,
int global, int naccounts,
xce_res_account_t** accounts, int charge)
{
@ -421,9 +421,9 @@ static int sched_update_res_accounts(xce_scheduler_t* sched, zarray_t* array,
xce_resource_t* r = NULL;
int rc = 0;
lock(sched->core.resmgr, RTTI_LOCK_READ);
rc = key_find(sched->core.resmgr, (void*)acct->resname, (void**)&r);
unlock(sched->core.resmgr);
lock(core->resmgr, RTTI_LOCK_READ);
rc = key_find(core->resmgr, (void*)acct->resname, (void**)&r);
unlock(core->resmgr);
if (rc < 0) {
return rc;
@ -433,15 +433,15 @@ static int sched_update_res_accounts(xce_scheduler_t* sched, zarray_t* array,
if (r == NULL) {
int rc;
rc = add_vres(&r, sched->core.resmgr, acct->resname, global);
rc = add_vres(&r, core->resmgr, acct->resname, global);
if (rc < 0) {
return rc;
}
lock(sched->core.resmgr, RTTI_LOCK_READ);
rc = key_find(sched->core.resmgr, (void*)acct->resname,
lock(core->resmgr, RTTI_LOCK_READ);
rc = key_find(core->resmgr, (void*)acct->resname,
(void**)&r);
unlock(sched->core.resmgr);
unlock(core->resmgr);
if (rc < 0) {
return rc;
}
@ -556,12 +556,12 @@ int sched_try_alloc_on_hosts(xce_worker_group_t* g, va_list ap)
return sched_try_alloc_on_host_(w, ap);
}
static int sched_update_account(xce_scheduler_t* sched,
static int sched_update_account(xce_sched_core_t* core,
xce_job_account_t* account, int charge)
{
int i;
sched_update_res_accounts(sched, &(sched->core.sharedInUse), 1,
sched_update_res_accounts(core, &(core->sharedInUse), 1,
account->nres_shared, account->accounts_shared,
charge);
for (i = 0; i < account->nhosts; i++) {
@ -572,34 +572,34 @@ static int sched_update_account(xce_scheduler_t* sched,
continue;
}
w = sched_get_worker(&(sched->core), haccount->host);
w = sched_get_worker(core, haccount->host);
if (w == NULL) {
continue;
}
sched_update_res_accounts(sched, &(w->resInUse), 0, haccount->naccount,
sched_update_res_accounts(core, &(w->resInUse), 0, haccount->naccount,
haccount->accounts, charge);
}
return 0;
}
int sched_charge_account(xce_scheduler_t* sched, xce_job_account_t* acct)
int sched_charge_account(xce_sched_core_t* core, xce_job_account_t* acct)
{
return sched_update_account(sched, acct, 1);
return sched_update_account(core, acct, 1);
}
int sched_uncharge_account(xce_scheduler_t* sched, xce_job_account_t* acct)
int sched_uncharge_account(xce_sched_core_t* core, xce_job_account_t* acct)
{
return sched_update_account(sched, acct, 0);
return sched_update_account(core, acct, 0);
}
int sched_commit_job_alloc(xce_scheduler_t* sched, xce_sched_job_t* job)
int sched_commit_job_alloc(xce_sched_core_t* core, xce_sched_job_t* job)
{
if (job->accounts == NULL) {
return 0;
}
return sched_charge_account(sched, job->accounts);
return sched_charge_account(core, job->accounts);
}
static float get_resval_by_id(xvec_idval_t* vec, int idx)

View file

@ -1250,14 +1250,17 @@ static int sched_reset_workers(xce_scheduler_t* sched)
}
/* No longer `static` as of scheduler-core spec task 5.2 -- see the
* declaration comment in scheduler.h for why the new normal-mode driver
* loop (main.c) needs to call this directly. Body unchanged. */
int sched_account_jobs(xce_scheduler_t* sched)
* declaration comment in core.h for why the new normal-mode driver loop
* (main.c) needs to call this directly. Relocated from an xce_scheduler_t*
* to an xce_sched_core_t* signature (scheduler-sim-decision-parity spec,
* task 1.1) -- see core.h's declaration comment for the full rationale;
* body unchanged. */
int sched_account_jobs(xce_sched_core_t* core)
{
iterator_t it;
int rc = 0;
rc = iter_make(&it, sched->core.jobs);
rc = iter_make(&it, core->jobs);
if (rc < 0) {
return rc;
}
@ -1275,7 +1278,7 @@ int sched_account_jobs(xce_scheduler_t* sched)
}
if (job->accounts != NULL) {
sched_charge_account(sched, job->accounts);
sched_charge_account(core, job->accounts);
}
}
@ -1305,7 +1308,7 @@ void sched_begin_session(xce_scheduler_t* sched)
sched_reset_shared_resval(sched);
sched_reset_workers(sched);
sched_account_jobs(sched);
sched_account_jobs(&(sched->core));
/* Staging merge done — sched_end_session's GC guard reads this field. */
atomic_store_explicit(&sched->core.staging_merge_complete, 1,
@ -1440,7 +1443,7 @@ int sched_commit_decision(xce_scheduler_t* sched, xce_decision_t* decision)
job->allocs = decision->u.alloc;
job->accounts = make_job_account(job->allocs);
return sched_commit_job_alloc(sched, job);
return sched_commit_job_alloc(&(sched->core), job);
}
return 0;

View file

@ -405,13 +405,14 @@ sched_driver_decision_cb_test = executable(
# through a genuinely test-registered xce_sched_plugin_t whose
# begin_session_fn/end_session_fn set/check an ordering flag -- per this
# task's own suggested technique, this is a stronger proof than spying on
# plugin_begin_session() itself. sched_account_jobs() (scheduler.c,
# xce_scheduler_t*-coupled, pulls in resource.c's sched_charge_account())
# is instead a SPY test double recording an order index -- this test's
# target is the DRIVER's ordering discipline, not sched_account_jobs()'s
# own resource-accounting correctness (out of this task's scope, same
# "spy the xce_scheduler_t*-coupled collaborator" rationale
# test_driver_decision_cb.c/test_driver_translate.c already established for
# plugin_begin_session() itself. sched_account_jobs() (scheduler.c, defined
# on xce_sched_core_t* as of scheduler-sim-decision-parity task 1.1, pulls
# in resource.c's sched_charge_account()) is instead a SPY test double
# recording an order index -- this test's target is the DRIVER's ordering
# discipline, not sched_account_jobs()'s own resource-accounting
# correctness (out of this task's scope, same "spy the collaborator"
# rationale test_driver_decision_cb.c/test_driver_translate.c already
# established for the xce_scheduler_t*-coupled
# sched_publish_dispatch()/sched_publish_pend_reason()).
sched_driver_session_ordering_test = executable(
'sched_driver_session_ordering_test',
@ -420,6 +421,23 @@ sched_driver_session_ordering_test = executable(
dependencies: [scheduler_test_deps],
install: false)
# Requirement 1.4 proof for the Core-Level Resource Accounting component
# (scheduler-sim-decision-parity spec, task 1.1): sched_charge_account()/
# sched_uncharge_account()/sched_commit_job_alloc() (resource.c, relocated
# from xce_scheduler_t* to xce_sched_core_t* by this task) produce
# byte-identical resInUse/sharedInUse output whether `core` came from an
# xce_scheduler_t (normal-mode driver) or an xce_sched_sim_driver_t
# (simulation driver, sim.h) -- see test_resource_accounting.c's own file
# header comment for why sched_account_jobs() itself (scheduler.c) is not
# also white-box #included here (no existing test in this directory
# white-box #includes scheduler.c, for the same reason documented there).
sched_resource_accounting_test = executable(
'sched_resource_accounting_test',
['test_resource_accounting.c'],
include_directories:[incdir],
dependencies: [scheduler_test_deps],
install: false)
# Cross-core behavioral-equivalence proof for
# xce_sched_core_save_snapshot()/xce_sched_core_load_snapshot()
# (scheduler-core spec, task 8.1, Req 5 AC 21 / Req 6 AC 5): save immediately
@ -996,6 +1014,9 @@ test('scheduler driver decision cb test',
test('scheduler driver session ordering test',
sched_driver_session_ordering_test, suite: 'scheduler',
env: ['ASAN_OPTIONS=detect_leaks=0'])
test('scheduler resource accounting test',
sched_resource_accounting_test, suite: 'scheduler',
env: ['ASAN_OPTIONS=detect_leaks=0'])
test('scheduler snapshot save load test',
sched_snapshot_save_load_test, suite: 'scheduler',
env: ['ASAN_OPTIONS=detect_leaks=0'])

View file

@ -178,9 +178,9 @@ int plugin_end_session(void* arg)
return 0;
}
int sched_account_jobs(xce_scheduler_t* sched)
int sched_account_jobs(xce_sched_core_t* core)
{
(void)sched;
(void)core;
return 0;
}

View file

@ -142,9 +142,9 @@ int plugin_end_session(void* arg)
return 0;
}
int sched_account_jobs(xce_scheduler_t* sched)
int sched_account_jobs(xce_sched_core_t* core)
{
(void)sched;
(void)core;
return 0;
}

View file

@ -80,13 +80,16 @@
* sched_account_jobs() (scheduler.c) is instead a SPY test double (this
* file never links scheduler.c) -- same rationale
* test_driver_decision_cb.c/test_driver_translate.c already established
* for sched_publish_dispatch()/sched_publish_pend_reason(): it is
* xce_scheduler_t*-coupled and pulls in resource.c's sched_charge_account()
* machinery, whose OWN resource-accounting correctness is out of THIS
* task's scope (this test's target is the DRIVER's ordering discipline --
* does it call sched_account_jobs() at all, and in the right window --
* not whether sched_account_jobs() itself charges resources correctly,
* which is scheduler-allocation spec territory with its own coverage).
* for sched_publish_dispatch()/sched_publish_pend_reason(): it pulls in
* resource.c's sched_charge_account() machinery, whose OWN resource-
* accounting correctness is out of THIS task's scope (this test's target
* is the DRIVER's ordering discipline -- does it call sched_account_jobs()
* at all, and in the right window -- not whether sched_account_jobs()
* itself charges resources correctly, which is scheduler-allocation spec
* territory, plus test_resource_accounting.c's own coverage as of
* scheduler-sim-decision-parity task 1.1). As of that same task,
* sched_account_jobs() takes xce_sched_core_t* (relocated from
* xce_scheduler_t*) -- this spy's signature matches.
*
* White-box #include of ../src/core.c, ../src/core_clock.c,
* ../src/core_events.c, ../src/core_session.c, ../src/decision.c,
@ -346,8 +349,8 @@ int sched_resolve_job_picker(xce_sched_core_t* core, xce_sched_plugin_t** out)
}
/* sched_account_jobs() itself is defined further below (SPY test double
* section) -- its prototype already comes from scheduler.h, included
* above. */
* section) -- its prototype already comes from core.h (included above via
* scheduler.h). */
void setUp(void) { return; }
void tearDown(void) { return; }
@ -409,9 +412,9 @@ static int find_log_index(ordering_event_tag_t tag)
static _Atomic int g_account_jobs_calls;
int sched_account_jobs(xce_scheduler_t* sched)
int sched_account_jobs(xce_sched_core_t* core)
{
(void)sched;
(void)core;
atomic_fetch_add(&g_account_jobs_calls, 1);
log_event(EVT_ACCOUNT_JOBS);

View file

@ -228,9 +228,9 @@ int plugin_end_session(void* arg)
return 0;
}
int sched_account_jobs(xce_scheduler_t* sched)
int sched_account_jobs(xce_sched_core_t* core)
{
(void)sched;
(void)core;
return 0;
}

View file

@ -143,9 +143,9 @@ int sched_fill_limit_requests(xce_scheduler_t* sched, xvec_idval_t* vec,
return 0;
}
int sched_account_jobs(xce_scheduler_t* sched)
int sched_account_jobs(xce_sched_core_t* core)
{
(void)sched;
(void)core;
return 0;
}

View file

@ -0,0 +1,359 @@
/*
* test_resource_accounting.c - Requirement 1.4 proof for the Core-Level
* Resource Accounting component (scheduler-sim-decision-parity spec, task
* 1.1; design.md's "Core-Level Resource Accounting" Unit Tests bullet):
* "produces identical resInUse/sharedInUse output whether called with a
* core obtained from xce_scheduler_t or from xce_sched_sim_driver_t."
*
* Task 1.1 itself is a pure signature relocation (xce_scheduler_t* ->
* xce_sched_core_t*) of sched_account_jobs()/sched_charge_account()/
* sched_uncharge_account()/sched_update_account()/
* sched_update_res_accounts()/sched_commit_job_alloc() -- body unchanged,
* every reference already `sched->core.*` before the move (confirmed via
* `grep "sched->" resource.c | grep -v "sched->core"` returning zero
* matches, design.md's Existing Architecture Analysis). Requirement 1.4 is
* nonetheless a genuinely NEW observable property worth its own test (not
* just "nothing broke"): now that these functions take xce_sched_core_t*
* directly, they are usable identically regardless of which driver-level
* container struct (xce_scheduler_t, the normal-mode driver's; or
* xce_sched_sim_driver_t, the simulation driver's, sim.h) embeds the core.
* This file drives the REAL sched_charge_account()/sched_uncharge_account()/
* sched_commit_job_alloc() (resource.c) against two independently
* initialized xce_sched_core_t instances -- one embedded in an
* xce_scheduler_t, one embedded in an xce_sched_sim_driver_t -- with
* identical worker/account state, and asserts byte-identical
* resInUse/sharedInUse results.
*
* sched_account_jobs() itself (scheduler.c) is NOT white-box #included here
* -- unlike this file's own sched_charge_account()/sched_commit_job_alloc()
* targets (resource.c, a lightweight, already-proven-includable file per
* test.c's own #include "../src/resource.c"), scheduler.c is a ~1400-line
* file with a much larger link-time dependency surface (controller-join,
* capnp decision publishing, HA candidate handling, picker/allocator thread
* lifecycle) that no existing test in this directory white-box #includes for
* exactly that reason (confirmed via grep across testsuite/ before writing
* this file). sched_account_jobs() itself is a thin, ~20-line wrapper
* (iterate core->jobs, call sched_charge_account() for each RUN-state job
* with a known allocation) with no accounting logic of its own -- its
* driver-ordering discipline (called at the right point in the round
* boundary, on both drivers) is already proven by
* test_driver_session_ordering.c (a spy, by design -- see that file's own
* header comment) and this feature's later tasks (2.x/3.x, sim_loop.c's own
* round-boundary integration). This file instead proves the actual
* accounting *body* -- the part design.md's Testing Strategy bullet is
* about -- via its real callees, exercised directly.
*
* White-box #include of ../src/core.c (for the real
* xce_sched_core_init()/sched_add_worker()/sched_get_worker() the fixture
* needs) and ../src/resource.c (the functions under test). Neither file
* needs core_clock.c/core_events.c/core_session.c/decision.c/plugin.c --
* this test never calls xce_core_push_events()/xce_core_begin_session()/
* anything decision- or plugin-registration-related, only the plain
* add-worker/charge-account entry points core.c/resource.c themselves
* define. Stubs below (kill_sched_* family, plugin_unregister) are the
* standard link-satisfying no-ops xce_sched_core_init()'s kvmgr datakillers
* reference (never actually invoked -- this fixture is never torn down,
* same convention as test_driver_session_ordering.c/
* test_snapshot_save_load.c's identical stub set).
* sched_worker_release_host_ctx() is the one real (non-stub) symbol this
* pulls from resource.c itself, not from core_session.c -- unused here
* (this test never calls it), listed only because resource.c defines it.
*/
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <unity/unity.h>
#include "scheduler.h"
#include "sim.h"
/* Pull in the real xce_sched_core_init()/sched_add_worker()/
* sched_get_worker() (core.c) and sched_charge_account()/
* sched_uncharge_account()/sched_commit_job_alloc()/get_resvals() (resource.c,
* the functions under test) into this TU (white-box #include, same
* convention as every other file in this directory). */
#include "../src/core.c"
#include "../src/resource.c"
void kill_sched_job(void* arg) { (void)arg; }
void kill_sched_queue(void* arg) { (void)arg; }
void kill_sched_worker(void* arg) { (void)arg; }
void kill_sched_user(void* arg) { (void)arg; }
void kill_sched_project(void* arg) { (void)arg; }
void kill_sched_resval(xce_val_t* val) { (void)val; }
void plugin_unregister(void* arg) { (void)arg; }
/* Link-satisfying stubs for resource.c's sched_try_alloc_on_host_() (its
* OWN callees, plugin_prepare_allocation()/sched_ctx_get_uncomit_host(),
* live in plugin.c/schedobj.c -- neither white-box #included here). Never
* actually invoked: this file never calls sched_try_alloc_on_hosts()/
* sched_try_alloc_on_host_(), only sched_charge_account()/
* sched_uncharge_account()/sched_commit_job_alloc(). Same two stubs
* test.c's own #include "../src/resource.c" already establishes needing. */
int plugin_prepare_allocation(void* s, xce_sched_ctx_t* c)
{
(void)s;
(void)c;
return 0;
}
int sched_ctx_get_uncomit_host(xvec_t** v, xce_sched_ctx_t* c, int i)
{
(void)v;
(void)c;
(void)i;
return 0;
}
void setUp(void) { return; }
void tearDown(void) { return; }
/* ---------------------------------------------------------------------- */
/* Fixture helpers */
/* ---------------------------------------------------------------------- */
/*
* setup_core_with_worker - real xce_sched_core_init() (core.c) plus one
* real worker (sched_add_worker(), core.c), its resvals/resInUse zarrays
* freshly zarray_init()'d exactly the way a real worker join would leave
* them (sched_add_worker() itself does not do this -- callers own it, same
* as every other white-box fixture in this directory that adds a worker
* directly rather than through the JOBS_PUSH/WORKERS_PUSH event path).
*/
static void setup_core_with_worker(xce_sched_core_t* core,
const char* worker_name)
{
xce_sched_worker_t* w;
int rc;
rc = xce_sched_core_init(core);
TEST_ASSERT_EQUAL_INT(0, rc);
w = (xce_sched_worker_t*)calloc(1, sizeof(*w));
TEST_ASSERT_NOT_NULL(w);
w->name = strdup(worker_name);
TEST_ASSERT_NOT_NULL(w->name);
rc = zarray_init(&(w->resvals), 8, NULL);
TEST_ASSERT_EQUAL_INT(0, rc);
rc = zarray_init(&(w->resInUse), 8, NULL);
TEST_ASSERT_EQUAL_INT(0, rc);
rc = sched_add_worker(core, w);
TEST_ASSERT_TRUE(rc >= 0);
}
/*
* make_test_account - one shared-resource charge (residx `shared_residx`)
* plus one per-host charge (residx `host_residx`, against `worker_name`) --
* enough to exercise both of sched_update_account()'s two branches
* (core->sharedInUse and a specific worker's resInUse). residx is set
* non-negative directly (bypassing sched_update_res_accounts()'s
* resmgr-lookup branch entirely) -- this test targets the charge/uncharge
* arithmetic itself, not resource-name resolution, which is unrelated,
* already-existing logic this task's relocation does not touch.
*/
static xce_job_account_t*
make_test_account(const char* worker_name, int shared_residx,
double shared_used, double shared_reserved, int host_residx,
double host_used, double host_reserved)
{
xce_job_account_t* acct
= (xce_job_account_t*)calloc(1, sizeof(*acct));
TEST_ASSERT_NOT_NULL(acct);
acct->nres_shared = 1;
acct->accounts_shared = (xce_res_account_t**)calloc(
1, sizeof(*acct->accounts_shared));
TEST_ASSERT_NOT_NULL(acct->accounts_shared);
acct->accounts_shared[0]
= (xce_res_account_t*)calloc(1, sizeof(xce_res_account_t));
TEST_ASSERT_NOT_NULL(acct->accounts_shared[0]);
acct->accounts_shared[0]->residx = shared_residx;
acct->accounts_shared[0]->used = shared_used;
acct->accounts_shared[0]->reserved = shared_reserved;
acct->nhosts = 1;
acct->accounts = (xce_host_account_t**)calloc(1, sizeof(*acct->accounts));
TEST_ASSERT_NOT_NULL(acct->accounts);
acct->accounts[0]
= (xce_host_account_t*)calloc(1, sizeof(xce_host_account_t));
TEST_ASSERT_NOT_NULL(acct->accounts[0]);
acct->accounts[0]->host = strdup(worker_name);
acct->accounts[0]->naccount = 1;
acct->accounts[0]->accounts
= (xce_res_account_t**)calloc(1, sizeof(*acct->accounts[0]->accounts));
TEST_ASSERT_NOT_NULL(acct->accounts[0]->accounts);
acct->accounts[0]->accounts[0]
= (xce_res_account_t*)calloc(1, sizeof(xce_res_account_t));
TEST_ASSERT_NOT_NULL(acct->accounts[0]->accounts[0]);
acct->accounts[0]->accounts[0]->residx = host_residx;
acct->accounts[0]->accounts[0]->used = host_used;
acct->accounts[0]->accounts[0]->reserved = host_reserved;
return acct;
}
/* ---------------------------------------------------------------------- */
/* Tests */
/* ---------------------------------------------------------------------- */
#define WORKER_NAME "worker-a"
#define SHARED_RESIDX 0
#define HOST_RESIDX 1
#define SHARED_USED 3.0
#define SHARED_RESERVED 1.5
#define HOST_USED 2.0
#define HOST_RESERVED 0.5
/* SHARED_USED + SHARED_RESERVED, HOST_USED + HOST_RESERVED */
#define EXPECTED_SHARED_CHARGE 4.5f
#define EXPECTED_HOST_CHARGE 2.5f
/*
* test_charge_account_identical_across_core_containers - Requirement 1.4:
* sched_charge_account()/sched_uncharge_account() (resource.c) produce
* byte-identical resInUse/sharedInUse results whether `core` came from an
* xce_scheduler_t (sched.core, the normal-mode driver's container) or an
* xce_sched_sim_driver_t (sim.core, the simulation driver's container) --
* the exact property that makes the simulation driver's build now able to
* call the same accounting service the normal-mode driver already used.
*/
static void test_charge_account_identical_across_core_containers(void)
{
xce_scheduler_t sched;
xce_sched_sim_driver_t sim;
xce_job_account_t* acct;
xce_sched_worker_t* w_sched;
xce_sched_worker_t* w_sim;
float shared_sched[1];
float shared_sim[1];
float host_sched[1];
float host_sim[1];
int shared_idx[1] = { SHARED_RESIDX };
int host_idx[1] = { HOST_RESIDX };
int rc;
memset(&sched, 0, sizeof(sched));
memset(&sim, 0, sizeof(sim));
setup_core_with_worker(&(sched.core), WORKER_NAME);
setup_core_with_worker(&(sim.core), WORKER_NAME);
/* Same account struct fed to both cores -- sched_charge_account() never
* mutates it when residx is already non-negative (see
* make_test_account()'s own comment), so reuse is safe and keeps the
* two calls genuinely identical, not merely equal-by-construction. */
acct = make_test_account(WORKER_NAME, SHARED_RESIDX, SHARED_USED,
SHARED_RESERVED, HOST_RESIDX, HOST_USED,
HOST_RESERVED);
rc = sched_charge_account(&(sched.core), acct);
TEST_ASSERT_EQUAL_INT(0, rc);
rc = sched_charge_account(&(sim.core), acct);
TEST_ASSERT_EQUAL_INT(0, rc);
/* --- sharedInUse: identical across both containers, and correct --- */
get_resvals(&(sched.core.sharedInUse), 1, shared_idx, shared_sched);
get_resvals(&(sim.core.sharedInUse), 1, shared_idx, shared_sim);
TEST_ASSERT_EQUAL_FLOAT(EXPECTED_SHARED_CHARGE, shared_sched[0]);
TEST_ASSERT_EQUAL_FLOAT(shared_sched[0], shared_sim[0]);
/* --- per-worker resInUse: identical across both containers --- */
w_sched = sched_get_worker(&(sched.core), WORKER_NAME);
w_sim = sched_get_worker(&(sim.core), WORKER_NAME);
TEST_ASSERT_NOT_NULL(w_sched);
TEST_ASSERT_NOT_NULL(w_sim);
get_resvals(&(w_sched->resInUse), 1, host_idx, host_sched);
get_resvals(&(w_sim->resInUse), 1, host_idx, host_sim);
TEST_ASSERT_EQUAL_FLOAT(EXPECTED_HOST_CHARGE, host_sched[0]);
TEST_ASSERT_EQUAL_FLOAT(host_sched[0], host_sim[0]);
/* --- sched_uncharge_account() mirrors this identically too --- */
rc = sched_uncharge_account(&(sched.core), acct);
TEST_ASSERT_EQUAL_INT(0, rc);
rc = sched_uncharge_account(&(sim.core), acct);
TEST_ASSERT_EQUAL_INT(0, rc);
get_resvals(&(sched.core.sharedInUse), 1, shared_idx, shared_sched);
get_resvals(&(sim.core.sharedInUse), 1, shared_idx, shared_sim);
TEST_ASSERT_EQUAL_FLOAT(0.0f, shared_sched[0]);
TEST_ASSERT_EQUAL_FLOAT(shared_sched[0], shared_sim[0]);
get_resvals(&(w_sched->resInUse), 1, host_idx, host_sched);
get_resvals(&(w_sim->resInUse), 1, host_idx, host_sim);
TEST_ASSERT_EQUAL_FLOAT(0.0f, host_sched[0]);
TEST_ASSERT_EQUAL_FLOAT(host_sched[0], host_sim[0]);
}
/*
* test_commit_job_alloc_identical_across_core_containers - Requirement 1.4
* for sched_commit_job_alloc() specifically (the third relocated group
* member scheduler.c's sched_commit_decision() calls, design.md's own File
* Structure Plan entry for scheduler.c): both its no-op (job->accounts ==
* NULL) and its charging path behave identically regardless of which
* driver-level struct the core came from.
*/
static void test_commit_job_alloc_identical_across_core_containers(void)
{
xce_scheduler_t sched;
xce_sched_sim_driver_t sim;
xce_sched_job_t job_sched;
xce_sched_job_t job_sim;
xce_job_account_t* acct;
float shared_sched[1];
float shared_sim[1];
int shared_idx[1] = { SHARED_RESIDX };
int rc;
memset(&sched, 0, sizeof(sched));
memset(&sim, 0, sizeof(sim));
memset(&job_sched, 0, sizeof(job_sched));
memset(&job_sim, 0, sizeof(job_sim));
setup_core_with_worker(&(sched.core), WORKER_NAME);
setup_core_with_worker(&(sim.core), WORKER_NAME);
/* --- job->accounts == NULL: documented no-op, identical on both --- */
job_sched.accounts = NULL;
job_sim.accounts = NULL;
rc = sched_commit_job_alloc(&(sched.core), &job_sched);
TEST_ASSERT_EQUAL_INT(0, rc);
rc = sched_commit_job_alloc(&(sim.core), &job_sim);
TEST_ASSERT_EQUAL_INT(0, rc);
get_resvals(&(sched.core.sharedInUse), 1, shared_idx, shared_sched);
get_resvals(&(sim.core.sharedInUse), 1, shared_idx, shared_sim);
TEST_ASSERT_EQUAL_FLOAT(0.0f, shared_sched[0]);
TEST_ASSERT_EQUAL_FLOAT(shared_sched[0], shared_sim[0]);
/* --- job->accounts set: charges through sched_charge_account(),
* identical on both --- */
acct = make_test_account(WORKER_NAME, SHARED_RESIDX,
SHARED_USED, SHARED_RESERVED,
HOST_RESIDX, HOST_USED,
HOST_RESERVED);
job_sched.accounts = acct;
job_sim.accounts = acct;
rc = sched_commit_job_alloc(&(sched.core), &job_sched);
TEST_ASSERT_EQUAL_INT(0, rc);
rc = sched_commit_job_alloc(&(sim.core), &job_sim);
TEST_ASSERT_EQUAL_INT(0, rc);
get_resvals(&(sched.core.sharedInUse), 1, shared_idx, shared_sched);
get_resvals(&(sim.core.sharedInUse), 1, shared_idx, shared_sim);
TEST_ASSERT_EQUAL_FLOAT(EXPECTED_SHARED_CHARGE, shared_sched[0]);
TEST_ASSERT_EQUAL_FLOAT(shared_sched[0], shared_sim[0]);
}
int main(void)
{
UNITY_BEGIN();
RUN_TEST(test_charge_account_identical_across_core_containers);
RUN_TEST(test_commit_job_alloc_identical_across_core_containers);
return UNITY_END();
}

View file

@ -233,9 +233,9 @@ int plugin_end_session(void* arg)
return 0;
}
int sched_account_jobs(xce_scheduler_t* sched)
int sched_account_jobs(xce_sched_core_t* core)
{
(void)sched;
(void)core;
return 0;
}

View file

@ -195,9 +195,9 @@ int sched_fill_limit_requests(xce_scheduler_t* sched, xvec_idval_t* vec,
* a no-op stand-in, same rationale test_record_lifecycle.c's own copy
* documents (real resource accounting is scheduler.c/resource.c territory,
* not exercised here). */
int sched_account_jobs(xce_scheduler_t* sched)
int sched_account_jobs(xce_sched_core_t* core)
{
(void)sched;
(void)core;
return 0;
}