feat(scheduler-sim-decision-trace): hook resource-requirement branching

Task 3.2: single requirements, top-level compounds, alternatives and
members push their path level and record one begin and one end; member
failures record the members not tried, merge failures and skipped
alternatives are separate entries, the chosen alternative or all-failed
is recorded, and each compound attempt's reasons are captured before the
job's stored reason is replaced. Scheduling results are unchanged.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Rongsong Shen 2026-10-08 11:23:16 +00:00
parent ed7b889bb7
commit 542c6e602a
Signed by untrusted user: rshen
GPG key ID: 80533536B0EB651C
4 changed files with 680 additions and 3 deletions

View file

@ -20,6 +20,7 @@
#include "resobj.h"
#include "resreq.h"
#include "sched/alloc_cache.h"
#include "sched/trace.h"
#include "scheduler.h"
#include "utils.h"
#include "xcealloc.h"
@ -343,16 +344,29 @@ static int sched_make_compound_alloc_(xce_sched_core_t* core,
int i;
xce_sched_ctx_t ctx;
xce_job_alloc_t* merged = NULL;
int m = resreq->primaries.count;
int rc = 0;
sched_ctx_init(&ctx, core, obj);
for (i = 0; i < resreq->primaries.count; i++) {
for (i = 0; i < m; i++) {
xce_primary_resreq_t* primary
= (xce_primary_resreq_t*)xlist_get(&(resreq->primaries), i);
sched_ctx_set_primary(&ctx, primary);
/* Decision trace (3.1-3.3): member i+1 of m under the container the
* caller pushed; a failing member lists the ones after it as not
* tried. */
SCHED_TRACE(core,
sched_trace_path_push(core->trace_rec,
XCE_TRACE_PATH_MEMBER, i + 1, m));
SCHED_TRACE(core,
sched_trace_resreq_begin(core->trace_rec, primary, 0));
rc = sched_make_primary_alloc_(core, &ctx);
SCHED_TRACE(core,
sched_trace_resreq_end(core->trace_rec, rc,
(rc <= 0) ? (m - (i + 1)) : 0));
SCHED_TRACE(core, sched_trace_path_pop(core->trace_rec));
if (rc <= 0) {
break;
}
@ -368,6 +382,10 @@ static int sched_make_compound_alloc_(xce_sched_core_t* core,
free_JobAlloc_ptr(&(ctx.alloc));
if (mrc < 0) {
/* member i+1 was satisfied; the merge failure is a
* separate entry, not a second end (3.3) */
SCHED_TRACE(core, sched_trace_member_failed(core->trace_rec,
i + 1, m, mrc));
rc = mrc;
break;
}
@ -388,6 +406,13 @@ static int sched_make_compound_alloc_(xce_sched_core_t* core,
}
}
/* Decision trace (3.5): a failed attempt's reasons, before obj->reason
* is replaced by them (or by a later alternative's). */
SCHED_TRACE(
core, if (rc <= 0) {
sched_trace_capture_attempt_reasons(core->trace_rec, ctx.reason);
});
if (ctx.reason != NULL) {
/* bug-1884: the previous attempt's reason (its decision has
* been published) or an earlier alternative's */
@ -410,8 +435,9 @@ static int sched_make_alter_allocation(xce_sched_core_t* core,
xce_sched_obj_t* obj)
{
int i;
int n = obj->u.alters.count;
for (i = 0; i < obj->u.alters.count; i++) {
for (i = 0; i < n; i++) {
/* bug-1769: u.alters holds xce_compound_schedobj_t* (every builder:
* core_events.c, snapshot_load.c, schedobj.c), not xce_sched_obj_t*
* -- reading them as the latter overran the 24-byte allocation.
@ -421,15 +447,31 @@ static int sched_make_alter_allocation(xce_sched_core_t* core,
int rc;
if ((alt == NULL) || (alt->resreq == NULL)) {
/* Decision trace (3.6): skipped in its position */
SCHED_TRACE(core,
sched_trace_alt_skipped(core->trace_rec, i + 1, n));
continue;
}
SCHED_TRACE(core, sched_trace_path_push(core->trace_rec,
XCE_TRACE_PATH_ALT, i + 1, n));
SCHED_TRACE(core,
sched_trace_resreq_begin(core->trace_rec, NULL,
alt->resreq->primaries.count));
rc = sched_make_compound_alloc_(core, obj, alt->resreq);
SCHED_TRACE(core, sched_trace_resreq_end(core->trace_rec, rc, 0));
SCHED_TRACE(core, sched_trace_path_pop(core->trace_rec));
if (rc > 0) {
/* Decision trace (3.4): chosen; the rest are not tried */
SCHED_TRACE(core,
sched_trace_alt_result(core->trace_rec, i + 1, n));
return rc;
}
}
/* Decision trace (3.5): every alternative failed */
SCHED_TRACE(core, sched_trace_alt_result(core->trace_rec, 0, n));
/* FIXME: merge pend reason from all children */
return 0;
@ -446,12 +488,28 @@ int sched_make_allocation(xce_sched_core_t* core, xce_sched_obj_t* obj)
switch (obj->tag) {
case XCE_RESREQ_PRIMARY:
/* Decision trace (3.1, 3.2): the single requirement */
SCHED_TRACE(core, sched_trace_path_push(core->trace_rec,
XCE_TRACE_PATH_SINGLE, 1, 1));
SCHED_TRACE(core, sched_trace_resreq_begin(core->trace_rec,
obj->u.primary->resreq, 0));
rc = sched_make_primary_allocation(core, obj);
SCHED_TRACE(core, sched_trace_resreq_end(core->trace_rec, rc, 0));
SCHED_TRACE(core, sched_trace_path_pop(core->trace_rec));
break;
case XCE_RESREQ_COMPOUND:
/* Decision trace (3.1, 3.2): the top-level compound container */
SCHED_TRACE(core, sched_trace_path_push(core->trace_rec,
XCE_TRACE_PATH_COMPOUND, 1, 1));
SCHED_TRACE(core, sched_trace_resreq_begin(
core->trace_rec, NULL,
obj->u.compound->resreq->primaries.count));
rc = sched_make_compound_allocation(core, obj);
SCHED_TRACE(core, sched_trace_resreq_end(core->trace_rec, rc, 0));
SCHED_TRACE(core, sched_trace_path_pop(core->trace_rec));
break;
case XCE_RESREQ_ALTER:
/* Decision trace (3.4-3.6): per-alternative hooks in the loop */
rc = sched_make_alter_allocation(core, obj);
break;
default:

View file

@ -2654,3 +2654,29 @@ sched_trace_hooks_alloc_loop_test = executable(
test('scheduler trace hooks alloc loop test',
sched_trace_hooks_alloc_loop_test, suite: 'scheduler',
env: ['ASAN_OPTIONS=detect_leaks=0'])
# scheduler-sim-decision-trace task 3.2: the resource-requirement branching
# trace hooks (design.md "TraceHooks", PRIMARY / COMPOUND / ALTER / member
# rows; Requirements 3.1-3.6) on the real sched_make_allocation() chain over
# a bare simulation core. Real-scheduler_srcs link technique; libxce's
# merge_job_alloc() is wrapped for the merge failure.
sched_trace_hooks_resreq_test_srcs = ['test_trace_hooks_resreq.c']
foreach f : scheduler_srcs
if f != 'src/main.c'
sched_trace_hooks_resreq_test_srcs += ['../' + f]
endif
endforeach
sched_trace_hooks_resreq_test = executable(
'sched_trace_hooks_resreq_test',
sched_trace_hooks_resreq_test_srcs,
include_directories:[incdir],
dependencies: [scheduler_deps, unity_dep],
link_args: ['-Wl,--wrap=xcecli_start',
'-Wl,--wrap=xcecli_join',
'-Wl,--wrap=merge_job_alloc'],
install: false)
test('scheduler trace hooks resreq test',
sched_trace_hooks_resreq_test, suite: 'scheduler',
env: ['ASAN_OPTIONS=detect_leaks=0'])

View file

@ -0,0 +1,592 @@
/*
* test_trace_hooks_resreq.c - the resource-requirement branching trace hooks
* (scheduler-sim-decision-trace spec, task 3.2; design.md "TraceHooks",
* PRIMARY / top-level COMPOUND / ALTER loop / compound member rows;
* Requirements 3.1-3.6).
*
* Drives the real sched_make_allocation() chain and the real seqjob/parjob
* allocator plugins on a bare simulation core (same fixture as
* test_sched_alloc_from_stamp.c's simulation-mode test: no threads, the
* allocators bootstrapped with the sim wrappers), with a record set on
* core->trace_rec for the call, and checks the requirement-branch entries
* the record holds:
*
* - a single requirement: SINGLE 1/1, one begin with its task counts and
* requested resources, one end (3.1, 3.2);
* - a compound whose second member cannot be satisfied: each member's
* begin/end under COMPOUND 1/1, the failing member listing the members
* after it as not tried, then the attempt's failure reasons (3.2, 3.3);
* - a compound whose merge fails after a satisfied member: the member's
* end stays satisfied and a separate merge-failure entry follows (3.3);
* - an alternative job with a skipped, a failed and a chosen alternative,
* and one after it: the skip in its position, the failed alternative's
* reasons, the chosen one and nothing for the rest (3.4, 3.6);
* - an alternative job where every alternative fails: each alternative's
* own failure reasons, then "all failed" (3.5).
*
* Every scenario first runs without a record and then with one, and the
* return code, allocation and pend reason left on the scheduling object
* must be identical (Requirement 10: tracing never changes scheduling).
*
* Only the requirement-branch entry types are inspected, so entries other
* hooks add inside the allocator (plans, positions, hosts) do not matter.
* merge_job_alloc() (libxce) is wrapped at link time for the merge failure.
*/
#include <errno.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unity/unity.h>
#include "client.h"
#include "controller.capnp.h"
#include "core.h"
#include "decision.h"
#include "plugin.h"
#include "preason.h"
#include "resobj.h"
#include "resreq.h"
#include "sched/trace.h"
#include "sched/types.h"
#include "scheduler.h"
#include "sim.h"
#include "xcealloc.h"
#include "xstring.h"
extern void sim_bootstrap_seqjob_allocator(xce_sched_sim_driver_t* sim);
extern void sim_bootstrap_parjob_allocator(xce_sched_sim_driver_t* sim);
#define WORKER_A_NAME "resreq-host-a"
#define WORKER_B_NAME "resreq-host-b"
#define WORKER_SLOTS 2.0 /* 4 slots in the cluster */
#define QUEUE_NAME "resreq_queue"
/* ── link seams ─────────────────────────────────────────────────────── */
int __wrap_xcecli_start(xcecli_ctx_t c)
{
(void)c;
return 0;
}
int __wrap_xcecli_join(xcecli_ctx_t c, int role, hostattrs_t* attrs)
{
(void)c;
(void)role;
(void)attrs;
return 0;
}
extern int __real_merge_job_alloc(xce_job_alloc_t*, const xce_job_alloc_t*);
/* > 0: that many merges succeed, then the next one fails with -ENOMEM and
* the counter is disarmed. 0: every merge is real. */
static int g_merge_fail_after;
int __wrap_merge_job_alloc(xce_job_alloc_t* dst, const xce_job_alloc_t* src)
{
if (g_merge_fail_after > 0) {
if (--g_merge_fail_after == 0) {
return -ENOMEM;
}
}
return __real_merge_job_alloc(dst, src);
}
void setUp(void) { return; }
void tearDown(void) { return; }
/* ── fixture ────────────────────────────────────────────────────────── */
static xce_sched_sim_driver_t g_sim;
static xce_scheduler_t g_job_wrapper; /* make_sched_job() only */
static xce_sched_trace_ctl_t g_ctl;
static void add_sim_worker(xce_sched_core_t* core, const char* name,
double slots)
{
xce_sched_worker_t* worker
= (xce_sched_worker_t*)calloc(1, sizeof(*worker));
xce_resource_t* r = NULL;
xce_val_t* val = NULL;
TEST_ASSERT_NOT_NULL(worker);
worker->name = strdup(name);
worker->status = 0;
worker->idx = -1;
zarray_init(&(worker->resvals), 128,
(zarray_item_killer_t)kill_sched_resval);
zarray_init(&(worker->resInUse), 128,
(zarray_item_killer_t)kill_sched_resval);
TEST_ASSERT_TRUE(key_find(core->resmgr, (void*)"slots", (void**)&r) >= 0);
TEST_ASSERT_NOT_NULL(r);
val = (xce_val_t*)calloc(1, sizeof(*val));
TEST_ASSERT_NOT_NULL(val);
val->which = Value_u_fvalue;
val->u.fvalue = slots;
zarray_put(&(worker->resvals), r->idx, val);
TEST_ASSERT_TRUE(sched_add_worker(core, worker) >= 0);
}
static void start_sim(void)
{
xce_resource_t res;
xce_sched_queue_t* queue;
memset(&g_sim, 0, sizeof(g_sim));
TEST_ASSERT_EQUAL_INT(0, xce_sched_core_init(&(g_sim.core)));
memset(&res, 0, sizeof(res));
res.name = strdup("slots");
TEST_ASSERT_NOT_NULL(makeResource(g_sim.core.resmgr, &res));
sim_bootstrap_seqjob_allocator(&g_sim);
sim_bootstrap_parjob_allocator(&g_sim);
TEST_ASSERT_NOT_NULL(g_sim.core.seqjob_allocator);
TEST_ASSERT_NOT_NULL(g_sim.core.parjob_allocator);
queue = (xce_sched_queue_t*)calloc(1, sizeof(*queue));
TEST_ASSERT_NOT_NULL(queue);
queue->name = makeString(QUEUE_NAME);
TEST_ASSERT_TRUE(sched_add_queue(&(g_sim.core), queue) >= 0);
add_sim_worker(&(g_sim.core), WORKER_A_NAME, WORKER_SLOTS);
add_sim_worker(&(g_sim.core), WORKER_B_NAME, WORKER_SLOTS);
memset(&g_job_wrapper, 0, sizeof(g_job_wrapper));
TEST_ASSERT_EQUAL_INT(0, xce_sched_core_init(&(g_job_wrapper.core)));
g_job_wrapper.core.resmgr = g_sim.core.resmgr;
g_job_wrapper.core.workers = g_sim.core.workers;
g_job_wrapper.core.queues = g_sim.core.queues;
TEST_ASSERT_EQUAL_INT(0, xce_core_begin_session(&(g_sim.core)));
memset(&g_ctl, 0, sizeof(g_ctl));
g_ctl.all = true;
g_ctl.level = XCE_TRACE_LEVEL_FULL;
g_ctl.line_bound = XCE_TRACE_LINE_BOUND_DEFAULT;
g_ctl.session_no = 1;
g_ctl.policy_name = "builtin_fcfs";
}
static xce_sched_job_t* make_job(uint64_t jobid, const char* resreq,
xce_resreq_tag_t want_tag)
{
xce_job_t* j = (xce_job_t*)calloc(1, sizeof(*j));
job_submit_data_t* submit = (job_submit_data_t*)calloc(1, sizeof(*submit));
xce_sched_job_t* job;
TEST_ASSERT_NOT_NULL(j);
TEST_ASSERT_NOT_NULL(submit);
submit->queue = (char*)QUEUE_NAME;
submit->user = (char*)"resreq_user";
submit->project_name = (char*)"resreq_proj";
submit->resreq = (char*)resreq;
submit->mintasks = 1;
submit->maxtasks = 1;
j->jobid = jobid;
j->state = JOB_STAT_PEND;
j->submit = submit;
job = make_sched_job(&g_job_wrapper, j);
TEST_ASSERT_NOT_NULL(job);
TEST_ASSERT_NOT_NULL(job->schedobj);
TEST_ASSERT_EQUAL_INT_MESSAGE(want_tag, job->schedobj->tag,
"fixture: unexpected resreq kind");
if (want_tag == XCE_RESREQ_PRIMARY) {
job->schedobj->u.primary->resreq->minTasks = 1;
job->schedobj->u.primary->resreq->maxTasks = 1;
}
TEST_ASSERT_TRUE(sched_add_job(&(g_sim.core), job) >= 0);
return job;
}
/* What one sched_make_allocation() call left on the scheduling object. */
typedef struct {
int rc;
xce_job_alloc_t* alloc;
xce_pendreason_t* reason;
} run_result_t;
/* Runs the real allocation chain on obj, with a record on the core when
* `rec` is non-NULL, and takes the allocation and reason off obj. */
static run_result_t run_alloc(xce_sched_obj_t* obj, xce_sched_trace_rec_t* rec,
int merge_fail_after)
{
run_result_t r;
obj->alloc = NULL;
obj->reason = NULL;
g_merge_fail_after = merge_fail_after;
g_sim.core.trace_rec = rec;
r.rc = sched_make_allocation(&(g_sim.core), obj);
g_sim.core.trace_rec = NULL;
TEST_ASSERT_EQUAL_INT_MESSAGE(0, g_merge_fail_after,
"fixture: the merge failure was not hit");
r.alloc = obj->alloc;
r.reason = obj->reason;
obj->alloc = NULL;
obj->reason = NULL;
return r;
}
/* Runs obj without and then with a record, asserts the scheduling results
* are identical, and returns the record. */
static xce_sched_trace_rec_t* run_traced(xce_sched_job_t* job,
int merge_fail_after, int* rc_out)
{
xce_sched_trace_rec_t* rec;
run_result_t off;
run_result_t on;
char* why = NULL;
off = run_alloc(job->schedobj, NULL, merge_fail_after);
rec = sched_trace_rec_new(&g_ctl, job->jobid, XCE_TRACE_REC_DECISION);
TEST_ASSERT_NOT_NULL(rec);
on = run_alloc(job->schedobj, rec, merge_fail_after);
TEST_ASSERT_EQUAL_INT_MESSAGE(off.rc, on.rc,
"tracing changed the allocation result");
TEST_ASSERT_TRUE_MESSAGE(job_alloc_eq(off.alloc, on.alloc, &why),
why != NULL ? why : "allocation differs");
TEST_ASSERT_TRUE_MESSAGE((off.reason == NULL) == (on.reason == NULL),
"tracing changed whether a reason is left");
if (off.reason != NULL) {
TEST_ASSERT_TRUE_MESSAGE(
job_preason_eq(off.reason->reasons, on.reason->reasons, &why),
why != NULL ? why : "pend reason differs");
}
TEST_ASSERT_EQUAL_INT_MESSAGE(0, rec->resreq_open,
"a requirement begin has no end");
TEST_ASSERT_EQUAL_INT_MESSAGE(0, rec->path.depth, "a path push has no pop");
free_JobAlloc_ptr(&(off.alloc));
free_JobAlloc_ptr(&(on.alloc));
free_PendReason_ptr(&(off.reason));
free_PendReason_ptr(&(on.reason));
*rc_out = on.rc;
return rec;
}
/* ── entry checks ───────────────────────────────────────────────────── */
#define MAX_BRANCH 32
static bool is_branch_entry(sched_trace_entry_type_t t)
{
return (t == SCHED_TRACE_E_RESREQ_BEGIN) || (t == SCHED_TRACE_E_RESREQ_END)
|| (t == SCHED_TRACE_E_ALT_SKIPPED) || (t == SCHED_TRACE_E_ALT_RESULT)
|| (t == SCHED_TRACE_E_MEMBER_FAILED)
|| (t == SCHED_TRACE_E_ATTEMPT_REASONS);
}
/* The requirement-branch entries of rec, in the order made. */
static int branch_entries(const xce_sched_trace_rec_t* rec,
const sched_trace_entry_t** out)
{
const sched_trace_entry_t* e;
int n = 0;
for (e = rec->entries; e != NULL; e = e->next) {
if (is_branch_entry(e->type)) {
TEST_ASSERT_TRUE(n < MAX_BRANCH);
out[n++] = e;
}
}
return n;
}
#define K_NONE (-1)
static void assert_path(const sched_trace_entry_t* e, int k0, int i0, int n0,
int k1, int i1, int n1)
{
int depth = (k0 == K_NONE) ? 0 : ((k1 == K_NONE) ? 1 : 2);
TEST_ASSERT_EQUAL_INT_MESSAGE(depth, e->path.depth, "path depth");
if (depth >= 1) {
TEST_ASSERT_EQUAL_INT(k0, e->path.lvl[0].kind);
TEST_ASSERT_EQUAL_INT(i0, e->path.lvl[0].i);
TEST_ASSERT_EQUAL_INT(n0, e->path.lvl[0].n);
}
if (depth == 2) {
TEST_ASSERT_EQUAL_INT(k1, e->path.lvl[1].kind);
TEST_ASSERT_EQUAL_INT(i1, e->path.lvl[1].i);
TEST_ASSERT_EQUAL_INT(n1, e->path.lvl[1].n);
}
}
static void assert_type(const sched_trace_entry_t* e,
sched_trace_entry_type_t t)
{
TEST_ASSERT_EQUAL_INT_MESSAGE(t, e->type, "unexpected entry type");
}
/* A member/single begin: task counts and one requested resource `res`. */
static void assert_primary_begin(const sched_trace_entry_t* e, int min_tasks,
int max_tasks, const char* res, float value)
{
int k;
assert_type(e, SCHED_TRACE_E_RESREQ_BEGIN);
TEST_ASSERT_FALSE(e->u.resreq_begin.container);
TEST_ASSERT_EQUAL_INT(min_tasks, e->u.resreq_begin.min_tasks);
TEST_ASSERT_EQUAL_INT(max_tasks, e->u.resreq_begin.max_tasks);
TEST_ASSERT_TRUE(e->u.resreq_begin.nreq > 0);
for (k = 0; k < e->u.resreq_begin.nreq; k++) {
const sched_trace_req_t* r = &e->u.resreq_begin.req[k];
if ((r->name != NULL) && (strcmp(r->name, res) == 0)) {
TEST_ASSERT_EQUAL_FLOAT(value, r->value);
return;
}
}
TEST_FAIL_MESSAGE("the requested resource is not in the begin entry");
}
static void assert_container_begin(const sched_trace_entry_t* e, int nmembers)
{
assert_type(e, SCHED_TRACE_E_RESREQ_BEGIN);
TEST_ASSERT_TRUE(e->u.resreq_begin.container);
TEST_ASSERT_EQUAL_INT(nmembers, e->u.resreq_begin.nmembers);
}
/* rc_sign: 1 satisfied, 0 not satisfied, -1 internal error */
static void assert_end(const sched_trace_entry_t* e, int rc_sign,
int not_tried_after)
{
assert_type(e, SCHED_TRACE_E_RESREQ_END);
if (rc_sign > 0) {
TEST_ASSERT_TRUE_MESSAGE(e->u.resreq_end.rc > 0, "expected satisfied");
} else if (rc_sign == 0) {
TEST_ASSERT_EQUAL_INT_MESSAGE(0, e->u.resreq_end.rc,
"expected not satisfied");
} else {
TEST_ASSERT_TRUE_MESSAGE(e->u.resreq_end.rc < 0, "expected an error");
}
TEST_ASSERT_EQUAL_INT(not_tried_after, e->u.resreq_end.not_tried_after);
}
/* ── tests ──────────────────────────────────────────────────────────── */
static void test_single_requirement(void)
{
xce_sched_job_t* job
= make_job(632001, "rusage[mem=1]", XCE_RESREQ_PRIMARY);
const sched_trace_entry_t* b[MAX_BRANCH];
xce_sched_trace_rec_t* rec;
int rc;
int n;
rec = run_traced(job, 0, &rc);
TEST_ASSERT_TRUE(rc > 0);
n = branch_entries(rec, b);
TEST_ASSERT_EQUAL_INT_MESSAGE(2, n, "a single requirement: begin + end");
assert_primary_begin(b[0], 1, 1, "mem", 1.0f);
assert_path(b[0], XCE_TRACE_PATH_SINGLE, 1, 1, K_NONE, 0, 0);
assert_end(b[1], 1, 0);
assert_path(b[1], XCE_TRACE_PATH_SINGLE, 1, 1, K_NONE, 0, 0);
sched_trace_rec_free(rec);
}
static void test_compound_failing_member(void)
{
xce_sched_job_t* job = make_job(
632002, "1*{rusage[mem=1]} + 9*{rusage[mem=2]} + 1*{rusage[mem=3]}",
XCE_RESREQ_COMPOUND);
const sched_trace_entry_t* b[MAX_BRANCH];
xce_sched_trace_rec_t* rec;
int rc;
int n;
rec = run_traced(job, 0, &rc);
TEST_ASSERT_EQUAL_INT(0, rc);
n = branch_entries(rec, b);
TEST_ASSERT_EQUAL_INT(7, n);
assert_container_begin(b[0], 3);
assert_path(b[0], XCE_TRACE_PATH_COMPOUND, 1, 1, K_NONE, 0, 0);
assert_primary_begin(b[1], 1, 1, "mem", 1.0f);
assert_path(b[1], XCE_TRACE_PATH_COMPOUND, 1, 1, XCE_TRACE_PATH_MEMBER, 1,
3);
assert_end(b[2], 1, 0);
assert_primary_begin(b[3], 9, 9, "mem", 2.0f);
assert_path(b[3], XCE_TRACE_PATH_COMPOUND, 1, 1, XCE_TRACE_PATH_MEMBER, 2,
3);
assert_end(b[4], 0, 1); /* point of failure: member 3/3 not tried */
assert_path(b[4], XCE_TRACE_PATH_COMPOUND, 1, 1, XCE_TRACE_PATH_MEMBER, 2,
3);
assert_type(b[5], SCHED_TRACE_E_ATTEMPT_REASONS);
TEST_ASSERT_TRUE_MESSAGE(b[5]->u.reasons.ncodes > 0,
"the failed attempt's reasons are not captured");
assert_path(b[5], XCE_TRACE_PATH_COMPOUND, 1, 1, K_NONE, 0, 0);
assert_end(b[6], 0, 0);
assert_path(b[6], XCE_TRACE_PATH_COMPOUND, 1, 1, K_NONE, 0, 0);
sched_trace_rec_free(rec);
}
static void test_compound_merge_failure(void)
{
xce_sched_job_t* job = make_job(
632003, "1*{rusage[mem=1]} + 1*{rusage[mem=2]} + 1*{rusage[mem=3]}",
XCE_RESREQ_COMPOUND);
const sched_trace_entry_t* b[MAX_BRANCH];
xce_sched_trace_rec_t* rec;
int rc;
int n;
/* the first merge (after member 2) fails */
rec = run_traced(job, 1, &rc);
TEST_ASSERT_EQUAL_INT(-ENOMEM, rc);
n = branch_entries(rec, b);
TEST_ASSERT_EQUAL_INT(8, n);
assert_container_begin(b[0], 3);
assert_primary_begin(b[1], 1, 1, "mem", 1.0f);
assert_end(b[2], 1, 0);
assert_primary_begin(b[3], 1, 1, "mem", 2.0f);
assert_path(b[3], XCE_TRACE_PATH_COMPOUND, 1, 1, XCE_TRACE_PATH_MEMBER, 2,
3);
assert_end(b[4], 1, 0); /* the member itself was satisfied */
assert_type(b[5], SCHED_TRACE_E_MEMBER_FAILED);
TEST_ASSERT_EQUAL_INT(2, b[5]->u.member_failed.j);
TEST_ASSERT_EQUAL_INT(3, b[5]->u.member_failed.m);
TEST_ASSERT_EQUAL_INT(-ENOMEM, b[5]->u.member_failed.err);
assert_type(b[6], SCHED_TRACE_E_ATTEMPT_REASONS);
assert_end(b[7], -1, 0);
TEST_ASSERT_EQUAL_INT(-ENOMEM, b[7]->u.resreq_end.rc);
sched_trace_rec_free(rec);
}
static void test_alternatives_skipped_failed_chosen(void)
{
xce_sched_job_t* job = make_job(
632004,
"{1*{rusage[mem=1]}} || {9*{rusage[mem=2]}} || "
"{1*{rusage[mem=3]} + 1*{rusage[mem=4]}} || {1*{rusage[mem=5]}}",
XCE_RESREQ_ALTER);
xce_compound_schedobj_t* alt1;
xce_compound_resreq_t* saved;
const sched_trace_entry_t* b[MAX_BRANCH];
xce_sched_trace_rec_t* rec;
int rc;
int n;
TEST_ASSERT_EQUAL_INT(4, job->schedobj->u.alters.count);
/* alternative 1 has no requirement content: skipped */
alt1 = (xce_compound_schedobj_t*)xlist_get(&(job->schedobj->u.alters), 0);
TEST_ASSERT_NOT_NULL(alt1);
saved = alt1->resreq;
alt1->resreq = NULL;
rec = run_traced(job, 0, &rc);
alt1->resreq = saved;
TEST_ASSERT_TRUE(rc > 0);
n = branch_entries(rec, b);
TEST_ASSERT_EQUAL_INT(13, n);
assert_type(b[0], SCHED_TRACE_E_ALT_SKIPPED);
TEST_ASSERT_EQUAL_INT(1, b[0]->u.alt.i);
TEST_ASSERT_EQUAL_INT(4, b[0]->u.alt.n);
assert_path(b[0], K_NONE, 0, 0, K_NONE, 0, 0);
/* alternative 2/4: not satisfied, with its reasons */
assert_container_begin(b[1], 1);
assert_path(b[1], XCE_TRACE_PATH_ALT, 2, 4, K_NONE, 0, 0);
assert_primary_begin(b[2], 9, 9, "mem", 2.0f);
assert_path(b[2], XCE_TRACE_PATH_ALT, 2, 4, XCE_TRACE_PATH_MEMBER, 1, 1);
assert_end(b[3], 0, 0);
assert_type(b[4], SCHED_TRACE_E_ATTEMPT_REASONS);
TEST_ASSERT_TRUE(b[4]->u.reasons.ncodes > 0);
assert_path(b[4], XCE_TRACE_PATH_ALT, 2, 4, K_NONE, 0, 0);
assert_end(b[5], 0, 0);
assert_path(b[5], XCE_TRACE_PATH_ALT, 2, 4, K_NONE, 0, 0);
/* alternative 3/4: two members, satisfied */
assert_container_begin(b[6], 2);
assert_path(b[6], XCE_TRACE_PATH_ALT, 3, 4, K_NONE, 0, 0);
assert_primary_begin(b[7], 1, 1, "mem", 3.0f);
assert_path(b[7], XCE_TRACE_PATH_ALT, 3, 4, XCE_TRACE_PATH_MEMBER, 1, 2);
assert_end(b[8], 1, 0);
assert_primary_begin(b[9], 1, 1, "mem", 4.0f);
assert_path(b[9], XCE_TRACE_PATH_ALT, 3, 4, XCE_TRACE_PATH_MEMBER, 2, 2);
assert_end(b[10], 1, 0);
assert_end(b[11], 1, 0);
assert_path(b[11], XCE_TRACE_PATH_ALT, 3, 4, K_NONE, 0, 0);
/* chosen: 3/4, alternative 4 not tried (nothing recorded for it) */
assert_type(b[12], SCHED_TRACE_E_ALT_RESULT);
TEST_ASSERT_EQUAL_INT(3, b[12]->u.alt.i);
TEST_ASSERT_EQUAL_INT(4, b[12]->u.alt.n);
assert_path(b[12], K_NONE, 0, 0, K_NONE, 0, 0);
sched_trace_rec_free(rec);
}
static void test_all_alternatives_fail_each_has_reasons(void)
{
xce_sched_job_t* job = make_job(
632005, "{9*{rusage[mem=1]}} || {7*{rusage[mem=2]}}", XCE_RESREQ_ALTER);
const sched_trace_entry_t* b[MAX_BRANCH];
xce_sched_trace_rec_t* rec;
int rc;
int n;
int a;
rec = run_traced(job, 0, &rc);
TEST_ASSERT_EQUAL_INT(0, rc);
n = branch_entries(rec, b);
TEST_ASSERT_EQUAL_INT(11, n);
for (a = 0; a < 2; a++) {
const sched_trace_entry_t** e = &b[a * 5];
assert_container_begin(e[0], 1);
assert_path(e[0], XCE_TRACE_PATH_ALT, a + 1, 2, K_NONE, 0, 0);
assert_primary_begin(e[1], (a == 0) ? 9 : 7, (a == 0) ? 9 : 7, "mem",
(a == 0) ? 1.0f : 2.0f);
assert_end(e[2], 0, 0);
assert_type(e[3], SCHED_TRACE_E_ATTEMPT_REASONS);
TEST_ASSERT_TRUE_MESSAGE(e[3]->u.reasons.ncodes > 0,
"an alternative's own reasons are missing");
assert_path(e[3], XCE_TRACE_PATH_ALT, a + 1, 2, K_NONE, 0, 0);
assert_end(e[4], 0, 0);
}
assert_type(b[10], SCHED_TRACE_E_ALT_RESULT);
TEST_ASSERT_EQUAL_INT_MESSAGE(0, b[10]->u.alt.i, "expected all failed");
TEST_ASSERT_EQUAL_INT(2, b[10]->u.alt.n);
sched_trace_rec_free(rec);
}
int main(void)
{
UNITY_BEGIN();
start_sim();
RUN_TEST(test_single_requirement);
RUN_TEST(test_compound_failing_member);
RUN_TEST(test_compound_merge_failure);
RUN_TEST(test_alternatives_skipped_failed_chosen);
RUN_TEST(test_all_alternatives_fail_each_has_reasons);
return UNITY_END();
}