Task 2.4: outcome capture moves to sched_trace_outcome.c and the position/host/summary/rollback half of the model to sched_trace_scan.c; shared helpers go behind sched_trace_internal.h (sti_ prefix). Code is moved unchanged; every target that compiles the trace sources gets all four, and the core isolation scan covers the new files. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
619 lines
24 KiB
C
619 lines
24 KiB
C
/*
|
|
* test_seqjob_rewrap_equivalence.c - task 3.2 (scheduler-allocation spec):
|
|
* the dedicated pre/post-rewrap host-selection EQUIVALENCE proof for
|
|
* seqjob allocation (Req 9.1) -- then seqjob_allocator.c's my_alloc(),
|
|
* today the framework routine sched_allocator_run() (alloc_runner.c).
|
|
*
|
|
* task 3.1's own test_seqjob_allocator.c only proves the rewrapped
|
|
* allocator still runs end to end through the shared plan_iter/host_iter
|
|
* composition (a single-obvious-host smoke case) and that the new
|
|
* maxTasks==1 guard (Req 4.6) works. Req 9.1's actual claim is narrower
|
|
* and harder to prove: "which host a job lands on, given the same
|
|
* worker/resource state" must be UNCHANGED by the rewrap. A test that only
|
|
* exercises the single-obvious-host case cannot distinguish "the new
|
|
* composition happens to work" from "the new composition picks the same
|
|
* host the old sched_collect_workers()+xlist_find() loop would have" --
|
|
* this file's whole job is to close that gap with fixture states rich
|
|
* enough that a plausible regression in the rewrap would actually show up.
|
|
*
|
|
* How equivalence is established (see this task's own status report for
|
|
* the full rationale): rather than literally resurrecting the deleted
|
|
* pre-rewrap sched_collect_workers()/sched_try_alloc_on_hosts() code (git
|
|
* history: commit b18c5f78^) and running it side by side, this file hand-
|
|
* derives the expected pick from the two well-defined, already-unit-tested
|
|
* primitives the composition is built from -- sched_meet_host_selection()
|
|
* and sched_check_host_satisfy() (both resource.c, unmodified by the
|
|
* rewrap) -- and asserts the allocator's real behavior against that
|
|
* derivation. This is valid because both the old and new code paths are
|
|
* confirmed (by reading resource.c) to funnel every host decision through
|
|
* the exact same leaf, sched_try_alloc_on_host_(): pre-rewrap it was
|
|
* `static`/va_list-based and reached via sched_try_alloc_on_hosts(); post-
|
|
* rewrap (task 3.1) it is the same logic, exported with real parameters.
|
|
* Byte-for-byte identical body (confirmed via `git show b18c5f78^:
|
|
* src/scheduler/src/resource.c` against the current file) -- the ONLY
|
|
* thing that changed is how candidate hosts are fed into it: an inline
|
|
* sched_collect_workers()+xlist_find() scan before, xce_host_iter_t's
|
|
* lazy iter_make()/iter_next()-driven scan (the SAME underlying RTTI
|
|
* primitive over the SAME sched->core.workers var -- see host_iter.c's own
|
|
* file header) after. So "identical host selection" reduces exactly to
|
|
* "the new host_iter-driven loop still visits candidates in
|
|
* iter_make()/iter_next() order and still picks the first one
|
|
* sched_try_alloc_on_host_() accepts" -- which is what each test below
|
|
* independently drives and checks, without assuming it.
|
|
*
|
|
* Covers (see design.md's Testing Strategy for
|
|
* test_seqjob_rewrap_equivalence.c):
|
|
* 1. Multiple candidate hosts, only one resource-satisfies -- the
|
|
* correct one is chosen, and ONLY it carries a provisional debit.
|
|
* 2. A host that fails the SELECT filter specifically, despite having
|
|
* abundant free resources -- must still be skipped.
|
|
* 3. Iteration-order sensitivity: with two equally-valid hosts, the pick
|
|
* must match whichever host sched->core.workers' own
|
|
* iter_make()/iter_next() (the identical primitive both the deleted
|
|
* pre-rewrap loop and today's host_iter.c use) visits first --
|
|
* derived directly from that primitive in the test itself, not
|
|
* assumed.
|
|
* 4. A select-failing host, then a resource-failing host, then a
|
|
* succeeding host, in that order -- proves the loop doesn't
|
|
* short-circuit or mis-skip when back-to-back failures of two
|
|
* DIFFERENT kinds precede the eventual success.
|
|
*
|
|
* White-box #include convention: identical combination
|
|
* test_seqjob_allocator.c (task 3.1) already established (core.c +
|
|
* resource.c + schedobj.c + host_iter.c + plugins/seqjob_allocator.c),
|
|
* plus <rtti.h> directly for iter_make()/iter_next()/iter_destroy() (test
|
|
* 3's own grounding of "iteration order"), and test_host_iter.c's
|
|
* make_rack_eq_select() helper (adapted for a resmgr-assigned residx
|
|
* rather than a hardcoded one, since this file -- like
|
|
* test_seqjob_allocator.c, unlike test_host_iter.c -- runs the REAL
|
|
* seqjob_prepare_allocation(), whose key_find(resmgr, "slots", ...) call
|
|
* needs a genuinely registered "slots" resource).
|
|
*/
|
|
#include <errno.h>
|
|
#include <rtti.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#include <unity/unity.h>
|
|
|
|
#include "resobj.h"
|
|
#include "resreq.h"
|
|
#include "sched/alloc_iter.h"
|
|
#include "scheduler.h"
|
|
#include "xcealloc.h"
|
|
|
|
/* Pull in the real xce_sched_core_init()/sched_add_worker() (core.c),
|
|
* sched_meet_host_selection()/sched_check_host_satisfy()/put_resvals()
|
|
* (resource.c), the real sched_ctx_init()/sched_ctx_get_uncomit_*()
|
|
* (schedobj.c), the shared host_iter.c implementation, and
|
|
* seqjob_allocator.c itself (the file under test) into this TU -- same
|
|
* combination test_seqjob_allocator.c (task 3.1) already established. */
|
|
#include "../src/core.c"
|
|
#include "../src/resource.c"
|
|
#include "../src/schedobj.c"
|
|
#include "../src/host_iter.c"
|
|
#include "../src/alloc_runner.c"
|
|
/* Hooked sources compile SCHED_TRACE bodies (scheduler-sim-decision-trace). */
|
|
#include "../src/sched_trace.c"
|
|
#include "../src/sched_trace_scan.c"
|
|
#include "../src/sched_trace_capture.c"
|
|
#include "../src/sched_trace_outcome.c"
|
|
#include "../src/plugins/seqjob_allocator.c"
|
|
|
|
/* ---------------------------------------------------------------------- */
|
|
/* Link-satisfying stubs (identical set/rationale to test_seqjob_allocator.c) */
|
|
/* ---------------------------------------------------------------------- */
|
|
|
|
int plugin_register(void* sched, void* plugin)
|
|
{
|
|
(void)sched;
|
|
(void)plugin;
|
|
return 0;
|
|
}
|
|
|
|
void plugin_unregister(void* arg) { (void)arg; }
|
|
|
|
/* Delegates to the real seqjob_prepare_allocation() under test, standing
|
|
* in for plugin_prepare_allocation()'s (plugin.c) real fan-out across
|
|
* every registered plugin -- exactly one (seqjob's own) in this file's
|
|
* fixtures. See test_seqjob_allocator.c's identical stub for the full
|
|
* xvec_idval_fini()/init() reset-before-fan-out rationale. */
|
|
int plugin_prepare_allocation(xce_sched_core_t* sched, xce_sched_ctx_t* ctx)
|
|
{
|
|
xce_sched_plugin_t fake_plugin;
|
|
|
|
if (ctx->worker == NULL) {
|
|
xvec_idval_fini(&(ctx->alloc_req.shared));
|
|
xvec_idval_init(&(ctx->alloc_req.shared), 8);
|
|
} else {
|
|
xvec_idval_fini(&(ctx->alloc_req.host));
|
|
xvec_idval_init(&(ctx->alloc_req.host), 8);
|
|
}
|
|
|
|
memset(&fake_plugin, 0, sizeof(fake_plugin));
|
|
fake_plugin.sched = sched;
|
|
/* scheduler-sim-live-allocation spec, task 4.3: seqjob_prepare_
|
|
* allocation() now reads plugin->core (honestly typed, task 1.2)
|
|
* instead of plugin->sched -- `sched` here is already an
|
|
* xce_sched_core_t* (this stub's own signature), so no cast is
|
|
* needed. */
|
|
fake_plugin.core = sched;
|
|
|
|
return seqjob_prepare_allocation(&fake_plugin, ctx);
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
/* Same rationale as test_seqjob_allocator.c's identical stub: avoids
|
|
* white-box-including the rest of scheduler.c just to build the leaf
|
|
* xce_alloc_t records the runner's success path constructs. */
|
|
xce_alloc_t** sched_make_allocs(xce_scheduler_t* sched, char* reserve,
|
|
int nres, int* idxs, float* vals,
|
|
int* duration)
|
|
{
|
|
xce_alloc_t** allocs;
|
|
int i;
|
|
|
|
(void)sched;
|
|
(void)reserve;
|
|
(void)idxs;
|
|
(void)vals;
|
|
(void)duration;
|
|
|
|
if (nres <= 0) {
|
|
return NULL;
|
|
}
|
|
|
|
allocs = (xce_alloc_t**)calloc(nres, sizeof(*allocs));
|
|
if (allocs == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
for (i = 0; i < nres; i++) {
|
|
allocs[i] = (xce_alloc_t*)calloc(1, sizeof(xce_alloc_t));
|
|
}
|
|
|
|
return allocs;
|
|
}
|
|
|
|
void setUp(void) { return; }
|
|
void tearDown(void) { return; }
|
|
|
|
/* ---------------------------------------------------------------------- */
|
|
/* Fixture helpers */
|
|
/* ---------------------------------------------------------------------- */
|
|
|
|
/* register_slots_resource - the real makeResource() (resobj.c), giving
|
|
* "slots" a resmgr-assigned idx -- identical to test_seqjob_allocator.c's
|
|
* own helper. */
|
|
static xce_resource_t* register_slots_resource(var resmgr)
|
|
{
|
|
xce_resource_t res;
|
|
|
|
memset(&res, 0, sizeof(res));
|
|
res.name = strdup("slots");
|
|
|
|
return makeResource(resmgr, &res);
|
|
}
|
|
|
|
/* add_worker_with_slots - a real sched_add_worker() (core.c) worker
|
|
* carrying `slots_total` free "slots" (idx == the resmgr-assigned index).
|
|
* Identical to test_seqjob_allocator.c's own helper. */
|
|
static xce_sched_worker_t* add_worker_with_slots(xce_sched_core_t* core,
|
|
const char* name,
|
|
int slots_idx,
|
|
float slots_total)
|
|
{
|
|
xce_sched_worker_t* w;
|
|
int idx[1];
|
|
float val[1];
|
|
int rc;
|
|
|
|
w = (xce_sched_worker_t*)calloc(1, sizeof(*w));
|
|
TEST_ASSERT_NOT_NULL(w);
|
|
|
|
w->name = strdup(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);
|
|
|
|
idx[0] = slots_idx;
|
|
val[0] = slots_total;
|
|
put_resvals(&(w->resvals), 1, idx, val);
|
|
val[0] = 0.0f;
|
|
put_resvals(&(w->resInUse), 1, idx, val);
|
|
|
|
rc = sched_add_worker(core, w);
|
|
TEST_ASSERT_TRUE(rc >= 0);
|
|
|
|
return w;
|
|
}
|
|
|
|
/* add_worker_with_slots_and_rack - same as above, plus a second, numeric
|
|
* "rack" resource value (never staged as a want -- consumed only by the
|
|
* select expression, matching test_host_iter.c's own add_worker() shape)
|
|
* so select-filter tests can distinguish hosts by something other than
|
|
* resource availability. */
|
|
static xce_sched_worker_t*
|
|
add_worker_with_slots_and_rack(xce_sched_core_t* core, const char* name,
|
|
int slots_idx, float slots_total, int rack_idx,
|
|
double rack)
|
|
{
|
|
xce_sched_worker_t* w = add_worker_with_slots(core, name, slots_idx, slots_total);
|
|
int idx[1];
|
|
float val[1];
|
|
|
|
idx[0] = rack_idx;
|
|
val[0] = (float)rack;
|
|
put_resvals(&(w->resvals), 1, idx, val);
|
|
|
|
return w;
|
|
}
|
|
|
|
/* make_rack_eq_select - builds `rack == literal` (SEL_EQ over a numeric
|
|
* SEL_RESNAME leaf and a SEL_NUM literal), the same hand-built tree shape
|
|
* test_host_iter.c's own helper uses, generalized to take a resmgr-
|
|
* assigned rack_idx instead of a hardcoded one. */
|
|
static void make_rack_eq_select(xce_select_node_t* resname,
|
|
xce_select_node_t* num, xce_select_node_t* eq,
|
|
xce_select_t* select, double literal,
|
|
int rack_idx)
|
|
{
|
|
memset(resname, 0, sizeof(*resname));
|
|
resname->type = SEL_RESNAME;
|
|
resname->u.resname.res_idx = rack_idx;
|
|
resname->u.resname.res_type = RES_TYPE_NUMERIC;
|
|
|
|
memset(num, 0, sizeof(*num));
|
|
num->type = SEL_NUM;
|
|
num->u.numval = literal;
|
|
|
|
memset(eq, 0, sizeof(*eq));
|
|
eq->type = SEL_EQ;
|
|
eq->u.binop.left = resname;
|
|
eq->u.binop.right = num;
|
|
|
|
select->root = eq;
|
|
}
|
|
|
|
static void init_primary_obj(xce_sched_obj_t* obj,
|
|
xce_primary_schedobj_t* primary_obj,
|
|
xce_primary_resreq_t* primary)
|
|
{
|
|
memset(primary_obj, 0, sizeof(*primary_obj));
|
|
primary_obj->resreq = primary;
|
|
|
|
memset(obj, 0, sizeof(*obj));
|
|
obj->tag = XCE_RESREQ_PRIMARY;
|
|
obj->u.primary = primary_obj;
|
|
}
|
|
|
|
/* drive_allocator - the exact production sequence a job's allocation
|
|
* attempt runs: sched_ctx_init() -> sched_ctx_set_primary() (the real
|
|
* call that stages ctx->alloc_req.shared via plugin_prepare_allocation()'s
|
|
* ctx->worker==NULL branch, exactly as sched_make_primary_allocation()
|
|
* does today) -> seqjob's registered entry point, the framework routine
|
|
* sched_allocator_run(). Returns its return code; ctx is left
|
|
* initialized (caller must sched_ctx_fini()). */
|
|
static int drive_allocator(xce_scheduler_t* sched, xce_sched_obj_t* obj,
|
|
xce_primary_resreq_t* primary, xce_sched_ctx_t* ctx)
|
|
{
|
|
xce_sched_plugin_t plugin;
|
|
|
|
/* scheduler-sim-live-allocation spec, task 2.1: ctx->sched is now
|
|
* honestly typed xce_sched_core_t* -- pass &(sched->core), not sched. */
|
|
TEST_ASSERT_EQUAL_INT(0, sched_ctx_init(ctx, &(sched->core), obj));
|
|
sched_ctx_set_primary(ctx, primary);
|
|
|
|
memset(&plugin, 0, sizeof(plugin));
|
|
plugin.sched = sched;
|
|
/* the runner reads plugin->core (honestly typed) for every core-safe
|
|
* call, plugin->sched (sched_make_allocs(), scheduler.c) and seqjob's
|
|
* allocator contract. */
|
|
plugin.core = &(sched->core);
|
|
plugin.allocator_iface = &seqjob_alloc_iface;
|
|
|
|
return sched_allocator_run(&plugin, ctx);
|
|
}
|
|
|
|
/* assert_uncommitted_host_debit - reads ctx->uncomitted.hosts[w->idx] for
|
|
* resource residx and asserts it equals `expected`. Same accessor pattern
|
|
* test_resource_accounting.c's own debit test uses. */
|
|
static void assert_uncommitted_host_debit(xce_sched_ctx_t* ctx,
|
|
xce_sched_worker_t* w, int residx,
|
|
float expected)
|
|
{
|
|
xvec_t* uncomit = NULL;
|
|
int rc;
|
|
|
|
rc = sched_ctx_get_uncomit_host(&uncomit, ctx, w->idx);
|
|
TEST_ASSERT_EQUAL_INT(1, rc);
|
|
TEST_ASSERT_NOT_NULL(uncomit);
|
|
TEST_ASSERT_EQUAL_FLOAT(expected, ((float*)uncomit->data)[residx]);
|
|
}
|
|
|
|
/* ---------------------------------------------------------------------- */
|
|
/* Tests */
|
|
/* ---------------------------------------------------------------------- */
|
|
|
|
/*
|
|
* Req 9.1, fixture 1: three candidate hosts, two of which fail the
|
|
* resource-availability check and one of which satisfies -- the
|
|
* satisfying host must be chosen, and it alone carries the provisional
|
|
* debit. Hosts are inserted in an order that requires the loop to try (and
|
|
* correctly reject) two failing candidates before reaching the one that
|
|
* works -- a loop that stopped after the first failure, or that debited
|
|
* the wrong host, would fail this test.
|
|
*/
|
|
static void
|
|
test_three_hosts_only_resource_satisfying_host_is_chosen_and_debited(void)
|
|
{
|
|
xce_scheduler_t sched;
|
|
xce_resource_t* slots_res;
|
|
xce_sched_worker_t* host_a;
|
|
xce_sched_worker_t* host_b;
|
|
xce_sched_worker_t* host_c;
|
|
xce_sched_obj_t obj;
|
|
xce_primary_schedobj_t primary_obj;
|
|
xce_primary_resreq_t primary;
|
|
xce_sched_ctx_t ctx;
|
|
int rc;
|
|
|
|
memset(&sched, 0, sizeof(sched));
|
|
TEST_ASSERT_EQUAL_INT(0, xce_sched_core_init(&(sched.core)));
|
|
|
|
slots_res = register_slots_resource(sched.core.resmgr);
|
|
TEST_ASSERT_NOT_NULL(slots_res);
|
|
|
|
host_a = add_worker_with_slots(&(sched.core), "host-a", slots_res->idx,
|
|
0.0f); /* no free slots -- resource-fails */
|
|
host_b = add_worker_with_slots(&(sched.core), "host-b", slots_res->idx,
|
|
0.0f); /* no free slots -- resource-fails */
|
|
host_c = add_worker_with_slots(&(sched.core), "host-c", slots_res->idx,
|
|
4.0f); /* plenty -- satisfies */
|
|
|
|
memset(&primary, 0, sizeof(primary));
|
|
primary.minTasks = 1;
|
|
primary.maxTasks = 1;
|
|
primary.select = NULL;
|
|
|
|
init_primary_obj(&obj, &primary_obj, &primary);
|
|
|
|
rc = drive_allocator(&sched, &obj, &primary, &ctx);
|
|
|
|
TEST_ASSERT_EQUAL_INT(1, rc);
|
|
TEST_ASSERT_NOT_NULL(ctx.alloc);
|
|
TEST_ASSERT_EQUAL_INT(1, ctx.alloc->nhosts);
|
|
TEST_ASSERT_NOT_NULL(ctx.alloc->allocs[0]);
|
|
TEST_ASSERT_EQUAL_STRING("host-c", ctx.alloc->allocs[0]->host);
|
|
|
|
assert_uncommitted_host_debit(&ctx, host_c, slots_res->idx, 1.0f);
|
|
assert_uncommitted_host_debit(&ctx, host_a, slots_res->idx, 0.0f);
|
|
assert_uncommitted_host_debit(&ctx, host_b, slots_res->idx, 0.0f);
|
|
|
|
sched_ctx_fini(&ctx);
|
|
}
|
|
|
|
/*
|
|
* Req 9.1, fixture 2: a host that fails the SELECT filter specifically
|
|
* (not resource availability -- it has abundant free slots) must still be
|
|
* skipped in favor of a host that both matches the select expression and
|
|
* satisfies resources. This is the exact case task 3.2's own task text
|
|
* and design.md's Testing Strategy call out by name: a test that never
|
|
* exercises a select-filter failure would not actually prove equivalence.
|
|
*/
|
|
static void
|
|
test_host_failing_select_filter_is_skipped_despite_plenty_of_resources(void)
|
|
{
|
|
xce_scheduler_t sched;
|
|
xce_resource_t* slots_res;
|
|
int rack_idx;
|
|
xce_sched_worker_t* rack_nomatch;
|
|
xce_sched_worker_t* rack_match;
|
|
xce_sched_obj_t obj;
|
|
xce_primary_schedobj_t primary_obj;
|
|
xce_primary_resreq_t primary;
|
|
xce_select_node_t resname, num, eq;
|
|
xce_select_t select;
|
|
xce_sched_ctx_t ctx;
|
|
int rc;
|
|
|
|
memset(&sched, 0, sizeof(sched));
|
|
TEST_ASSERT_EQUAL_INT(0, xce_sched_core_init(&(sched.core)));
|
|
|
|
slots_res = register_slots_resource(sched.core.resmgr);
|
|
TEST_ASSERT_NOT_NULL(slots_res);
|
|
rack_idx = slots_res->idx + 1;
|
|
|
|
/* rack-nomatch: 8 free slots (far more than the 1-slot want a
|
|
* maxTasks==1 job stages) but the wrong rack -- inserted FIRST so a
|
|
* regression that checked resource availability before (or instead
|
|
* of) the select expression would wrongly pick it. */
|
|
rack_nomatch = add_worker_with_slots_and_rack(
|
|
&(sched.core), "rack-nomatch", slots_res->idx, 8.0f, rack_idx, 2.0);
|
|
rack_match = add_worker_with_slots_and_rack(
|
|
&(sched.core), "rack-match", slots_res->idx, 4.0f, rack_idx, 1.0);
|
|
|
|
make_rack_eq_select(&resname, &num, &eq, &select, 1.0, rack_idx);
|
|
|
|
memset(&primary, 0, sizeof(primary));
|
|
primary.minTasks = 1;
|
|
primary.maxTasks = 1;
|
|
primary.select = &select;
|
|
|
|
init_primary_obj(&obj, &primary_obj, &primary);
|
|
|
|
rc = drive_allocator(&sched, &obj, &primary, &ctx);
|
|
|
|
TEST_ASSERT_EQUAL_INT(1, rc);
|
|
TEST_ASSERT_NOT_NULL(ctx.alloc);
|
|
TEST_ASSERT_EQUAL_STRING("rack-match", ctx.alloc->allocs[0]->host);
|
|
|
|
assert_uncommitted_host_debit(&ctx, rack_match, slots_res->idx, 1.0f);
|
|
assert_uncommitted_host_debit(&ctx, rack_nomatch, slots_res->idx, 0.0f);
|
|
|
|
sched_ctx_fini(&ctx);
|
|
}
|
|
|
|
/*
|
|
* Req 9.1, fixture 3 (iteration-order sensitivity): two hosts that are
|
|
* BOTH select-matching and resource-satisfying -- the allocator must pick
|
|
* whichever one sched->core.workers' own iteration mechanism visits
|
|
* FIRST. That mechanism -- iter_make()/iter_next() over the RTTI `var`
|
|
* sched->core.workers -- is the exact same primitive the deleted
|
|
* pre-rewrap sched_collect_workers() used (confirmed via `git show
|
|
* b18c5f78^:src/scheduler/src/scheduler.c`) and the one host_iter.c uses
|
|
* today (host_iter.c's own file header comment): same underlying object,
|
|
* same traversal call, so "first in that order" is definitionally
|
|
* identical pre- and post-rewrap. This test derives that order directly
|
|
* (an independent iter_make()/iter_next() scan below) rather than
|
|
* assuming worker-insertion order happens to match it, then asserts
|
|
* the allocator's pick against the derived value.
|
|
*/
|
|
static void
|
|
test_pick_matches_first_host_in_the_shared_workers_iteration_order(void)
|
|
{
|
|
xce_scheduler_t sched;
|
|
xce_resource_t* slots_res;
|
|
xce_sched_obj_t obj;
|
|
xce_primary_schedobj_t primary_obj;
|
|
xce_primary_resreq_t primary;
|
|
xce_sched_ctx_t ctx;
|
|
iterator_t it;
|
|
xce_sched_worker_t* first_in_order = NULL;
|
|
int rc;
|
|
|
|
memset(&sched, 0, sizeof(sched));
|
|
TEST_ASSERT_EQUAL_INT(0, xce_sched_core_init(&(sched.core)));
|
|
|
|
slots_res = register_slots_resource(sched.core.resmgr);
|
|
TEST_ASSERT_NOT_NULL(slots_res);
|
|
|
|
/* Two equally-valid hosts -- neither the select expression (unset)
|
|
* nor resource availability (both have plenty) distinguishes them. */
|
|
add_worker_with_slots(&(sched.core), "host-1", slots_res->idx, 4.0f);
|
|
add_worker_with_slots(&(sched.core), "host-2", slots_res->idx, 4.0f);
|
|
|
|
/* Ground truth: the same RTTI iteration primitive host_iter.c (and,
|
|
* pre-rewrap, sched_collect_workers()) uses over the same
|
|
* sched.core.workers var. */
|
|
rc = iter_make(&it, sched.core.workers);
|
|
TEST_ASSERT_TRUE(rc >= 0);
|
|
rc = iter_next(&it, (void**)&first_in_order);
|
|
TEST_ASSERT_TRUE(rc > 0);
|
|
TEST_ASSERT_NOT_NULL(first_in_order);
|
|
iter_destroy(&it);
|
|
|
|
memset(&primary, 0, sizeof(primary));
|
|
primary.minTasks = 1;
|
|
primary.maxTasks = 1;
|
|
primary.select = NULL;
|
|
|
|
init_primary_obj(&obj, &primary_obj, &primary);
|
|
|
|
rc = drive_allocator(&sched, &obj, &primary, &ctx);
|
|
|
|
TEST_ASSERT_EQUAL_INT(1, rc);
|
|
TEST_ASSERT_NOT_NULL(ctx.alloc);
|
|
TEST_ASSERT_EQUAL_STRING(first_in_order->name, ctx.alloc->allocs[0]->host);
|
|
|
|
sched_ctx_fini(&ctx);
|
|
}
|
|
|
|
/*
|
|
* Req 9.1, fixture 4: a select-failing host, THEN a resource-failing
|
|
* host, THEN a succeeding host, in that exact order -- proves the
|
|
* composition correctly skips two DIFFERENT kinds of failure back to back
|
|
* without short-circuiting (stopping early) or mis-skipping (e.g.
|
|
* skipping past the eventual success too).
|
|
*/
|
|
static void
|
|
test_select_fail_then_resource_fail_then_success_does_not_short_circuit(void)
|
|
{
|
|
xce_scheduler_t sched;
|
|
xce_resource_t* slots_res;
|
|
int rack_idx;
|
|
xce_sched_worker_t* sel_fail;
|
|
xce_sched_worker_t* res_fail;
|
|
xce_sched_worker_t* success;
|
|
xce_sched_obj_t obj;
|
|
xce_primary_schedobj_t primary_obj;
|
|
xce_primary_resreq_t primary;
|
|
xce_select_node_t resname, num, eq;
|
|
xce_select_t select;
|
|
xce_sched_ctx_t ctx;
|
|
int rc;
|
|
|
|
memset(&sched, 0, sizeof(sched));
|
|
TEST_ASSERT_EQUAL_INT(0, xce_sched_core_init(&(sched.core)));
|
|
|
|
slots_res = register_slots_resource(sched.core.resmgr);
|
|
TEST_ASSERT_NOT_NULL(slots_res);
|
|
rack_idx = slots_res->idx + 1;
|
|
|
|
sel_fail = add_worker_with_slots_and_rack(&(sched.core), "sel-fail",
|
|
slots_res->idx, 8.0f, rack_idx,
|
|
2.0 /* wrong rack */);
|
|
res_fail = add_worker_with_slots_and_rack(&(sched.core), "res-fail",
|
|
slots_res->idx, 0.0f, rack_idx,
|
|
1.0 /* right rack, no slots */);
|
|
success = add_worker_with_slots_and_rack(&(sched.core), "success",
|
|
slots_res->idx, 4.0f, rack_idx,
|
|
1.0 /* right rack, plenty */);
|
|
|
|
make_rack_eq_select(&resname, &num, &eq, &select, 1.0, rack_idx);
|
|
|
|
memset(&primary, 0, sizeof(primary));
|
|
primary.minTasks = 1;
|
|
primary.maxTasks = 1;
|
|
primary.select = &select;
|
|
|
|
init_primary_obj(&obj, &primary_obj, &primary);
|
|
|
|
rc = drive_allocator(&sched, &obj, &primary, &ctx);
|
|
|
|
TEST_ASSERT_EQUAL_INT(1, rc);
|
|
TEST_ASSERT_NOT_NULL(ctx.alloc);
|
|
TEST_ASSERT_EQUAL_STRING("success", ctx.alloc->allocs[0]->host);
|
|
|
|
assert_uncommitted_host_debit(&ctx, success, slots_res->idx, 1.0f);
|
|
assert_uncommitted_host_debit(&ctx, sel_fail, slots_res->idx, 0.0f);
|
|
assert_uncommitted_host_debit(&ctx, res_fail, slots_res->idx, 0.0f);
|
|
|
|
sched_ctx_fini(&ctx);
|
|
}
|
|
|
|
int main(void)
|
|
{
|
|
UNITY_BEGIN();
|
|
RUN_TEST(
|
|
test_three_hosts_only_resource_satisfying_host_is_chosen_and_debited);
|
|
RUN_TEST(
|
|
test_host_failing_select_filter_is_skipped_despite_plenty_of_resources);
|
|
RUN_TEST(
|
|
test_pick_matches_first_host_in_the_shared_workers_iteration_order);
|
|
RUN_TEST(
|
|
test_select_fail_then_resource_fail_then_success_does_not_short_circuit);
|
|
return UNITY_END();
|
|
}
|