/* * test_alloc_equivalence_seqjob.c - scheduler-allocator-runner task 2.1: * characterization of the sequential-job allocator (Req 4.5, 5.4, 5.8, * 7.2), driven only through the registered plugin->job_alloc_fn of * seqjob_allocator_new_plugin() (the framework runner since task 4.2), * with the Req 2.2 and 5.5 changes of task 4.2 pinned. Each fixture asserts * result code, hosts, per-host records, shared records, per-host and shared * uncommitted amounts and pend reasons. * * Out-of-memory injection (task 2.2 copies it), only at points that stay * the same across the switch: the sched_make_allocs() stub failing on call * N (1 = claimed host's records, 2 = shared records) and -Wl,--wrap of * libxce make_host_alloc/make_job_alloc (meson.build) failing on call N. * The host-array allocation is not injected (its origin changes; task 3.3). * * Multi-host pending (Req 3.7), more shared requests than short ones * (bug-1893), the plan shape and missing inputs (Req 5.1) are in * test_alloc_equivalence_seqjob_changes.c. COMPOUND layouts where the * positional uncommitted read (bug-1892) matters are kept out. */ #include #include #include #include #include "preason.h" #include "resobj.h" #include "resreq.h" #include "sched/alloc_iter.h" #include "scheduler.h" #include "xcealloc.h" #include "xstring.h" /* White-box sources under test. */ #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 (same convention as test_seqjob_allocator.c) */ /* ---------------------------------------------------------------------- */ int plugin_register(void* sched, void* plugin) { (void)sched; (void)plugin; return 0; } void plugin_unregister(void* arg) { (void)arg; } /* Stands in for plugin.c's fan-out with exactly one plugin (seqjob's own * prepare hook): reset the request vectors exactly as plugin.c's * plugin_prepare_allocation() does (job-level pass: .shared and .host, * bug-1883; per-host pass: .host), then call the hook. */ 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); xvec_idval_fini(&(ctx->alloc_req.host)); xvec_idval_init(&(ctx->alloc_req.host), 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; fake_plugin.core = sched; return seqjob_prepare_allocation(&fake_plugin, ctx); } int plugin_job_update(void* s, xce_sched_job_t* j) { return (void)s, (void)j, 0; } int plugin_queue_update(void* s, xce_sched_queue_t* q) { return (void)s, (void)q, 0; } int plugin_job_rusage_update(void* s, xce_sched_job_t* j, xce_job_rusages_t* r) { return (void)s, (void)j, (void)r, 0; } /* ---------------------------------------------------------------------- */ /* Out-of-memory injection */ /* ---------------------------------------------------------------------- */ /* Each counter counts every call; a fail_on value N > 0 makes call N fail * (NULL, errno ENOMEM). 0 disarms. oom_injection_reset() clears all. */ static int g_make_allocs_calls = 0; static int g_make_allocs_fail_on = 0; static int g_host_alloc_calls = 0; static int g_host_alloc_fail_on = 0; static int g_job_alloc_calls = 0; static int g_job_alloc_fail_on = 0; static void oom_injection_reset(void) { g_make_allocs_calls = 0; g_make_allocs_fail_on = 0; g_host_alloc_calls = 0; g_host_alloc_fail_on = 0; g_job_alloc_calls = 0; g_job_alloc_fail_on = 0; } /* Stub for scheduler.c's sched_make_allocs(): builds real records with * make_alloc() (resource name from the resmgr, the requested value, empty * reservation name, duration -1 as for a NULL duration array), and * returns NULL for nres <= 0 as the real one does. instid is 0. */ 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; g_make_allocs_calls++; if ((g_make_allocs_fail_on > 0) && (g_make_allocs_calls == g_make_allocs_fail_on)) { errno = ENOMEM; return NULL; } if ((sched == NULL) || (nres <= 0) || (idxs == NULL) || (vals == NULL)) { errno = EINVAL; return NULL; } allocs = (xce_alloc_t**)calloc(nres, sizeof(*allocs)); if (allocs == NULL) { return NULL; } for (i = 0; i < nres; i++) { xce_resource_t* r = NULL; vget(sched->core.resmgr, idxs[i], (void**)&r); if (r == NULL) { continue; } allocs[i] = make_alloc(r->name, reserve, 0, vals[i], (duration != NULL) ? duration[i] : -1); if (allocs[i] == NULL) { free_ResAlloc_list(nres, allocs); errno = ENOMEM; return NULL; } } return allocs; } xce_host_alloc_t* __real_make_host_alloc(char*, int, xce_alloc_t**); xce_job_alloc_t* __real_make_job_alloc(uint64_t, int, xce_host_alloc_t**); xce_host_alloc_t* __wrap_make_host_alloc(char* host, int nalloc, xce_alloc_t** allocs) { g_host_alloc_calls++; if ((g_host_alloc_fail_on > 0) && (g_host_alloc_calls == g_host_alloc_fail_on)) { errno = ENOMEM; return NULL; } return __real_make_host_alloc(host, nalloc, allocs); } xce_job_alloc_t* __wrap_make_job_alloc(uint64_t jobid, int nhost, xce_host_alloc_t** hallocs) { g_job_alloc_calls++; if ((g_job_alloc_fail_on > 0) && (g_job_alloc_calls == g_job_alloc_fail_on)) { errno = ENOMEM; return NULL; } return __real_make_job_alloc(jobid, nhost, hallocs); } void setUp(void) { oom_injection_reset(); } void tearDown(void) { oom_injection_reset(); } /* ---------------------------------------------------------------------- */ /* Fixture */ /* ---------------------------------------------------------------------- */ #define HOST_A "eq-host-a" #define HOST_B "eq-host-b" typedef struct { xce_scheduler_t sched; xce_resource_t* slots; xce_resource_t* mem; xce_resource_t* rack; xce_resource_t* lic; /* shared (XCE_RESF_GLOBAL) */ xce_sched_plugin_t* plugin; xce_sched_obj_t obj; xce_primary_schedobj_t primary_obj; xce_sched_ctx_t ctx; int ctx_ready; } eq_fixture_t; /* One primary request: `mem` per task, `lic` per job (0 = not requested), * optional `rack == N` select. */ typedef struct { xce_primary_resreq_t req; rusage_value_t mem_rusage; rusage_value_t lic_rusage; xce_select_t select; xce_select_node_t sel_res, sel_num, sel_eq; } eq_primary_t; static xce_resource_t* eq_register_resource(var resmgr, const char* name, int flags) { xce_resource_t res; memset(&res, 0, sizeof(res)); res.name = (char*)name; res.flags = flags; return makeResource(resmgr, &res); } /* Shared pool "licenses" has `lic_total` capacity, none in use. */ static void eq_fixture_init(eq_fixture_t* f, float lic_total) { int idx[1]; float val[1]; memset(f, 0, sizeof(*f)); TEST_ASSERT_EQUAL_INT(0, xce_sched_core_init(&(f->sched.core))); /* xce_sched_core_fini() finalizes these; both drivers calloc them */ for (idx[0] = 0; idx[0] < 2; idx[0]++) { f->sched.core.pend_updates[idx[0]] = (dataq_t*)calloc(1, sizeof(dataq_t)); TEST_ASSERT_NOT_NULL(f->sched.core.pend_updates[idx[0]]); dataq_init(f->sched.core.pend_updates[idx[0]], NULL); } f->slots = eq_register_resource(f->sched.core.resmgr, "slots", 0); f->mem = eq_register_resource(f->sched.core.resmgr, "mem", 0); f->rack = eq_register_resource(f->sched.core.resmgr, "rack", 0); f->lic = eq_register_resource(f->sched.core.resmgr, "licenses", XCE_RESF_GLOBAL); TEST_ASSERT_NOT_NULL(f->slots); TEST_ASSERT_NOT_NULL(f->mem); TEST_ASSERT_NOT_NULL(f->rack); TEST_ASSERT_NOT_NULL(f->lic); idx[0] = f->lic->idx; val[0] = lic_total; put_resvals(&(f->sched.core.sharedInst), 1, idx, val); f->plugin = seqjob_allocator_new_plugin(); TEST_ASSERT_NOT_NULL(f->plugin); TEST_ASSERT_NOT_NULL(f->plugin->job_alloc_fn); f->plugin->sched = &(f->sched); f->plugin->core = &(f->sched.core); } static xce_sched_worker_t* eq_add_host(eq_fixture_t* f, const char* name, float slots, float mem, float rack) { xce_sched_worker_t* w; int idx[3] = { f->slots->idx, f->mem->idx, f->rack->idx }; float total[3] = { slots, mem, rack }; float inuse[2] = { 0.0f, 0.0f }; w = (xce_sched_worker_t*)calloc(1, sizeof(*w)); TEST_ASSERT_NOT_NULL(w); w->name = makeString((char*)name); TEST_ASSERT_NOT_NULL(w->name); zarray_init(&(w->resvals), 8, (zarray_item_killer_t)kill_sched_resval); zarray_init(&(w->resInUse), 8, (zarray_item_killer_t)kill_sched_resval); put_resvals(&(w->resvals), 3, idx, total); put_resvals(&(w->resInUse), 2, idx, inuse); TEST_ASSERT_TRUE(sched_add_worker(&(f->sched.core), w) >= 0); return w; } static void eq_primary_init(eq_primary_t* p, int max_tasks, float mem, float lic, const eq_fixture_t* f, int rack) { memset(p, 0, sizeof(*p)); p->req.minTasks = (max_tasks > 0) ? 1 : 0; p->req.maxTasks = max_tasks; xlist_init(&(p->req.rusages), 4); if (mem > 0.0f) { p->mem_rusage.res = (char*)"mem"; p->mem_rusage.value = mem; p->mem_rusage.per_flag = PER_TASK; xlist_store(&(p->req.rusages), &(p->mem_rusage)); } if (lic > 0.0f) { p->lic_rusage.res = (char*)"licenses"; p->lic_rusage.value = lic; p->lic_rusage.per_flag = PER_JOB; xlist_store(&(p->req.rusages), &(p->lic_rusage)); } if (rack > 0) { p->sel_res.type = SEL_RESNAME; p->sel_res.u.resname.res_idx = f->rack->idx; p->sel_res.u.resname.res_type = RES_TYPE_NUMERIC; p->sel_num.type = SEL_NUM; p->sel_num.u.numval = (double)rack; p->sel_eq.type = SEL_EQ; p->sel_eq.u.binop.left = &(p->sel_res); p->sel_eq.u.binop.right = &(p->sel_num); p->select.root = &(p->sel_eq); p->req.select = &(p->select); } } /* ctx over a PRIMARY obj for `p`, job-level prepare pass included * (sched_ctx_set_primary() -> plugin_prepare_allocation(), ctx->worker * NULL), exactly as sched_make_primary_allocation() drives it. */ static void eq_ctx_begin_primary(eq_fixture_t* f, eq_primary_t* p) { f->primary_obj.resreq = &(p->req); f->obj.tag = XCE_RESREQ_PRIMARY; f->obj.u.primary = &(f->primary_obj); TEST_ASSERT_EQUAL_INT(0, sched_ctx_init(&(f->ctx), &(f->sched.core), &(f->obj))); f->ctx_ready = 1; sched_ctx_set_primary(&(f->ctx), &(p->req)); } static int eq_alloc(eq_fixture_t* f) { return f->plugin->job_alloc_fn(f->plugin, &(f->ctx)); } static void eq_fixture_fini(eq_fixture_t* f) { if (f->ctx_ready) { free_JobAlloc_ptr(&(f->ctx.alloc)); free_PendReason_ptr(&(f->ctx.reason)); sched_ctx_fini(&(f->ctx)); } f->plugin->fini_fn(f->plugin); xce_sched_core_fini(&(f->sched.core)); /* xce_sched_core_init() creates the process-wide string table on every * call and xce_sched_core_fini() leaves it; release it per fixture */ xstring_table_fini(); } /* ---------------------------------------------------------------------- */ /* Assertion helpers */ /* ---------------------------------------------------------------------- */ static float eq_host_uncomit(eq_fixture_t* f, xce_sched_worker_t* w, xce_resource_t* r) { xvec_t* v = (xvec_t*)zarray_get(&(f->ctx.uncomitted.hosts), w->idx); return (v == NULL) ? 0.0f : ((float*)v->data)[r->idx]; } static float eq_shared_uncomit(eq_fixture_t* f) { return ((float*)f->ctx.uncomitted.shared->data)[f->lic->idx]; } static void eq_assert_record(xce_alloc_t* a, const char* resname, float value) { TEST_ASSERT_NOT_NULL(a); TEST_ASSERT_EQUAL_STRING(resname, a->resname); TEST_ASSERT_EQUAL_STRING("", a->reserve); TEST_ASSERT_EQUAL_FLOAT(value, a->value); TEST_ASSERT_EQUAL_INT(-1, a->duration); } /* One host allocation carrying mem then slots (seqjob's per-host prepare * computes the rusage wants first, then stores slots). */ static void eq_assert_host_mem_slots(xce_host_alloc_t* ha, const char* host, float mem) { TEST_ASSERT_NOT_NULL(ha); TEST_ASSERT_EQUAL_STRING(host, ha->host); TEST_ASSERT_EQUAL_INT(2, ha->nallocs); TEST_ASSERT_NOT_NULL(ha->allocs); eq_assert_record(ha->allocs[0], "mem", mem); eq_assert_record(ha->allocs[1], "slots", 1.0f); } static void eq_assert_reason_counts(eq_fixture_t* f, int main_reason, int nhost, int nres) { TEST_ASSERT_NOT_NULL(f->ctx.reason); TEST_ASSERT_NOT_NULL(f->ctx.reason->reasons); TEST_ASSERT_EQUAL_INT(main_reason, f->ctx.reason->reasons->main_reason); TEST_ASSERT_EQUAL_INT(nhost, f->ctx.reason->reasons->nhost_reasons); TEST_ASSERT_EQUAL_INT(nres, f->ctx.reason->reasons->nres_reasons); } static void eq_assert_res_reason(eq_fixture_t* f, int i, const char* host, const char* res) { xce_res_reason_t* rr = f->ctx.reason->reasons->res_based_reasons[i]; TEST_ASSERT_NOT_NULL(rr); TEST_ASSERT_EQUAL_STRING(host, rr->host); TEST_ASSERT_EQUAL_STRING(res, rr->resource); TEST_ASSERT_EQUAL_INT(PREASON_NO_RESOURCE, rr->reason); } static void eq_primary_fini(eq_primary_t* p) { xlist_fini(&(p->req.rusages)); } static void eq_assert_calls(int make_allocs, int host_alloc, int job_alloc) { TEST_ASSERT_EQUAL_INT(make_allocs, g_make_allocs_calls); TEST_ASSERT_EQUAL_INT(host_alloc, g_host_alloc_calls); TEST_ASSERT_EQUAL_INT(job_alloc, g_job_alloc_calls); } /* The standard satisfiable job: one host (4 slots, 8 mem, rack 1), 10 * shared licenses; the job wants 2 mem per task and 3 licenses per job. * Returns the job_alloc_fn result; the fixture is left for the caller. */ static int eq_run_standard(eq_fixture_t* f, eq_primary_t* p, xce_sched_worker_t** host) { eq_fixture_init(f, 10.0f); *host = eq_add_host(f, HOST_A, 4.0f, 8.0f, 1.0f); eq_primary_init(p, 1, 2.0f, 3.0f, f, 0); eq_ctx_begin_primary(f, p); return eq_alloc(f); } static void eq_finish(eq_fixture_t* f, eq_primary_t* p) { eq_fixture_fini(f); eq_primary_fini(p); } /* ---------------------------------------------------------------------- */ /* Fixtures */ /* ---------------------------------------------------------------------- */ /* 1. Success: one host allocation (mem, slots) and one shared record. */ static void test_seqjob_success_one_host_with_shared(void) { eq_fixture_t f; eq_primary_t p; xce_sched_worker_t* a; TEST_ASSERT_EQUAL_INT(1, eq_run_standard(&f, &p, &a)); TEST_ASSERT_NOT_NULL(f.ctx.alloc); TEST_ASSERT_EQUAL_UINT64(0, f.ctx.alloc->jobid); TEST_ASSERT_EQUAL_INT(1, f.ctx.alloc->nhosts); eq_assert_host_mem_slots(f.ctx.alloc->allocs[0], HOST_A, 2.0f); TEST_ASSERT_EQUAL_INT(1, f.ctx.alloc->nres_shared); eq_assert_record(f.ctx.alloc->allocs_shared[0], "licenses", 3.0f); TEST_ASSERT_EQUAL_FLOAT(2.0f, eq_host_uncomit(&f, a, f.mem)); TEST_ASSERT_EQUAL_FLOAT(1.0f, eq_host_uncomit(&f, a, f.slots)); TEST_ASSERT_EQUAL_FLOAT(3.0f, eq_shared_uncomit(&f)); eq_assert_reason_counts(&f, 0, 0, 0); eq_assert_calls(2, 1, 1); eq_finish(&f, &p); } /* 2. Pending on the one shared resource the job requests, which is short * (2 licenses, 3 wanted): no host is tried and nothing is counted. */ static int eq_run_short_shared(eq_fixture_t* f, eq_primary_t* p, xce_sched_worker_t** host) { eq_fixture_init(f, 2.0f); *host = eq_add_host(f, HOST_A, 4.0f, 8.0f, 1.0f); eq_primary_init(p, 1, 2.0f, 3.0f, f, 0); eq_ctx_begin_primary(f, p); return eq_alloc(f); } static void test_seqjob_pending_on_short_shared_resource(void) { eq_fixture_t f; eq_primary_t p; xce_sched_worker_t* a; TEST_ASSERT_EQUAL_INT(0, eq_run_short_shared(&f, &p, &a)); TEST_ASSERT_NULL(f.ctx.alloc); TEST_ASSERT_NOT_NULL(f.ctx.reason); TEST_ASSERT_EQUAL_INT(0, f.ctx.reason->reasons->main_reason); TEST_ASSERT_EQUAL_INT(0, f.ctx.reason->reasons->nhost_reasons); TEST_ASSERT_NULL(zarray_get(&(f.ctx.uncomitted.hosts), a->idx)); TEST_ASSERT_EQUAL_FLOAT(0.0f, eq_shared_uncomit(&f)); eq_assert_calls(0, 0, 0); eq_finish(&f, &p); } /* 2, resource reasons (Req 2.2, bug-1893): exactly one host-less * NO_RESOURCE reason (host stored as ""), naming "licenses". */ static void test_seqjob_pending_on_short_shared_names_it(void) { eq_fixture_t f; eq_primary_t p; xce_sched_worker_t* a; TEST_ASSERT_EQUAL_INT(0, eq_run_short_shared(&f, &p, &a)); eq_assert_reason_counts(&f, 0, 0, 1); eq_assert_res_reason(&f, 0, "", "licenses"); eq_finish(&f, &p); } /* 3. Pending on hosts: the only candidate has a free slot but too little * mem. The pre-filter state (slots only) lets it through, and the per- * host check records NO_RESOURCE for (host, mem). The met shared request * stays counted as uncommitted. */ static void test_seqjob_pending_single_host_short_of_mem(void) { eq_fixture_t f; eq_primary_t p; xce_sched_worker_t* a; eq_fixture_init(&f, 10.0f); a = eq_add_host(&f, HOST_A, 4.0f, 1.0f, 1.0f); eq_primary_init(&p, 1, 4.0f, 3.0f, &f, 0); eq_ctx_begin_primary(&f, &p); TEST_ASSERT_EQUAL_INT(0, eq_alloc(&f)); TEST_ASSERT_NULL(f.ctx.alloc); eq_assert_reason_counts(&f, 0, 0, 1); eq_assert_res_reason(&f, 0, HOST_A, "mem"); TEST_ASSERT_EQUAL_FLOAT(0.0f, eq_host_uncomit(&f, a, f.mem)); TEST_ASSERT_EQUAL_FLOAT(0.0f, eq_host_uncomit(&f, a, f.slots)); TEST_ASSERT_EQUAL_FLOAT(3.0f, eq_shared_uncomit(&f)); eq_assert_calls(0, 0, 0); eq_finish(&f, &p); } /* 4a. Out of memory building the claimed host's allocation records * (sched_make_allocs() call 1). Per-host debit: see _releases_debit. */ static void test_seqjob_oom_host_records_returns_enomem(void) { eq_fixture_t f; eq_primary_t p; xce_sched_worker_t* a; g_make_allocs_fail_on = 1; TEST_ASSERT_EQUAL_INT(-ENOMEM, eq_run_standard(&f, &p, &a)); TEST_ASSERT_NULL(f.ctx.alloc); TEST_ASSERT_EQUAL_FLOAT(3.0f, eq_shared_uncomit(&f)); eq_assert_reason_counts(&f, 0, 0, 0); eq_assert_calls(1, 0, 0); eq_finish(&f, &p); } /* 4b. Out of memory in make_host_alloc(). Per-host debit: see * _releases_debit. */ static void test_seqjob_oom_host_alloc_returns_enomem(void) { eq_fixture_t f; eq_primary_t p; xce_sched_worker_t* a; g_host_alloc_fail_on = 1; TEST_ASSERT_EQUAL_INT(-ENOMEM, eq_run_standard(&f, &p, &a)); TEST_ASSERT_NULL(f.ctx.alloc); TEST_ASSERT_EQUAL_FLOAT(3.0f, eq_shared_uncomit(&f)); eq_assert_reason_counts(&f, 0, 0, 0); eq_assert_calls(1, 1, 0); eq_finish(&f, &p); } /* 4a/4b per-host debit (Req 5.5): the claim was not turned into a host * allocation, so the claimed host's debit is released. */ static void test_seqjob_oom_host_stage_releases_debit(void) { int stage; for (stage = 0; stage < 2; stage++) { eq_fixture_t f; eq_primary_t p; xce_sched_worker_t* a; oom_injection_reset(); if (stage == 0) { g_make_allocs_fail_on = 1; /* host allocation records */ } else { g_host_alloc_fail_on = 1; /* make_host_alloc() */ } TEST_ASSERT_EQUAL_INT(-ENOMEM, eq_run_standard(&f, &p, &a)); TEST_ASSERT_EQUAL_FLOAT(0.0f, eq_host_uncomit(&f, a, f.mem)); TEST_ASSERT_EQUAL_FLOAT(0.0f, eq_host_uncomit(&f, a, f.slots)); eq_finish(&f, &p); } } /* 4c. Out of memory in make_job_alloc(); the debit stays (also after the * switch: the host allocation was built). */ static void test_seqjob_oom_job_alloc_returns_enomem_debit_stays(void) { eq_fixture_t f; eq_primary_t p; xce_sched_worker_t* a; g_job_alloc_fail_on = 1; TEST_ASSERT_EQUAL_INT(-ENOMEM, eq_run_standard(&f, &p, &a)); TEST_ASSERT_NULL(f.ctx.alloc); TEST_ASSERT_EQUAL_FLOAT(2.0f, eq_host_uncomit(&f, a, f.mem)); TEST_ASSERT_EQUAL_FLOAT(1.0f, eq_host_uncomit(&f, a, f.slots)); TEST_ASSERT_EQUAL_FLOAT(3.0f, eq_shared_uncomit(&f)); eq_assert_reason_counts(&f, 0, 0, 0); eq_assert_calls(1, 1, 1); eq_finish(&f, &p); } /* 4d. Out of memory building the shared records (sched_make_allocs() * call 2); the debit stays. */ static void test_seqjob_oom_shared_records_returns_enomem_debit_stays(void) { eq_fixture_t f; eq_primary_t p; xce_sched_worker_t* a; g_make_allocs_fail_on = 2; TEST_ASSERT_EQUAL_INT(-ENOMEM, eq_run_standard(&f, &p, &a)); TEST_ASSERT_NULL(f.ctx.alloc); TEST_ASSERT_EQUAL_FLOAT(2.0f, eq_host_uncomit(&f, a, f.mem)); TEST_ASSERT_EQUAL_FLOAT(1.0f, eq_host_uncomit(&f, a, f.slots)); TEST_ASSERT_EQUAL_FLOAT(3.0f, eq_shared_uncomit(&f)); eq_assert_reason_counts(&f, 0, 0, 0); eq_assert_calls(2, 1, 1); eq_finish(&f, &p); } /* 5. A task count other than 1 reaching seqjob directly: its prepare hook * is a no-op for maxTasks != 1, so both request vectors stay empty and the * job is placed on the first host with no resource entries at all. */ static void eq_check_task_count(int max_tasks) { eq_fixture_t f; eq_primary_t p; xce_sched_worker_t* a; eq_fixture_init(&f, 10.0f); a = eq_add_host(&f, HOST_A, 4.0f, 8.0f, 1.0f); eq_primary_init(&p, max_tasks, 2.0f, 3.0f, &f, 0); eq_ctx_begin_primary(&f, &p); TEST_ASSERT_EQUAL_INT(1, eq_alloc(&f)); TEST_ASSERT_NOT_NULL(f.ctx.alloc); TEST_ASSERT_EQUAL_INT(1, f.ctx.alloc->nhosts); TEST_ASSERT_NOT_NULL(f.ctx.alloc->allocs[0]); TEST_ASSERT_EQUAL_STRING(HOST_A, f.ctx.alloc->allocs[0]->host); TEST_ASSERT_EQUAL_INT(0, f.ctx.alloc->allocs[0]->nallocs); TEST_ASSERT_NULL(f.ctx.alloc->allocs[0]->allocs); TEST_ASSERT_EQUAL_INT(0, f.ctx.alloc->nres_shared); TEST_ASSERT_NULL(f.ctx.alloc->allocs_shared); TEST_ASSERT_EQUAL_FLOAT(0.0f, eq_host_uncomit(&f, a, f.mem)); TEST_ASSERT_EQUAL_FLOAT(0.0f, eq_host_uncomit(&f, a, f.slots)); TEST_ASSERT_EQUAL_FLOAT(0.0f, eq_shared_uncomit(&f)); eq_assert_reason_counts(&f, 0, 0, 0); eq_assert_calls(1, 1, 1); eq_finish(&f, &p); } static void test_seqjob_task_count_zero_reaches_plugin(void) { eq_check_task_count(0); } static void test_seqjob_task_count_two_reaches_plugin(void) { eq_check_task_count(2); } /* 6. COMPOUND: one ctx, two primaries allocated in turn as * sched_make_compound_alloc_() does. The selects (rack 1 / rack 2) keep * the second primary off the host the first claimed, so the positional * uncommitted read (bug-1892) cannot touch the expected values. Each * primary is sized from its own request (Req 5.8, bug-1768). */ static void test_seqjob_compound_second_primary_sized_from_own_request(void) { eq_fixture_t f; eq_primary_t p1; eq_primary_t p2; xce_compound_resreq_t cres; xce_compound_schedobj_t cobj; xce_job_alloc_t* first = NULL; xce_sched_worker_t* a; xce_sched_worker_t* b; eq_fixture_init(&f, 10.0f); a = eq_add_host(&f, HOST_A, 1.0f, 8.0f, 1.0f); b = eq_add_host(&f, HOST_B, 1.0f, 8.0f, 2.0f); eq_primary_init(&p1, 1, 2.0f, 3.0f, &f, 1); eq_primary_init(&p2, 1, 3.0f, 0.0f, &f, 2); xlist_init(&(cres.primaries), 2); xlist_store(&(cres.primaries), &(p1.req)); xlist_store(&(cres.primaries), &(p2.req)); memset(&cobj, 0, sizeof(cobj)); cobj.owner = &(f.obj); cobj.resreq = &cres; f.obj.tag = XCE_RESREQ_COMPOUND; f.obj.u.compound = &cobj; TEST_ASSERT_EQUAL_INT(0, sched_ctx_init(&(f.ctx), &(f.sched.core), &(f.obj))); f.ctx_ready = 1; sched_ctx_set_primary(&(f.ctx), &(p1.req)); TEST_ASSERT_EQUAL_INT(1, eq_alloc(&f)); MOVE_PTR(first, f.ctx.alloc); sched_ctx_set_primary(&(f.ctx), &(p2.req)); TEST_ASSERT_EQUAL_INT(1, eq_alloc(&f)); TEST_ASSERT_NOT_NULL(first); TEST_ASSERT_EQUAL_INT(1, first->nhosts); eq_assert_host_mem_slots(first->allocs[0], HOST_A, 2.0f); TEST_ASSERT_EQUAL_INT(1, first->nres_shared); eq_assert_record(first->allocs_shared[0], "licenses", 3.0f); TEST_ASSERT_NOT_NULL(f.ctx.alloc); TEST_ASSERT_EQUAL_INT(1, f.ctx.alloc->nhosts); eq_assert_host_mem_slots(f.ctx.alloc->allocs[0], HOST_B, 3.0f); TEST_ASSERT_EQUAL_INT(0, f.ctx.alloc->nres_shared); TEST_ASSERT_NULL(f.ctx.alloc->allocs_shared); TEST_ASSERT_EQUAL_FLOAT(2.0f, eq_host_uncomit(&f, a, f.mem)); TEST_ASSERT_EQUAL_FLOAT(1.0f, eq_host_uncomit(&f, a, f.slots)); TEST_ASSERT_EQUAL_FLOAT(3.0f, eq_host_uncomit(&f, b, f.mem)); TEST_ASSERT_EQUAL_FLOAT(1.0f, eq_host_uncomit(&f, b, f.slots)); TEST_ASSERT_EQUAL_FLOAT(3.0f, eq_shared_uncomit(&f)); eq_assert_reason_counts(&f, 0, 0, 0); eq_assert_calls(3, 2, 2); free_JobAlloc_ptr(&first); xlist_fini(&(cres.primaries)); eq_fixture_fini(&f); eq_primary_fini(&p1); eq_primary_fini(&p2); } int main(void) { UNITY_BEGIN(); RUN_TEST(test_seqjob_success_one_host_with_shared); RUN_TEST(test_seqjob_pending_on_short_shared_resource); RUN_TEST(test_seqjob_pending_on_short_shared_names_it); RUN_TEST(test_seqjob_pending_single_host_short_of_mem); RUN_TEST(test_seqjob_oom_host_records_returns_enomem); RUN_TEST(test_seqjob_oom_host_alloc_returns_enomem); RUN_TEST(test_seqjob_oom_host_stage_releases_debit); RUN_TEST(test_seqjob_oom_job_alloc_returns_enomem_debit_stays); RUN_TEST(test_seqjob_oom_shared_records_returns_enomem_debit_stays); RUN_TEST(test_seqjob_task_count_zero_reaches_plugin); RUN_TEST(test_seqjob_task_count_two_reaches_plugin); RUN_TEST(test_seqjob_compound_second_primary_sized_from_own_request); return UNITY_END(); }