myxce/src/scheduler/testsuite/test_trace_model.c
Rongsong Shen 144a01982e
refactor(scheduler-sim-decision-trace): split trace sources under the 800-line cap
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>
2026-10-08 10:16:46 +00:00

2020 lines
80 KiB
C

/*
* test_trace_model.c - unit tests for the decision trace record model
* (scheduler-sim-decision-trace spec, task 1.3; design.md "TraceModel").
*
* Covers the model foundation:
* - job selection matching: all jobs, a plain job, an array element, and
* a whole array including elements that did not exist when the
* selector was made (Requirements 1.2, 1.3);
* - the record header copied from the settings snapshot and the
* per-record arena: every string and number is copied, so a record
* never points into scheduler (or REPL) memory, and the record reports
* its own byte size (Requirement 9.1, design "Invariants");
* - finishing a record as a no-decision kind (Requirement 2.4);
* - freeing a NULL record is a no-op.
*
* Task 1.4 adds the entry sequence and the line budget:
* - requirement path push and pop, balanced requirement begin and end
* entries, each storing its full nesting path (3.1, 3.3, 3.4, 3.6);
* - every entry kind, one stored entry (= one rendered line) each, with
* its payload copied (3.3-3.6, 4.3-4.5, 4.7, 4.8, 5.10);
* - the line budget at bound-1, bound and bound+1, with the exact
* omitted count (9.1, 9.2).
*
* Task 1.5 adds the counters and the merged entries:
* - per-requirement plan counts and the search-end statement: none
* generated, all discarded, none placed, including a plan not tried
* after a discarded one (4.6, 4.7, 4.9);
* - decision-wide counters (plans, requirement outcomes, host
* evaluations per verdict kind) stay exact after truncation (9.3);
* - at summary level a position-summary entry replaces the host lines,
* counting a host with several verdict kinds once under each and
* tallying hits per limit (5.9, 5.10);
* - consecutive released-host calls merge into one rollback entry (5.8).
*
* Links only the model, src/sched_trace.c and src/sched_trace_scan.c (plus
* libxce for the jobid helpers).
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unity/unity.h>
#include "sched/alloc_iter.h"
#include "sched/trace.h"
#include "utils.h"
/* A plain job, and an array block whose elements are tagged TAG_JOB_ARRAY
* and numbered from 1 within the block (job-array-one-based-index). */
#define PLAIN_ID(seq) MAKE_JOBID(1, 2, 1000, 0x0, (uint64_t)(seq))
#define ARRAY_BASE(blk) \
MAKE_JOBID(1, 2, 1000, 0x0, ((uint64_t)(blk)) << N_ARRAY_IDX_BITS)
#define ARRAY_ELEM(blk, k) \
MAKE_JOBID(1, 2, 1000, TAG_JOB_ARRAY, \
(((uint64_t)(blk)) << N_ARRAY_IDX_BITS) | (uint64_t)(k))
void setUp(void) { return; }
void tearDown(void) { return; }
static xce_sched_trace_ctl_t make_ctl(xce_sched_trace_sel_t* sel, int nsel)
{
xce_sched_trace_ctl_t ctl;
memset(&ctl, 0, sizeof(ctl));
ctl.all = false;
ctl.nsel = nsel;
ctl.sel = sel;
ctl.level = XCE_TRACE_LEVEL_SUMMARY;
ctl.line_bound = XCE_TRACE_LINE_BOUND_DEFAULT;
ctl.session_no = 7;
ctl.policy_name = "fcfs";
ctl.position = 3;
return ctl;
}
/* ---- constants ------------------------------------------------------- */
static void test_constants_match_requirements(void)
{
TEST_ASSERT_EQUAL_INT(1000, XCE_TRACE_LINE_BOUND_DEFAULT);
TEST_ASSERT_EQUAL_INT(50, XCE_TRACE_LINE_BOUND_MIN);
TEST_ASSERT_EQUAL_INT(100000, XCE_TRACE_LINE_BOUND_MAX);
TEST_ASSERT_EQUAL_INT(50, XCE_TRACE_OUTCOME_HOSTS);
TEST_ASSERT_EQUAL_INT(XCE_TRACE_LEVEL_SUMMARY, 0);
TEST_ASSERT_EQUAL_INT(XCE_TRACE_REC_DECISION, 0);
/* verdict kinds are distinct single bits, one per counter slot */
TEST_ASSERT_EQUAL_HEX(0x3f, XCE_TRACE_V_ACCEPTED | XCE_TRACE_V_SELECT
| XCE_TRACE_V_SHORTFALL | XCE_TRACE_V_LIMIT
| XCE_TRACE_V_CLAIMED
| XCE_TRACE_V_INTERNAL);
TEST_ASSERT_EQUAL_INT(6, XCE_TRACE_V_NKINDS);
}
/* ---- selection matching (1.2, 1.3) ----------------------------------- */
static void test_wants_null_ctl_is_false(void)
{
TEST_ASSERT_FALSE(sched_trace_ctl_wants(NULL, PLAIN_ID(5)));
}
static void test_wants_all_matches_every_job(void)
{
xce_sched_trace_ctl_t ctl = make_ctl(NULL, 0);
ctl.all = true;
TEST_ASSERT_TRUE(sched_trace_ctl_wants(&ctl, PLAIN_ID(5)));
TEST_ASSERT_TRUE(sched_trace_ctl_wants(&ctl, ARRAY_ELEM(3, 1)));
TEST_ASSERT_TRUE(sched_trace_ctl_wants(&ctl, ARRAY_ELEM(3, 400)));
}
static void test_wants_empty_selection_matches_nothing(void)
{
xce_sched_trace_ctl_t ctl = make_ctl(NULL, 0);
TEST_ASSERT_FALSE(sched_trace_ctl_wants(&ctl, PLAIN_ID(5)));
TEST_ASSERT_FALSE(sched_trace_ctl_wants(&ctl, ARRAY_ELEM(3, 1)));
}
static void test_wants_plain_job_matches_only_that_job(void)
{
xce_sched_trace_sel_t sel[] = {
{ PLAIN_ID(5), false }
};
xce_sched_trace_ctl_t ctl = make_ctl(sel, 1);
TEST_ASSERT_TRUE(sched_trace_ctl_wants(&ctl, PLAIN_ID(5)));
TEST_ASSERT_FALSE(sched_trace_ctl_wants(&ctl, PLAIN_ID(6)));
TEST_ASSERT_FALSE(sched_trace_ctl_wants(&ctl, ARRAY_ELEM(3, 5)));
}
static void test_wants_element_matches_only_that_element(void)
{
xce_sched_trace_sel_t sel[] = {
{ ARRAY_ELEM(3, 2), false }
};
xce_sched_trace_ctl_t ctl = make_ctl(sel, 1);
TEST_ASSERT_TRUE(sched_trace_ctl_wants(&ctl, ARRAY_ELEM(3, 2)));
TEST_ASSERT_FALSE(sched_trace_ctl_wants(&ctl, ARRAY_ELEM(3, 1)));
TEST_ASSERT_FALSE(sched_trace_ctl_wants(&ctl, ARRAY_ELEM(3, 3)));
TEST_ASSERT_FALSE(sched_trace_ctl_wants(&ctl, ARRAY_ELEM(4, 2)));
TEST_ASSERT_FALSE(sched_trace_ctl_wants(&ctl, ARRAY_BASE(3)));
}
static void test_wants_whole_array_matches_every_element(void)
{
xce_sched_trace_sel_t sel[] = {
{ ARRAY_BASE(3), true }
};
xce_sched_trace_ctl_t ctl = make_ctl(sel, 1);
TEST_ASSERT_TRUE(sched_trace_ctl_wants(&ctl, ARRAY_ELEM(3, 1)));
TEST_ASSERT_TRUE(sched_trace_ctl_wants(&ctl, ARRAY_ELEM(3, 2)));
/* elements created later (any index in the block) also match (1.3) */
TEST_ASSERT_TRUE(sched_trace_ctl_wants(&ctl, ARRAY_ELEM(3, MAX_ARRAY_IDX)));
/* another array, and a plain job that happens to share the base id's
* bit pattern, do not */
TEST_ASSERT_FALSE(sched_trace_ctl_wants(&ctl, ARRAY_ELEM(4, 1)));
TEST_ASSERT_FALSE(sched_trace_ctl_wants(&ctl, ARRAY_BASE(3)));
TEST_ASSERT_FALSE(sched_trace_ctl_wants(&ctl, PLAIN_ID(7)));
}
static void test_wants_unknown_yet_future_ids(void)
{
/* Selectors naming jobs that do not exist yet are kept as plain keys:
* the predicate is a pure function of the id, so the job matches once
* it appears (1.3). */
xce_sched_trace_sel_t sel[] = {
{ PLAIN_ID(900), false },
{ ARRAY_ELEM(8, 4), false },
{ ARRAY_BASE(9), true }
};
xce_sched_trace_ctl_t ctl = make_ctl(sel, 3);
TEST_ASSERT_TRUE(sched_trace_ctl_wants(&ctl, PLAIN_ID(900)));
TEST_ASSERT_TRUE(sched_trace_ctl_wants(&ctl, ARRAY_ELEM(8, 4)));
TEST_ASSERT_TRUE(sched_trace_ctl_wants(&ctl, ARRAY_ELEM(9, 1)));
TEST_ASSERT_TRUE(sched_trace_ctl_wants(&ctl, ARRAY_ELEM(9, 77)));
TEST_ASSERT_FALSE(sched_trace_ctl_wants(&ctl, ARRAY_ELEM(8, 5)));
TEST_ASSERT_FALSE(sched_trace_ctl_wants(&ctl, PLAIN_ID(901)));
}
/* ---- record header and arena ----------------------------------------- */
static void test_rec_new_null_ctl_returns_null(void)
{
TEST_ASSERT_NULL(
sched_trace_rec_new(NULL, PLAIN_ID(5), XCE_TRACE_REC_DECISION));
}
static void test_rec_new_copies_header_from_ctl(void)
{
char policy[] = "priority-fcfs";
xce_sched_trace_ctl_t ctl = make_ctl(NULL, 0);
xce_sched_trace_rec_t* rec;
ctl.level = XCE_TRACE_LEVEL_FULL;
ctl.line_bound = 75;
ctl.policy_name = policy;
rec = sched_trace_rec_new(&ctl, PLAIN_ID(5), XCE_TRACE_REC_DECISION);
TEST_ASSERT_NOT_NULL(rec);
/* change every source the record was built from */
ctl.level = XCE_TRACE_LEVEL_SUMMARY;
ctl.line_bound = 9999;
ctl.session_no = 99;
ctl.position = 42;
memset(policy, 'x', sizeof(policy) - 1);
TEST_ASSERT_EQUAL_UINT64(PLAIN_ID(5), rec->jobid);
TEST_ASSERT_EQUAL_INT(XCE_TRACE_REC_DECISION, rec->kind);
TEST_ASSERT_EQUAL_INT(0, rec->err);
TEST_ASSERT_EQUAL_INT(XCE_TRACE_LEVEL_FULL, rec->level);
TEST_ASSERT_EQUAL_INT(75, rec->line_bound);
TEST_ASSERT_EQUAL_UINT64(7, rec->session_no);
TEST_ASSERT_EQUAL_INT(3, rec->position);
TEST_ASSERT_FALSE(rec->incomplete);
TEST_ASSERT_NOT_NULL(rec->policy_name);
TEST_ASSERT_TRUE(rec->policy_name != policy);
TEST_ASSERT_EQUAL_STRING("priority-fcfs", rec->policy_name);
TEST_ASSERT_EQUAL_INT(0, rec->path.depth);
TEST_ASSERT_EQUAL_INT(0, rec->counters.plans_tried);
TEST_ASSERT_EQUAL_INT(0, rec->counters.omitted_lines);
sched_trace_rec_free(rec);
}
static void test_rec_new_null_policy_stays_null(void)
{
xce_sched_trace_ctl_t ctl = make_ctl(NULL, 0);
xce_sched_trace_rec_t* rec;
ctl.policy_name = NULL;
rec = sched_trace_rec_new(&ctl, PLAIN_ID(5), XCE_TRACE_REC_RECONSTRUCTED);
TEST_ASSERT_NOT_NULL(rec);
TEST_ASSERT_NULL(rec->policy_name);
TEST_ASSERT_EQUAL_INT(XCE_TRACE_REC_RECONSTRUCTED, rec->kind);
TEST_ASSERT_FALSE(rec->incomplete);
sched_trace_rec_free(rec);
}
static void test_rec_new_does_not_keep_ctl_selection(void)
{
/* The record keeps nothing that points into the ctl: freeing the
* ctl's selector array and policy string must leave it intact. */
xce_sched_trace_sel_t* sel = calloc(1, sizeof(*sel));
char* policy = strdup("fcfs");
xce_sched_trace_ctl_t ctl;
xce_sched_trace_rec_t* rec;
TEST_ASSERT_NOT_NULL(sel);
TEST_ASSERT_NOT_NULL(policy);
sel[0].key = PLAIN_ID(5);
ctl = make_ctl(sel, 1);
ctl.policy_name = policy;
rec = sched_trace_rec_new(&ctl, PLAIN_ID(5), XCE_TRACE_REC_DECISION);
TEST_ASSERT_NOT_NULL(rec);
free(sel);
free(policy);
/* ASAN would flag a read of either freed block here */
TEST_ASSERT_EQUAL_STRING("fcfs", rec->policy_name);
sched_trace_rec_free(rec);
}
static void test_arena_strdup_copies_string(void)
{
xce_sched_trace_ctl_t ctl = make_ctl(NULL, 0);
xce_sched_trace_rec_t* rec
= sched_trace_rec_new(&ctl, PLAIN_ID(5), XCE_TRACE_REC_DECISION);
char* src = strdup("host-n01");
char* cp;
TEST_ASSERT_NOT_NULL(rec);
TEST_ASSERT_NOT_NULL(src);
cp = sched_trace_arena_strdup(rec, src);
TEST_ASSERT_NOT_NULL(cp);
TEST_ASSERT_TRUE(cp != src);
free(src);
TEST_ASSERT_EQUAL_STRING("host-n01", cp);
/* empty string is a real copy, NULL is NULL without marking the
* record incomplete */
cp = sched_trace_arena_strdup(rec, "");
TEST_ASSERT_NOT_NULL(cp);
TEST_ASSERT_EQUAL_STRING("", cp);
TEST_ASSERT_NULL(sched_trace_arena_strdup(rec, NULL));
TEST_ASSERT_FALSE(rec->incomplete);
TEST_ASSERT_NULL(sched_trace_arena_strdup(NULL, "x"));
sched_trace_rec_free(rec);
}
static void test_arena_memdup_copies_numbers(void)
{
xce_sched_trace_ctl_t ctl = make_ctl(NULL, 0);
xce_sched_trace_rec_t* rec
= sched_trace_rec_new(&ctl, PLAIN_ID(5), XCE_TRACE_REC_DECISION);
float* src = malloc(4 * sizeof(float));
float* cp;
TEST_ASSERT_NOT_NULL(rec);
TEST_ASSERT_NOT_NULL(src);
src[0] = 1.5f;
src[1] = 2.0f;
src[2] = -3.25f;
src[3] = 1e9f;
cp = sched_trace_arena_memdup(rec, src, 4 * sizeof(float));
TEST_ASSERT_NOT_NULL(cp);
TEST_ASSERT_TRUE(cp != src);
src[0] = 0.0f;
free(src);
TEST_ASSERT_EQUAL_FLOAT(1.5f, cp[0]);
TEST_ASSERT_EQUAL_FLOAT(2.0f, cp[1]);
TEST_ASSERT_EQUAL_FLOAT(-3.25f, cp[2]);
TEST_ASSERT_EQUAL_FLOAT(1e9f, cp[3]);
/* storage is suitably aligned for any scalar */
TEST_ASSERT_EQUAL_UINT(0, ((uintptr_t)cp) % _Alignof(max_align_t));
TEST_ASSERT_NULL(sched_trace_arena_memdup(rec, NULL, 8));
TEST_ASSERT_FALSE(rec->incomplete);
sched_trace_rec_free(rec);
}
static void test_arena_alloc_is_zeroed_and_aligned(void)
{
xce_sched_trace_ctl_t ctl = make_ctl(NULL, 0);
xce_sched_trace_rec_t* rec
= sched_trace_rec_new(&ctl, PLAIN_ID(5), XCE_TRACE_REC_DECISION);
unsigned char* p;
size_t i;
int k;
TEST_ASSERT_NOT_NULL(rec);
for (k = 0; k < 50; k++) {
size_t n = (size_t)(k * 37 + 1);
p = sched_trace_arena_alloc(rec, n);
TEST_ASSERT_NOT_NULL(p);
TEST_ASSERT_EQUAL_UINT(0, ((uintptr_t)p) % _Alignof(max_align_t));
for (i = 0; i < n; i++) {
TEST_ASSERT_EQUAL_UINT8(0, p[i]);
}
memset(p, 0xa5, n); /* ASAN flags any overlap with a neighbour */
}
sched_trace_rec_free(rec);
}
static void test_rec_bytes_reports_record_and_arena(void)
{
xce_sched_trace_ctl_t ctl = make_ctl(NULL, 0);
xce_sched_trace_rec_t* rec;
size_t b0, b1, b2;
int k;
TEST_ASSERT_EQUAL_UINT(0, sched_trace_rec_bytes(NULL));
rec = sched_trace_rec_new(&ctl, PLAIN_ID(5), XCE_TRACE_REC_DECISION);
TEST_ASSERT_NOT_NULL(rec);
b0 = sched_trace_rec_bytes(rec);
TEST_ASSERT_TRUE(b0 >= sizeof(*rec) + strlen("fcfs") + 1);
TEST_ASSERT_EQUAL_UINT(sizeof(*rec) + rec->arena_bytes, b0);
/* a large copy grows the arena by at least its own size */
TEST_ASSERT_NOT_NULL(sched_trace_arena_alloc(rec, 1 << 20));
b1 = sched_trace_rec_bytes(rec);
TEST_ASSERT_TRUE(b1 >= b0 + (1 << 20));
/* many small copies: size never shrinks and covers everything copied */
for (k = 0; k < 1000; k++) {
TEST_ASSERT_NOT_NULL(sched_trace_arena_strdup(rec, "0123456789"));
}
b2 = sched_trace_rec_bytes(rec);
TEST_ASSERT_TRUE(b2 >= b1 + 1000 * 11);
TEST_ASSERT_EQUAL_UINT(sizeof(*rec) + rec->arena_bytes, b2);
sched_trace_rec_free(rec);
}
static void test_rec_free_null_is_noop(void)
{
sched_trace_rec_free(NULL);
TEST_PASS();
}
/* ---- record finishing (2.4) ------------------------------------------ */
static void finish_and_check(xce_sched_trace_rec_kind_t kind, int err)
{
xce_sched_trace_ctl_t ctl = make_ctl(NULL, 0);
xce_sched_trace_rec_t* rec
= sched_trace_rec_new(&ctl, PLAIN_ID(5), XCE_TRACE_REC_DECISION);
TEST_ASSERT_NOT_NULL(rec);
sched_trace_rec_finish(rec, kind, err);
TEST_ASSERT_EQUAL_INT(kind, rec->kind);
TEST_ASSERT_EQUAL_INT(err, rec->err);
/* the header is otherwise untouched */
TEST_ASSERT_EQUAL_UINT64(PLAIN_ID(5), rec->jobid);
TEST_ASSERT_EQUAL_INT(3, rec->position);
TEST_ASSERT_EQUAL_UINT64(7, rec->session_no);
sched_trace_rec_free(rec);
}
static void test_finish_records_kind_and_error(void)
{
finish_and_check(XCE_TRACE_REC_NOT_FOUND, -2 /* -ENOENT */);
finish_and_check(XCE_TRACE_REC_NO_SCHEDOBJ, 0);
finish_and_check(XCE_TRACE_REC_INTERNAL_ERROR, -5 /* -EIO */);
finish_and_check(XCE_TRACE_REC_INTERNAL_ERROR, -12 /* -ENOMEM */);
finish_and_check(XCE_TRACE_REC_PEND_NO_REASON, 0);
}
static void test_finish_rejects_non_no_decision_kinds(void)
{
xce_sched_trace_ctl_t ctl = make_ctl(NULL, 0);
xce_sched_trace_rec_t* rec
= sched_trace_rec_new(&ctl, PLAIN_ID(5), XCE_TRACE_REC_DECISION);
TEST_ASSERT_NOT_NULL(rec);
sched_trace_rec_finish(rec, XCE_TRACE_REC_RECONSTRUCTED, -5);
TEST_ASSERT_EQUAL_INT(XCE_TRACE_REC_DECISION, rec->kind);
TEST_ASSERT_EQUAL_INT(0, rec->err);
sched_trace_rec_finish(rec, XCE_TRACE_REC_DECISION, -5);
TEST_ASSERT_EQUAL_INT(XCE_TRACE_REC_DECISION, rec->kind);
TEST_ASSERT_EQUAL_INT(0, rec->err);
sched_trace_rec_finish(NULL, XCE_TRACE_REC_NOT_FOUND, -2); /* no crash */
sched_trace_rec_free(rec);
}
/* ---- entry sequence (task 1.4) ---------------------------------------- */
static xce_sched_trace_rec_t* new_rec_level(int line_bound,
xce_sched_trace_level_t level)
{
xce_sched_trace_ctl_t ctl = make_ctl(NULL, 0);
ctl.level = level;
ctl.line_bound = line_bound;
return sched_trace_rec_new(&ctl, PLAIN_ID(5), XCE_TRACE_REC_DECISION);
}
static xce_sched_trace_rec_t* new_rec(int line_bound)
{
return new_rec_level(line_bound, XCE_TRACE_LEVEL_FULL);
}
static int count_entries(const xce_sched_trace_rec_t* rec)
{
const sched_trace_entry_t* e;
int n = 0;
for (e = rec->entries; e != NULL; e = e->next) {
n++;
}
return n;
}
static const sched_trace_entry_t* entry_at(const xce_sched_trace_rec_t* rec,
int k)
{
const sched_trace_entry_t* e = rec->entries;
while ((e != NULL) && (k > 0)) {
e = e->next;
k--;
}
TEST_ASSERT_NOT_NULL_MESSAGE(e, "entry index out of range");
return e;
}
static void assert_path1(const sched_trace_entry_t* e,
xce_sched_trace_path_kind_t kind, int i, int n)
{
TEST_ASSERT_EQUAL_INT(1, e->path.depth);
TEST_ASSERT_EQUAL_INT(kind, e->path.lvl[0].kind);
TEST_ASSERT_EQUAL_INT(i, e->path.lvl[0].i);
TEST_ASSERT_EQUAL_INT(n, e->path.lvl[0].n);
}
/* A primary with two requested resources; the xlist points at the
* caller's arrays (no heap). */
typedef struct {
xce_primary_resreq_t p;
rusage_value_t v[2];
void* ptrs[2];
char name0[16];
char name1[16];
} fixture_primary_t;
static void make_primary(fixture_primary_t* f, int min_tasks, int max_tasks)
{
memset(f, 0, sizeof(*f));
strcpy(f->name0, "mem");
strcpy(f->name1, "license");
f->v[0].res = f->name0;
f->v[0].value = 8192.0f;
f->v[0].per_flag = PER_TASK;
f->v[1].res = f->name1;
f->v[1].value = 2.0f;
f->v[1].per_flag = PER_JOB;
f->ptrs[0] = &f->v[0];
f->ptrs[1] = &f->v[1];
f->p.minTasks = min_tasks;
f->p.maxTasks = max_tasks;
f->p.rusages.count = 2;
f->p.rusages.caps = 2;
f->p.rusages.delta = 1;
f->p.rusages.data = f->ptrs;
}
static void test_single_requirement_path_and_balance(void)
{
xce_sched_trace_rec_t* rec = new_rec(XCE_TRACE_LINE_BOUND_DEFAULT);
fixture_primary_t fp;
const sched_trace_entry_t* b;
const sched_trace_entry_t* e;
TEST_ASSERT_NOT_NULL(rec);
TEST_ASSERT_NULL(rec->entries);
TEST_ASSERT_EQUAL_INT(0, rec->nentries);
make_primary(&fp, 2, 4);
sched_trace_path_push(rec, XCE_TRACE_PATH_SINGLE, 1, 1);
TEST_ASSERT_EQUAL_INT(1, rec->path.depth);
sched_trace_resreq_begin(rec, &fp.p, 0);
sched_trace_resreq_end(rec, 1, 0);
sched_trace_path_pop(rec);
TEST_ASSERT_EQUAL_INT(0, rec->path.depth);
TEST_ASSERT_EQUAL_INT(0, rec->resreq_open);
TEST_ASSERT_EQUAL_INT(0, rec->depth);
TEST_ASSERT_EQUAL_INT(2, rec->nentries);
TEST_ASSERT_EQUAL_INT(2, count_entries(rec));
b = entry_at(rec, 0);
TEST_ASSERT_EQUAL_INT(SCHED_TRACE_E_RESREQ_BEGIN, b->type);
TEST_ASSERT_EQUAL_INT(0, b->depth);
assert_path1(b, XCE_TRACE_PATH_SINGLE, 1, 1);
TEST_ASSERT_FALSE(b->u.resreq_begin.container);
TEST_ASSERT_EQUAL_INT(2, b->u.resreq_begin.min_tasks);
TEST_ASSERT_EQUAL_INT(4, b->u.resreq_begin.max_tasks);
TEST_ASSERT_EQUAL_INT(2, b->u.resreq_begin.nreq);
TEST_ASSERT_EQUAL_STRING("mem", b->u.resreq_begin.req[0].name);
TEST_ASSERT_EQUAL_FLOAT(8192.0f, b->u.resreq_begin.req[0].value);
TEST_ASSERT_EQUAL_INT(PER_TASK, b->u.resreq_begin.req[0].per);
TEST_ASSERT_EQUAL_STRING("license", b->u.resreq_begin.req[1].name);
TEST_ASSERT_EQUAL_FLOAT(2.0f, b->u.resreq_begin.req[1].value);
TEST_ASSERT_EQUAL_INT(PER_JOB, b->u.resreq_begin.req[1].per);
e = entry_at(rec, 1);
TEST_ASSERT_EQUAL_INT(SCHED_TRACE_E_RESREQ_END, e->type);
TEST_ASSERT_EQUAL_INT(0, e->depth); /* same level as its begin */
assert_path1(e, XCE_TRACE_PATH_SINGLE, 1, 1);
TEST_ASSERT_EQUAL_INT(1, e->u.resreq_end.rc);
TEST_ASSERT_EQUAL_INT(0, e->u.resreq_end.not_tried_after);
TEST_ASSERT_TRUE(rec->entries_tail == e);
/* the requested resources are copies, not the scheduler's strings */
TEST_ASSERT_TRUE(b->u.resreq_begin.req[0].name != fp.name0);
strcpy(fp.name0, "XXX");
fp.v[1].value = 99.0f;
TEST_ASSERT_EQUAL_STRING("mem", b->u.resreq_begin.req[0].name);
TEST_ASSERT_EQUAL_FLOAT(2.0f, b->u.resreq_begin.req[1].value);
sched_trace_rec_free(rec);
}
static void test_alternative_member_nesting_stores_full_path(void)
{
xce_sched_trace_rec_t* rec = new_rec(XCE_TRACE_LINE_BOUND_DEFAULT);
fixture_primary_t fp;
const sched_trace_entry_t* e;
int k;
TEST_ASSERT_NOT_NULL(rec);
make_primary(&fp, 1, 1);
/* alternative 2/3 (a compound of 2 members): member 1/2 fails, so
* member 2/2 is not tried */
sched_trace_path_push(rec, XCE_TRACE_PATH_ALT, 2, 3);
sched_trace_resreq_begin(rec, NULL, 2);
sched_trace_path_push(rec, XCE_TRACE_PATH_MEMBER, 1, 2);
TEST_ASSERT_EQUAL_INT(2, rec->path.depth);
sched_trace_resreq_begin(rec, &fp.p, 0);
sched_trace_resreq_end(rec, 0, 1);
sched_trace_path_pop(rec);
sched_trace_resreq_end(rec, 0, 0);
sched_trace_path_pop(rec);
TEST_ASSERT_EQUAL_INT(0, rec->path.depth);
TEST_ASSERT_EQUAL_INT(0, rec->resreq_open);
TEST_ASSERT_EQUAL_INT(0, rec->depth);
TEST_ASSERT_EQUAL_INT(4, count_entries(rec));
e = entry_at(rec, 0); /* container begin */
TEST_ASSERT_EQUAL_INT(SCHED_TRACE_E_RESREQ_BEGIN, e->type);
TEST_ASSERT_EQUAL_INT(0, e->depth);
assert_path1(e, XCE_TRACE_PATH_ALT, 2, 3);
TEST_ASSERT_TRUE(e->u.resreq_begin.container);
TEST_ASSERT_EQUAL_INT(2, e->u.resreq_begin.nmembers);
TEST_ASSERT_EQUAL_INT(0, e->u.resreq_begin.nreq);
for (k = 1; k <= 2; k++) { /* member begin and end: two-level path */
e = entry_at(rec, k);
TEST_ASSERT_EQUAL_INT(1, e->depth);
TEST_ASSERT_EQUAL_INT(2, e->path.depth);
TEST_ASSERT_EQUAL_INT(XCE_TRACE_PATH_ALT, e->path.lvl[0].kind);
TEST_ASSERT_EQUAL_INT(2, e->path.lvl[0].i);
TEST_ASSERT_EQUAL_INT(3, e->path.lvl[0].n);
TEST_ASSERT_EQUAL_INT(XCE_TRACE_PATH_MEMBER, e->path.lvl[1].kind);
TEST_ASSERT_EQUAL_INT(1, e->path.lvl[1].i);
TEST_ASSERT_EQUAL_INT(2, e->path.lvl[1].n);
}
TEST_ASSERT_EQUAL_INT(SCHED_TRACE_E_RESREQ_BEGIN, entry_at(rec, 1)->type);
TEST_ASSERT_FALSE(entry_at(rec, 1)->u.resreq_begin.container);
e = entry_at(rec, 2);
TEST_ASSERT_EQUAL_INT(SCHED_TRACE_E_RESREQ_END, e->type);
TEST_ASSERT_EQUAL_INT(0, e->u.resreq_end.rc);
TEST_ASSERT_EQUAL_INT(1, e->u.resreq_end.not_tried_after);
e = entry_at(rec, 3); /* container end */
TEST_ASSERT_EQUAL_INT(SCHED_TRACE_E_RESREQ_END, e->type);
TEST_ASSERT_EQUAL_INT(0, e->depth);
assert_path1(e, XCE_TRACE_PATH_ALT, 2, 3);
sched_trace_rec_free(rec);
}
static void test_compound_container_path(void)
{
xce_sched_trace_rec_t* rec = new_rec(XCE_TRACE_LINE_BOUND_DEFAULT);
fixture_primary_t fp;
const sched_trace_entry_t* e;
int j;
int nbegin = 0;
int nend = 0;
TEST_ASSERT_NOT_NULL(rec);
make_primary(&fp, 1, 2);
sched_trace_path_push(rec, XCE_TRACE_PATH_COMPOUND, 1, 1);
sched_trace_resreq_begin(rec, NULL, 3);
for (j = 1; j <= 3; j++) {
sched_trace_path_push(rec, XCE_TRACE_PATH_MEMBER, j, 3);
sched_trace_resreq_begin(rec, &fp.p, 0);
sched_trace_resreq_end(rec, 1, 0);
sched_trace_path_pop(rec);
}
sched_trace_resreq_end(rec, 1, 0);
sched_trace_path_pop(rec);
TEST_ASSERT_EQUAL_INT(8, count_entries(rec));
e = entry_at(rec, 0);
assert_path1(e, XCE_TRACE_PATH_COMPOUND, 1, 1);
TEST_ASSERT_TRUE(e->u.resreq_begin.container);
TEST_ASSERT_EQUAL_INT(3, e->u.resreq_begin.nmembers);
e = entry_at(rec, 5); /* member 3/3 begin */
TEST_ASSERT_EQUAL_INT(2, e->path.depth);
TEST_ASSERT_EQUAL_INT(XCE_TRACE_PATH_COMPOUND, e->path.lvl[0].kind);
TEST_ASSERT_EQUAL_INT(3, e->path.lvl[1].i);
TEST_ASSERT_EQUAL_INT(3, e->path.lvl[1].n);
/* balanced: one end per begin, nesting back to the top */
for (e = rec->entries; e != NULL; e = e->next) {
nbegin += (e->type == SCHED_TRACE_E_RESREQ_BEGIN);
nend += (e->type == SCHED_TRACE_E_RESREQ_END);
}
TEST_ASSERT_EQUAL_INT(4, nbegin);
TEST_ASSERT_EQUAL_INT(nbegin, nend);
TEST_ASSERT_EQUAL_INT(0, rec->resreq_open);
TEST_ASSERT_EQUAL_INT(0, rec->depth);
sched_trace_rec_free(rec);
}
static void test_path_push_beyond_two_levels_stays_balanced(void)
{
xce_sched_trace_rec_t* rec = new_rec(XCE_TRACE_LINE_BOUND_DEFAULT);
TEST_ASSERT_NOT_NULL(rec);
sched_trace_path_push(rec, XCE_TRACE_PATH_ALT, 1, 2);
sched_trace_path_push(rec, XCE_TRACE_PATH_MEMBER, 1, 1);
sched_trace_path_push(rec, XCE_TRACE_PATH_MEMBER, 1, 1); /* not kept */
TEST_ASSERT_EQUAL_INT(2, rec->path.depth);
TEST_ASSERT_EQUAL_INT(XCE_TRACE_PATH_MEMBER, rec->path.lvl[1].kind);
sched_trace_path_pop(rec); /* pops the one not kept */
TEST_ASSERT_EQUAL_INT(2, rec->path.depth);
sched_trace_path_pop(rec);
TEST_ASSERT_EQUAL_INT(1, rec->path.depth);
sched_trace_path_pop(rec);
TEST_ASSERT_EQUAL_INT(0, rec->path.depth);
sched_trace_path_pop(rec); /* unbalanced pop: stays at the top */
TEST_ASSERT_EQUAL_INT(0, rec->path.depth);
TEST_ASSERT_EQUAL_INT(0, rec->nentries); /* the path is not an entry */
sched_trace_rec_free(rec);
}
static void test_requirement_level_entries(void)
{
xce_sched_trace_rec_t* rec = new_rec(XCE_TRACE_LINE_BOUND_DEFAULT);
const sched_trace_entry_t* e;
sched_trace_reason_count_t codes[2] = { { 3, 4 }, { 7, 1 } };
char h0[8] = "n01";
char h1[8] = "n02";
char r0[16] = "license";
const char* hosts[2] = { h0, h1 };
const char* res[1] = { r0 };
TEST_ASSERT_NOT_NULL(rec);
sched_trace_path_push(rec, XCE_TRACE_PATH_ALT, 1, 3);
sched_trace_alt_skipped(rec, 1, 3);
sched_trace_path_pop(rec);
sched_trace_path_push(rec, XCE_TRACE_PATH_ALT, 2, 3);
sched_trace_resreq_begin(rec, NULL, 2);
sched_trace_member_failed(rec, 1, 2, -12);
sched_trace_attempt_reasons(rec, codes, 2, hosts, 2, res, 1);
sched_trace_resreq_end(rec, -12, 0);
sched_trace_path_pop(rec);
sched_trace_alt_result(rec, 3, 3);
sched_trace_alt_result(rec, 0, 3);
TEST_ASSERT_EQUAL_INT(7, count_entries(rec));
e = entry_at(rec, 0);
TEST_ASSERT_EQUAL_INT(SCHED_TRACE_E_ALT_SKIPPED, e->type);
TEST_ASSERT_EQUAL_INT(1, e->u.alt.i);
TEST_ASSERT_EQUAL_INT(3, e->u.alt.n);
TEST_ASSERT_EQUAL_INT(0, e->depth);
e = entry_at(rec, 2);
TEST_ASSERT_EQUAL_INT(SCHED_TRACE_E_MEMBER_FAILED, e->type);
TEST_ASSERT_EQUAL_INT(1, e->depth); /* inside the alternative */
assert_path1(e, XCE_TRACE_PATH_ALT, 2, 3);
TEST_ASSERT_EQUAL_INT(1, e->u.member_failed.j);
TEST_ASSERT_EQUAL_INT(2, e->u.member_failed.m);
TEST_ASSERT_EQUAL_INT(-12, e->u.member_failed.err);
e = entry_at(rec, 3);
TEST_ASSERT_EQUAL_INT(SCHED_TRACE_E_ATTEMPT_REASONS, e->type);
TEST_ASSERT_EQUAL_INT(1, e->depth);
TEST_ASSERT_EQUAL_INT(2, e->u.reasons.ncodes);
TEST_ASSERT_EQUAL_INT(3, e->u.reasons.codes[0].code);
TEST_ASSERT_EQUAL_INT(4, e->u.reasons.codes[0].count);
TEST_ASSERT_EQUAL_INT(7, e->u.reasons.codes[1].code);
TEST_ASSERT_EQUAL_INT(1, e->u.reasons.codes[1].count);
TEST_ASSERT_EQUAL_INT(2, e->u.reasons.nhosts);
TEST_ASSERT_EQUAL_INT(1, e->u.reasons.nres);
TEST_ASSERT_TRUE(e->u.reasons.codes != codes);
TEST_ASSERT_TRUE(e->u.reasons.hosts[0] != h0);
codes[0].count = 99;
strcpy(h0, "zz");
strcpy(r0, "zz");
TEST_ASSERT_EQUAL_INT(4, e->u.reasons.codes[0].count);
TEST_ASSERT_EQUAL_STRING("n01", e->u.reasons.hosts[0]);
TEST_ASSERT_EQUAL_STRING("n02", e->u.reasons.hosts[1]);
TEST_ASSERT_EQUAL_STRING("license", e->u.reasons.res[0]);
e = entry_at(rec, 4);
TEST_ASSERT_EQUAL_INT(SCHED_TRACE_E_RESREQ_END, e->type);
TEST_ASSERT_EQUAL_INT(-12, e->u.resreq_end.rc);
e = entry_at(rec, 5);
TEST_ASSERT_EQUAL_INT(SCHED_TRACE_E_ALT_RESULT, e->type);
TEST_ASSERT_EQUAL_INT(3, e->u.alt.i);
TEST_ASSERT_EQUAL_INT(3, e->u.alt.n);
TEST_ASSERT_EQUAL_INT(0, e->path.depth); /* after the pop */
e = entry_at(rec, 6);
TEST_ASSERT_EQUAL_INT(SCHED_TRACE_E_ALT_RESULT, e->type);
TEST_ASSERT_EQUAL_INT(0, e->u.alt.i); /* all alternatives failed */
sched_trace_rec_free(rec);
}
static void test_attempt_reasons_empty_lists(void)
{
xce_sched_trace_rec_t* rec = new_rec(XCE_TRACE_LINE_BOUND_DEFAULT);
const sched_trace_entry_t* e;
TEST_ASSERT_NOT_NULL(rec);
sched_trace_attempt_reasons(rec, NULL, 0, NULL, 0, NULL, 0);
TEST_ASSERT_EQUAL_INT(1, rec->nentries);
e = entry_at(rec, 0);
TEST_ASSERT_EQUAL_INT(SCHED_TRACE_E_ATTEMPT_REASONS, e->type);
TEST_ASSERT_EQUAL_INT(0, e->u.reasons.ncodes);
TEST_ASSERT_EQUAL_INT(0, e->u.reasons.nhosts);
TEST_ASSERT_EQUAL_INT(0, e->u.reasons.nres);
TEST_ASSERT_FALSE(rec->incomplete);
sched_trace_rec_free(rec);
}
/* A plan of `n` positions with `slots` tasks each, slot array owned by the
* caller. */
static xce_alloc_plan_t make_plan(int* slots, int n)
{
xce_alloc_plan_t plan;
memset(&plan, 0, sizeof(plan));
plan.nbuckets = n;
plan.bucket_slots = slots;
return plan;
}
static void test_check_and_plan_entries(void)
{
xce_sched_trace_rec_t* rec = new_rec(XCE_TRACE_LINE_BOUND_DEFAULT);
fixture_primary_t fp;
const sched_trace_entry_t* e;
int s1[2] = { 3, 1 };
int s2[1] = { 4 };
int s3[2] = { 2, 2 };
int s4[4] = { 1, 1, 1, 1 };
xce_alloc_plan_t p1 = make_plan(s1, 2);
xce_alloc_plan_t p2 = make_plan(s2, 1);
xce_alloc_plan_t p3 = make_plan(s3, 2);
xce_alloc_plan_t p4 = make_plan(s4, 4);
const xce_sched_obj_t* obj = (const xce_sched_obj_t*)&fp;
TEST_ASSERT_NOT_NULL(rec);
make_primary(&fp, 4, 4);
sched_trace_path_push(rec, XCE_TRACE_PATH_SINGLE, 1, 1);
sched_trace_resreq_begin(rec, &fp.p, 0); /* 0 */
sched_trace_check_begin(rec, obj); /* no entry */
TEST_ASSERT_TRUE(rec->scope_obj == obj);
sched_trace_check_end(rec, 1, true); /* 1 */
TEST_ASSERT_NULL(rec->scope_obj);
sched_trace_plan_discarded(rec, &p1); /* 2: plan 1 */
sched_trace_plan_begin(rec, &p2); /* 3: plan 2 */
sched_trace_plan_end(rec, 0, 1); /* 4 */
sched_trace_plan_begin(rec, &p3); /* 5: plan 3 */
sched_trace_plan_end(rec, 1, 0); /* 6 */
sched_trace_commit(rec); /* 7 */
sched_trace_plan_not_tried(rec, &p4, -12); /* 8: plan 4 */
sched_trace_finalize_failed(rec, -5); /* 9 */
sched_trace_no_plan(rec, XCE_TRACE_NOPLAN_NONE_GENERATED); /* 10 */
sched_trace_resreq_end(rec, 1, 0); /* 11 */
sched_trace_path_pop(rec);
TEST_ASSERT_EQUAL_INT(12, count_entries(rec));
e = entry_at(rec, 1);
TEST_ASSERT_EQUAL_INT(SCHED_TRACE_E_CHECK_END, e->type);
TEST_ASSERT_EQUAL_INT(1, e->depth); /* inside the requirement */
assert_path1(e, XCE_TRACE_PATH_SINGLE, 1, 1);
TEST_ASSERT_EQUAL_INT(1, e->u.check_end.rc);
TEST_ASSERT_TRUE(e->u.check_end.plans_follow);
e = entry_at(rec, 2);
TEST_ASSERT_EQUAL_INT(SCHED_TRACE_E_PLAN_DISCARDED, e->type);
TEST_ASSERT_EQUAL_INT(1, e->depth);
TEST_ASSERT_EQUAL_INT(1, e->u.plan.plan_no);
TEST_ASSERT_EQUAL_INT(2, e->u.plan.nbuckets);
TEST_ASSERT_EQUAL_INT(3, e->u.plan.slots[0]);
TEST_ASSERT_EQUAL_INT(1, e->u.plan.slots[1]);
TEST_ASSERT_TRUE(e->u.plan.slots != s1);
e = entry_at(rec, 3);
TEST_ASSERT_EQUAL_INT(SCHED_TRACE_E_PLAN_BEGIN, e->type);
TEST_ASSERT_EQUAL_INT(1, e->depth);
TEST_ASSERT_EQUAL_INT(2, e->u.plan.plan_no);
TEST_ASSERT_EQUAL_INT(1, e->u.plan.nbuckets);
TEST_ASSERT_EQUAL_INT(4, e->u.plan.slots[0]);
e = entry_at(rec, 4);
TEST_ASSERT_EQUAL_INT(SCHED_TRACE_E_PLAN_END, e->type);
TEST_ASSERT_EQUAL_INT(1, e->depth); /* same level as its begin */
TEST_ASSERT_EQUAL_INT(2, e->u.plan_end.plan_no);
TEST_ASSERT_EQUAL_INT(0, e->u.plan_end.rc);
TEST_ASSERT_EQUAL_INT(1, e->u.plan_end.failed_pos);
e = entry_at(rec, 6);
TEST_ASSERT_EQUAL_INT(SCHED_TRACE_E_PLAN_END, e->type);
TEST_ASSERT_EQUAL_INT(3, e->u.plan_end.plan_no);
TEST_ASSERT_EQUAL_INT(1, e->u.plan_end.rc);
e = entry_at(rec, 7);
TEST_ASSERT_EQUAL_INT(SCHED_TRACE_E_COMMIT, e->type);
TEST_ASSERT_EQUAL_INT(1, e->depth);
TEST_ASSERT_EQUAL_INT(3, e->u.commit.plan_no); /* the last plan begun */
e = entry_at(rec, 8);
TEST_ASSERT_EQUAL_INT(SCHED_TRACE_E_PLAN_NOT_TRIED, e->type);
TEST_ASSERT_EQUAL_INT(4, e->u.plan.plan_no);
TEST_ASSERT_EQUAL_INT(4, e->u.plan.nbuckets);
TEST_ASSERT_EQUAL_INT(-12, e->u.plan.err);
e = entry_at(rec, 9);
TEST_ASSERT_EQUAL_INT(SCHED_TRACE_E_FINALIZE_FAILED, e->type);
TEST_ASSERT_EQUAL_INT(3, e->u.commit.plan_no);
TEST_ASSERT_EQUAL_INT(-5, e->u.commit.err);
e = entry_at(rec, 10);
TEST_ASSERT_EQUAL_INT(SCHED_TRACE_E_NO_PLAN, e->type);
TEST_ASSERT_EQUAL_INT(XCE_TRACE_NOPLAN_NONE_GENERATED, e->u.no_plan.why);
e = entry_at(rec, 11);
TEST_ASSERT_EQUAL_INT(SCHED_TRACE_E_RESREQ_END, e->type);
TEST_ASSERT_EQUAL_INT(0, e->depth);
TEST_ASSERT_EQUAL_INT(0, rec->depth);
/* plan numbers restart with the next requirement */
sched_trace_path_push(rec, XCE_TRACE_PATH_SINGLE, 1, 1);
sched_trace_resreq_begin(rec, &fp.p, 0);
sched_trace_plan_begin(rec, &p2);
TEST_ASSERT_EQUAL_INT(1, rec->entries_tail->u.plan.plan_no);
sched_trace_plan_end(rec, 1, 0);
sched_trace_resreq_end(rec, 1, 0);
sched_trace_path_pop(rec);
sched_trace_rec_free(rec);
}
static void test_check_failed_and_no_plan_reasons(void)
{
xce_sched_trace_rec_t* rec = new_rec(XCE_TRACE_LINE_BOUND_DEFAULT);
TEST_ASSERT_NOT_NULL(rec);
sched_trace_check_begin(rec, NULL);
sched_trace_check_end(rec, 0, false);
TEST_ASSERT_EQUAL_INT(SCHED_TRACE_E_CHECK_END, rec->entries_tail->type);
TEST_ASSERT_EQUAL_INT(0, rec->entries_tail->u.check_end.rc);
TEST_ASSERT_FALSE(rec->entries_tail->u.check_end.plans_follow);
sched_trace_no_plan(rec, XCE_TRACE_NOPLAN_DECLINED);
TEST_ASSERT_EQUAL_INT(XCE_TRACE_NOPLAN_DECLINED,
rec->entries_tail->u.no_plan.why);
sched_trace_no_plan(rec, XCE_TRACE_NOPLAN_GENERATOR_FAILED);
TEST_ASSERT_EQUAL_INT(XCE_TRACE_NOPLAN_GENERATOR_FAILED,
rec->entries_tail->u.no_plan.why);
TEST_ASSERT_EQUAL_INT(3, rec->nentries);
sched_trace_rec_free(rec);
}
static xce_sched_worker_t make_worker(char* name, int idx)
{
xce_sched_worker_t w;
memset(&w, 0, sizeof(w));
w.name = name;
w.idx = idx;
return w;
}
static void test_position_and_host_entries(void)
{
xce_sched_trace_rec_t* rec = new_rec(XCE_TRACE_LINE_BOUND_DEFAULT);
const sched_trace_entry_t* e;
char n1[8] = "n01";
char n2[8] = "n02";
char n3[8] = "n03";
char n4[8] = "n04";
char n5[8] = "n05";
xce_sched_worker_t w1 = make_worker(n1, 0);
xce_sched_worker_t w2 = make_worker(n2, 1);
xce_sched_worker_t w3 = make_worker(n3, 2);
xce_sched_worker_t w4 = make_worker(n4, 3);
xce_sched_worker_t w5 = make_worker(n5, 4);
int s[2] = { 4, 2 };
xce_alloc_plan_t plan = make_plan(s, 2);
const xce_sched_obj_t* obj = (const xce_sched_obj_t*)&plan;
TEST_ASSERT_NOT_NULL(rec);
sched_trace_plan_begin(rec, &plan); /* 0, depth 0 */
sched_trace_pos_begin(rec, 1, 4); /* 1, depth 1 */
sched_trace_host_begin(rec, obj, &w1); /* no entry */
TEST_ASSERT_TRUE(rec->scope_obj == obj);
TEST_ASSERT_TRUE(rec->scope_w == &w1);
sched_trace_host_verdict(rec, XCE_TRACE_V_SELECT, 0); /* 2 */
TEST_ASSERT_NULL(rec->scope_w); /* the host scope ended */
TEST_ASSERT_NULL(rec->scope_obj);
sched_trace_host_begin(rec, obj, &w2);
sched_trace_host_verdict(rec, 0, 0); /* from rows: none -> SELECT */
sched_trace_host_begin(rec, obj, &w3);
rec->host_row_verdicts = XCE_TRACE_V_SHORTFALL | XCE_TRACE_V_LIMIT;
sched_trace_host_verdict(rec, 0, 0); /* from rows */
TEST_ASSERT_EQUAL_UINT(0, rec->host_row_verdicts);
sched_trace_host_begin(rec, obj, &w4);
sched_trace_host_verdict(rec, XCE_TRACE_V_CLAIMED, 0);
sched_trace_host_begin(rec, obj, &w5);
sched_trace_host_verdict(rec, XCE_TRACE_V_INTERNAL, -5);
sched_trace_host_begin(rec, obj, &w3);
sched_trace_host_verdict(rec, XCE_TRACE_V_ACCEPTED, 0);
sched_trace_pos_end(rec, XCE_TRACE_POS_FILLED, n3); /* 8 */
sched_trace_pos_begin(rec, 2, 2); /* 9 */
sched_trace_host_begin(rec, obj, &w1);
sched_trace_host_verdict(rec, XCE_TRACE_V_SELECT, 0); /* 10 */
sched_trace_pos_scan_error(rec, -5); /* no entry */
sched_trace_pos_end(rec, XCE_TRACE_POS_UNFILLED, NULL); /* 11 */
sched_trace_pos_begin(rec, 2, 2); /* 12 */
sched_trace_pos_end(rec, XCE_TRACE_POS_UNFILLED, NULL); /* 13 */
sched_trace_pos_begin(rec, 2, 2); /* 14 */
sched_trace_pos_end(rec, XCE_TRACE_POS_INTERNAL, n2); /* 15 */
sched_trace_plan_end(rec, 0, 2); /* 16 */
TEST_ASSERT_EQUAL_INT(17, count_entries(rec));
TEST_ASSERT_EQUAL_INT(0, rec->depth);
e = entry_at(rec, 1);
TEST_ASSERT_EQUAL_INT(SCHED_TRACE_E_POS_BEGIN, e->type);
TEST_ASSERT_EQUAL_INT(1, e->depth);
TEST_ASSERT_EQUAL_INT(1, e->u.pos_begin.pos);
TEST_ASSERT_EQUAL_INT(4, e->u.pos_begin.slots);
e = entry_at(rec, 2);
TEST_ASSERT_EQUAL_INT(SCHED_TRACE_E_HOST, e->type);
TEST_ASSERT_EQUAL_INT(2, e->depth);
TEST_ASSERT_EQUAL_STRING("n01", e->u.host.host);
TEST_ASSERT_TRUE(e->u.host.host != n1);
TEST_ASSERT_EQUAL_UINT(XCE_TRACE_V_SELECT, e->u.host.verdicts);
TEST_ASSERT_EQUAL_INT(SCHED_TRACE_PHASE_PREFILTER, e->u.host.phase);
e = entry_at(rec, 3);
TEST_ASSERT_EQUAL_STRING("n02", e->u.host.host);
TEST_ASSERT_EQUAL_UINT(XCE_TRACE_V_SELECT, e->u.host.verdicts);
TEST_ASSERT_EQUAL_INT(SCHED_TRACE_PHASE_PERHOST, e->u.host.phase);
e = entry_at(rec, 4);
TEST_ASSERT_EQUAL_STRING("n03", e->u.host.host);
TEST_ASSERT_EQUAL_UINT(XCE_TRACE_V_SHORTFALL | XCE_TRACE_V_LIMIT,
e->u.host.verdicts);
TEST_ASSERT_EQUAL_INT(SCHED_TRACE_PHASE_PERHOST, e->u.host.phase);
e = entry_at(rec, 5);
TEST_ASSERT_EQUAL_UINT(XCE_TRACE_V_CLAIMED, e->u.host.verdicts);
TEST_ASSERT_EQUAL_INT(SCHED_TRACE_PHASE_NONE, e->u.host.phase);
e = entry_at(rec, 6);
TEST_ASSERT_EQUAL_STRING("n05", e->u.host.host);
TEST_ASSERT_EQUAL_UINT(XCE_TRACE_V_INTERNAL, e->u.host.verdicts);
TEST_ASSERT_EQUAL_INT(SCHED_TRACE_PHASE_NONE, e->u.host.phase);
TEST_ASSERT_EQUAL_INT(-5, e->u.host.err);
e = entry_at(rec, 7);
TEST_ASSERT_EQUAL_UINT(XCE_TRACE_V_ACCEPTED, e->u.host.verdicts);
TEST_ASSERT_EQUAL_INT(SCHED_TRACE_PHASE_NONE, e->u.host.phase);
e = entry_at(rec, 8);
TEST_ASSERT_EQUAL_INT(SCHED_TRACE_E_POS_END, e->type);
TEST_ASSERT_EQUAL_INT(1, e->depth); /* same level as its begin */
TEST_ASSERT_EQUAL_INT(1, e->u.pos_end.pos);
TEST_ASSERT_EQUAL_INT(XCE_TRACE_POS_FILLED, e->u.pos_end.how);
TEST_ASSERT_EQUAL_STRING("n03", e->u.pos_end.host);
TEST_ASSERT_TRUE(e->u.pos_end.host != n3);
e = entry_at(rec, 11); /* UNFILLED after a scan error */
TEST_ASSERT_EQUAL_INT(SCHED_TRACE_E_POS_END, e->type);
TEST_ASSERT_EQUAL_INT(2, e->u.pos_end.pos);
TEST_ASSERT_EQUAL_INT(XCE_TRACE_POS_SCAN_ERROR, e->u.pos_end.how);
TEST_ASSERT_EQUAL_INT(-5, e->u.pos_end.err);
TEST_ASSERT_NULL(e->u.pos_end.host);
e = entry_at(rec, 13); /* the scan error belonged to the earlier one */
TEST_ASSERT_EQUAL_INT(XCE_TRACE_POS_UNFILLED, e->u.pos_end.how);
TEST_ASSERT_EQUAL_INT(0, e->u.pos_end.err);
e = entry_at(rec, 15);
TEST_ASSERT_EQUAL_INT(XCE_TRACE_POS_INTERNAL, e->u.pos_end.how);
TEST_ASSERT_EQUAL_STRING("n02", e->u.pos_end.host);
e = entry_at(rec, 16);
TEST_ASSERT_EQUAL_INT(SCHED_TRACE_E_PLAN_END, e->type);
TEST_ASSERT_EQUAL_INT(0, e->depth);
TEST_ASSERT_EQUAL_INT(2, e->u.plan_end.failed_pos);
/* the host names are copies */
strcpy(n1, "zz");
strcpy(n3, "zz");
TEST_ASSERT_EQUAL_STRING("n01", entry_at(rec, 2)->u.host.host);
TEST_ASSERT_EQUAL_STRING("n03", entry_at(rec, 8)->u.pos_end.host);
sched_trace_rec_free(rec);
}
static void test_pos_end_scan_error_direct(void)
{
xce_sched_trace_rec_t* rec = new_rec(XCE_TRACE_LINE_BOUND_DEFAULT);
TEST_ASSERT_NOT_NULL(rec);
sched_trace_pos_begin(rec, 1, 1);
sched_trace_pos_end(rec, XCE_TRACE_POS_SCAN_ERROR, NULL);
TEST_ASSERT_EQUAL_INT(2, rec->nentries);
TEST_ASSERT_EQUAL_INT(XCE_TRACE_POS_SCAN_ERROR,
rec->entries_tail->u.pos_end.how);
TEST_ASSERT_EQUAL_INT(1, rec->entries_tail->u.pos_end.pos);
sched_trace_rec_free(rec);
}
/* Drives one call of every entry kind; returns the number of entries it
* makes (every call below stores exactly one entry). */
static int emit_every_kind(xce_sched_trace_rec_t* rec)
{
static int slots[1] = { 1 };
static char hname[] = "n01";
static fixture_primary_t fp;
xce_alloc_plan_t plan = make_plan(slots, 1);
xce_sched_worker_t w = make_worker(hname, 0);
sched_trace_reason_count_t rc = { 1, 1 };
sched_trace_row_t row;
make_primary(&fp, 1, 1);
memset(&row, 0, sizeof(row));
row.res = "mem";
row.total = 1;
sched_trace_path_push(rec, XCE_TRACE_PATH_ALT, 1, 2);
sched_trace_alt_skipped(rec, 1, 2); /* 1 */
sched_trace_path_pop(rec);
sched_trace_path_push(rec, XCE_TRACE_PATH_ALT, 2, 2);
sched_trace_resreq_begin(rec, NULL, 1); /* 2 */
sched_trace_path_push(rec, XCE_TRACE_PATH_MEMBER, 1, 1);
sched_trace_resreq_begin(rec, &fp.p, 0); /* 3 */
sched_trace_check_begin(rec, NULL);
sched_trace_row(rec, &row); /* 4 */
sched_trace_check_end(rec, 1, true); /* 5 */
sched_trace_no_plan(rec, XCE_TRACE_NOPLAN_DECLINED); /* 6 */
sched_trace_plan_discarded(rec, &plan); /* 7 */
sched_trace_plan_not_tried(rec, &plan, -12); /* 8 */
sched_trace_plan_begin(rec, &plan); /* 9 */
sched_trace_pos_begin(rec, 1, 1); /* 10 */
sched_trace_host_begin(rec, NULL, &w);
sched_trace_row(rec, &row); /* 11, held */
sched_trace_host_verdict(rec, XCE_TRACE_V_ACCEPTED, 0); /* 12 */
sched_trace_pos_end(rec, XCE_TRACE_POS_FILLED, hname); /* 13 */
sched_trace_plan_end(rec, 1, 0); /* 14 */
sched_trace_rollback_host(rec, hname); /* 15 */
sched_trace_rollback_host(rec, hname); /* merged */
sched_trace_commit(rec); /* 16 */
sched_trace_finalize_failed(rec, -5); /* 17 */
sched_trace_search_end(rec, 1); /* 18 */
sched_trace_resreq_end(rec, 1, 0); /* 19 */
sched_trace_path_pop(rec);
sched_trace_member_failed(rec, 1, 1, -12); /* 20 */
sched_trace_attempt_reasons(rec, &rc, 1, NULL, 0, NULL, 0); /* 21 */
sched_trace_resreq_end(rec, 0, 0); /* 22 */
sched_trace_path_pop(rec);
sched_trace_alt_result(rec, 0, 2); /* 23 */
return 23;
}
static void test_every_entry_kind_is_one_entry(void)
{
xce_sched_trace_rec_t* rec = new_rec(XCE_TRACE_LINE_BOUND_DEFAULT);
const sched_trace_entry_t* e;
int seen[SCHED_TRACE_E_NTYPES];
int n;
int t;
TEST_ASSERT_NOT_NULL(rec);
memset(seen, 0, sizeof(seen));
n = emit_every_kind(rec);
TEST_ASSERT_EQUAL_INT(n, rec->nentries);
TEST_ASSERT_EQUAL_INT(n, count_entries(rec));
TEST_ASSERT_EQUAL_INT(0, rec->counters.omitted_lines);
TEST_ASSERT_EQUAL_INT(0, rec->depth);
TEST_ASSERT_EQUAL_INT(0, rec->resreq_open);
TEST_ASSERT_EQUAL_INT(0, rec->path.depth);
TEST_ASSERT_FALSE(rec->incomplete);
for (e = rec->entries; e != NULL; e = e->next) {
TEST_ASSERT_TRUE(e->type < SCHED_TRACE_E_NTYPES);
seen[e->type]++;
}
TEST_ASSERT_EQUAL_INT(0, seen[SCHED_TRACE_E_POS_SUMMARY]);
sched_trace_rec_free(rec);
/* summary level: the position summary replaces the host lines (the
* host verdict and its row) and the position end, so the same calls
* make two entries fewer */
rec = new_rec_level(XCE_TRACE_LINE_BOUND_DEFAULT, XCE_TRACE_LEVEL_SUMMARY);
TEST_ASSERT_NOT_NULL(rec);
TEST_ASSERT_EQUAL_INT(n, emit_every_kind(rec));
TEST_ASSERT_EQUAL_INT(n - 2, rec->nentries);
for (e = rec->entries; e != NULL; e = e->next) {
TEST_ASSERT_TRUE(e->type != SCHED_TRACE_E_HOST);
TEST_ASSERT_TRUE(e->type != SCHED_TRACE_E_HOST_ROW);
TEST_ASSERT_TRUE(e->type != SCHED_TRACE_E_POS_END);
seen[e->type]++;
}
for (t = 0; t < SCHED_TRACE_E_NTYPES; t++) {
TEST_ASSERT_TRUE_MESSAGE(seen[t] > 0, "entry kind never stored");
}
sched_trace_rec_free(rec);
}
/* ---- line budget (9.1, 9.2) ------------------------------------------- */
/* Appends `k` entries (each alt_skipped call is one line). */
static void emit_lines(xce_sched_trace_rec_t* rec, int k)
{
int i;
for (i = 1; i <= k; i++) {
sched_trace_alt_skipped(rec, i, k);
}
}
static void check_budget(int bound, int k, int want_stored, int want_omitted)
{
xce_sched_trace_rec_t* rec = new_rec(bound);
TEST_ASSERT_NOT_NULL(rec);
emit_lines(rec, k);
TEST_ASSERT_EQUAL_INT(want_stored, rec->nentries);
TEST_ASSERT_EQUAL_INT(want_stored, count_entries(rec));
TEST_ASSERT_EQUAL_INT(want_omitted, rec->counters.omitted_lines);
TEST_ASSERT_FALSE(rec->incomplete);
/* the stored ones are the first ones, in order */
if (want_stored > 0) {
TEST_ASSERT_EQUAL_INT(1, rec->entries->u.alt.i);
TEST_ASSERT_EQUAL_INT(want_stored, rec->entries_tail->u.alt.i);
}
sched_trace_rec_free(rec);
}
static void test_budget_edges(void)
{
const int b = XCE_TRACE_LINE_BOUND_MIN;
check_budget(b, b - 1, b - 1, 0);
check_budget(b, b, b, 0);
check_budget(b, b + 1, b, 1);
check_budget(b, b + 37, b, 37);
check_budget(XCE_TRACE_LINE_BOUND_DEFAULT,
XCE_TRACE_LINE_BOUND_DEFAULT - 1,
XCE_TRACE_LINE_BOUND_DEFAULT - 1, 0);
check_budget(XCE_TRACE_LINE_BOUND_DEFAULT, XCE_TRACE_LINE_BOUND_DEFAULT,
XCE_TRACE_LINE_BOUND_DEFAULT, 0);
check_budget(XCE_TRACE_LINE_BOUND_DEFAULT,
XCE_TRACE_LINE_BOUND_DEFAULT + 1,
XCE_TRACE_LINE_BOUND_DEFAULT, 1);
}
/* Past the bound nothing more is stored or allocated, every kind counts
* one omitted line, and the builder state stays balanced. */
static void test_budget_counts_every_kind_and_keeps_state(void)
{
const int b = XCE_TRACE_LINE_BOUND_MIN;
xce_sched_trace_rec_t* rec = new_rec(b);
size_t bytes;
int n;
TEST_ASSERT_NOT_NULL(rec);
emit_lines(rec, b);
TEST_ASSERT_EQUAL_INT(b, rec->nentries);
bytes = sched_trace_rec_bytes(rec);
n = emit_every_kind(rec);
TEST_ASSERT_EQUAL_INT(b, rec->nentries);
TEST_ASSERT_EQUAL_INT(n, rec->counters.omitted_lines);
TEST_ASSERT_EQUAL_UINT(bytes, sched_trace_rec_bytes(rec));
TEST_ASSERT_EQUAL_INT(0, rec->depth);
TEST_ASSERT_EQUAL_INT(0, rec->resreq_open);
TEST_ASSERT_EQUAL_INT(0, rec->path.depth);
TEST_ASSERT_NULL(rec->scope_w);
TEST_ASSERT_NULL(rec->scope_obj);
/* path push/pop and the scope-only calls are not lines */
sched_trace_path_push(rec, XCE_TRACE_PATH_SINGLE, 1, 1);
sched_trace_check_begin(rec, NULL);
sched_trace_pos_scan_error(rec, -5);
sched_trace_path_pop(rec);
TEST_ASSERT_EQUAL_INT(n, rec->counters.omitted_lines);
sched_trace_rec_free(rec);
}
/* An entry that straddles the bound: the begin is stored, its end is not,
* and the nesting level still returns to the top. */
static void test_budget_cut_inside_a_scope(void)
{
const int b = XCE_TRACE_LINE_BOUND_MIN;
xce_sched_trace_rec_t* rec = new_rec(b);
int s[1] = { 1 };
xce_alloc_plan_t plan = make_plan(s, 1);
TEST_ASSERT_NOT_NULL(rec);
emit_lines(rec, b - 1);
sched_trace_plan_begin(rec, &plan); /* the b-th line, stored */
sched_trace_pos_begin(rec, 1, 1); /* omitted */
sched_trace_pos_end(rec, XCE_TRACE_POS_UNFILLED, NULL);
sched_trace_plan_end(rec, 0, 1);
TEST_ASSERT_EQUAL_INT(b, rec->nentries);
TEST_ASSERT_EQUAL_INT(SCHED_TRACE_E_PLAN_BEGIN, rec->entries_tail->type);
TEST_ASSERT_EQUAL_INT(3, rec->counters.omitted_lines);
TEST_ASSERT_EQUAL_INT(0, rec->depth);
sched_trace_rec_free(rec);
}
/* ---- plan counts and search-end statements (4.6, 4.7, 4.9) ------------ */
/* Counter slot of a verdict kind (its bit position). */
static int vi(unsigned verdict)
{
return __builtin_ctz(verdict);
}
static void assert_search_end(const sched_trace_entry_t* e, int g, int t,
int d, int x, int rc,
sched_trace_search_stmt_t stmt)
{
TEST_ASSERT_EQUAL_INT(SCHED_TRACE_E_SEARCH_END, e->type);
TEST_ASSERT_EQUAL_INT_MESSAGE(g, e->u.search_end.generated, "generated");
TEST_ASSERT_EQUAL_INT_MESSAGE(t, e->u.search_end.tried, "tried");
TEST_ASSERT_EQUAL_INT_MESSAGE(d, e->u.search_end.discarded, "discarded");
TEST_ASSERT_EQUAL_INT_MESSAGE(x, e->u.search_end.not_tried, "not tried");
TEST_ASSERT_EQUAL_INT(rc, e->u.search_end.rc);
TEST_ASSERT_EQUAL_INT_MESSAGE(stmt, e->u.search_end.stmt, "statement");
}
static void assert_req_counters(const xce_sched_trace_rec_t* rec, int gen0,
int alldisc, int noplace)
{
TEST_ASSERT_EQUAL_INT_MESSAGE(gen0, rec->counters.reqs_none_generated,
"reqs_none_generated");
TEST_ASSERT_EQUAL_INT_MESSAGE(alldisc, rec->counters.reqs_all_discarded,
"reqs_all_discarded");
TEST_ASSERT_EQUAL_INT_MESSAGE(noplace, rec->counters.reqs_none_placed,
"reqs_none_placed");
}
/* Opens a SINGLE requirement with its cluster-wide check passed. */
static void open_single(xce_sched_trace_rec_t* rec, fixture_primary_t* fp)
{
make_primary(fp, 1, 1);
sched_trace_path_push(rec, XCE_TRACE_PATH_SINGLE, 1, 1);
sched_trace_resreq_begin(rec, &fp->p, 0);
sched_trace_check_begin(rec, NULL);
sched_trace_check_end(rec, 1, true);
}
static void close_single(xce_sched_trace_rec_t* rec, int rc)
{
sched_trace_resreq_end(rec, rc, 0);
sched_trace_path_pop(rec);
}
static void test_search_end_none_generated(void)
{
xce_sched_trace_rec_t* rec = new_rec(XCE_TRACE_LINE_BOUND_DEFAULT);
fixture_primary_t fp;
TEST_ASSERT_NOT_NULL(rec);
open_single(rec, &fp);
sched_trace_no_plan(rec, XCE_TRACE_NOPLAN_NONE_GENERATED);
sched_trace_search_end(rec, 0);
assert_search_end(rec->entries_tail, 0, 0, 0, 0, 0,
SCHED_TRACE_SEARCH_NONE_GENERATED);
TEST_ASSERT_EQUAL_INT(1, rec->entries_tail->depth); /* in the req */
assert_path1(rec->entries_tail, XCE_TRACE_PATH_SINGLE, 1, 1);
close_single(rec, 0);
assert_req_counters(rec, 1, 0, 0);
TEST_ASSERT_EQUAL_INT(0, rec->counters.plans_tried);
TEST_ASSERT_EQUAL_INT(0, rec->counters.plans_discarded);
sched_trace_rec_free(rec);
}
static void test_search_end_all_discarded(void)
{
xce_sched_trace_rec_t* rec = new_rec(XCE_TRACE_LINE_BOUND_DEFAULT);
fixture_primary_t fp;
int s[1] = { 1 };
xce_alloc_plan_t plan = make_plan(s, 1);
TEST_ASSERT_NOT_NULL(rec);
open_single(rec, &fp);
sched_trace_plan_discarded(rec, &plan);
sched_trace_plan_discarded(rec, &plan);
sched_trace_search_end(rec, 0);
assert_search_end(rec->entries_tail, 2, 0, 2, 0, 0,
SCHED_TRACE_SEARCH_ALL_DISCARDED);
close_single(rec, 0);
assert_req_counters(rec, 0, 1, 0);
TEST_ASSERT_EQUAL_INT(0, rec->counters.plans_tried);
TEST_ASSERT_EQUAL_INT(2, rec->counters.plans_discarded);
sched_trace_rec_free(rec);
}
static void test_search_end_none_placed(void)
{
xce_sched_trace_rec_t* rec = new_rec(XCE_TRACE_LINE_BOUND_DEFAULT);
fixture_primary_t fp;
int s[1] = { 1 };
xce_alloc_plan_t plan = make_plan(s, 1);
TEST_ASSERT_NOT_NULL(rec);
open_single(rec, &fp);
sched_trace_plan_discarded(rec, &plan);
sched_trace_plan_begin(rec, &plan);
sched_trace_pos_begin(rec, 1, 1);
sched_trace_pos_end(rec, XCE_TRACE_POS_UNFILLED, NULL);
sched_trace_plan_end(rec, 0, 1);
sched_trace_plan_begin(rec, &plan);
sched_trace_plan_end(rec, 0, 1);
sched_trace_search_end(rec, 0);
assert_search_end(rec->entries_tail, 3, 2, 1, 0, 0,
SCHED_TRACE_SEARCH_NONE_PLACED);
close_single(rec, 0);
assert_req_counters(rec, 0, 0, 1);
TEST_ASSERT_EQUAL_INT(2, rec->counters.plans_tried);
TEST_ASSERT_EQUAL_INT(1, rec->counters.plans_discarded);
sched_trace_rec_free(rec);
}
/* A tried plan that ended in an internal error: no "none placed", whatever
* rc the search returns. */
static void test_search_end_plan_internal_error_has_no_statement(void)
{
xce_sched_trace_rec_t* rec = new_rec(XCE_TRACE_LINE_BOUND_DEFAULT);
fixture_primary_t fp;
int s[1] = { 1 };
xce_alloc_plan_t plan = make_plan(s, 1);
TEST_ASSERT_NOT_NULL(rec);
open_single(rec, &fp);
sched_trace_plan_begin(rec, &plan);
sched_trace_plan_end(rec, 0, 1);
sched_trace_plan_begin(rec, &plan);
sched_trace_plan_end(rec, -5, 1);
sched_trace_search_end(rec, 0);
assert_search_end(rec->entries_tail, 2, 2, 0, 0, 0,
SCHED_TRACE_SEARCH_NO_STMT);
close_single(rec, 0);
open_single(rec, &fp);
sched_trace_plan_begin(rec, &plan);
sched_trace_plan_end(rec, -5, 1);
sched_trace_search_end(rec, -5);
assert_search_end(rec->entries_tail, 1, 1, 0, 0, -5,
SCHED_TRACE_SEARCH_NO_STMT);
close_single(rec, -5);
assert_req_counters(rec, 0, 0, 0);
TEST_ASSERT_EQUAL_INT(3, rec->counters.plans_tried);
sched_trace_rec_free(rec);
}
/* A plan not tried (internal error) after a discarded one: not every
* generated plan was discarded, and none was tried, so no statement. */
static void test_search_end_not_tried_after_discarded(void)
{
xce_sched_trace_rec_t* rec = new_rec(XCE_TRACE_LINE_BOUND_DEFAULT);
fixture_primary_t fp;
int s[1] = { 1 };
xce_alloc_plan_t plan = make_plan(s, 1);
TEST_ASSERT_NOT_NULL(rec);
open_single(rec, &fp);
sched_trace_plan_discarded(rec, &plan);
sched_trace_plan_not_tried(rec, &plan, -12);
sched_trace_search_end(rec, -12);
assert_search_end(rec->entries_tail, 2, 0, 1, 1, -12,
SCHED_TRACE_SEARCH_NO_STMT);
close_single(rec, -12);
/* the same plans with rc 0 still make no statement: d != g, t == 0 */
open_single(rec, &fp);
sched_trace_plan_discarded(rec, &plan);
sched_trace_plan_not_tried(rec, &plan, -12);
sched_trace_search_end(rec, 0);
assert_search_end(rec->entries_tail, 2, 0, 1, 1, 0,
SCHED_TRACE_SEARCH_NO_STMT);
close_single(rec, 0);
assert_req_counters(rec, 0, 0, 0);
TEST_ASSERT_EQUAL_INT(0, rec->counters.plans_tried);
TEST_ASSERT_EQUAL_INT(2, rec->counters.plans_discarded);
sched_trace_rec_free(rec);
}
/* rc > 0 (committed) and rc < 0 never carry a statement, even when the
* counts alone would match one. */
static void test_search_end_nonzero_rc_has_no_statement(void)
{
xce_sched_trace_rec_t* rec = new_rec(XCE_TRACE_LINE_BOUND_DEFAULT);
fixture_primary_t fp;
int s[1] = { 1 };
xce_alloc_plan_t plan = make_plan(s, 1);
TEST_ASSERT_NOT_NULL(rec);
open_single(rec, &fp);
sched_trace_plan_discarded(rec, &plan);
sched_trace_plan_begin(rec, &plan);
sched_trace_plan_end(rec, 1, 0);
sched_trace_commit(rec);
sched_trace_search_end(rec, 1);
assert_search_end(rec->entries_tail, 2, 1, 1, 0, 1,
SCHED_TRACE_SEARCH_NO_STMT);
close_single(rec, 1);
open_single(rec, &fp); /* generator failed: no plan, rc < 0 */
sched_trace_no_plan(rec, XCE_TRACE_NOPLAN_GENERATOR_FAILED);
sched_trace_search_end(rec, -12);
assert_search_end(rec->entries_tail, 0, 0, 0, 0, -12,
SCHED_TRACE_SEARCH_NO_STMT);
close_single(rec, -12);
assert_req_counters(rec, 0, 0, 0);
sched_trace_rec_free(rec);
}
/* Counts belong to the requirement the search ran for: they restart with
* each member, while the decision-wide counters add up. */
static void test_search_end_counts_per_requirement(void)
{
xce_sched_trace_rec_t* rec = new_rec(XCE_TRACE_LINE_BOUND_DEFAULT);
fixture_primary_t fp;
int s[1] = { 1 };
xce_alloc_plan_t plan = make_plan(s, 1);
TEST_ASSERT_NOT_NULL(rec);
make_primary(&fp, 1, 1);
sched_trace_path_push(rec, XCE_TRACE_PATH_COMPOUND, 1, 1);
sched_trace_resreq_begin(rec, NULL, 2);
sched_trace_path_push(rec, XCE_TRACE_PATH_MEMBER, 1, 2);
sched_trace_resreq_begin(rec, &fp.p, 0);
sched_trace_plan_begin(rec, &plan);
sched_trace_plan_end(rec, 1, 0);
sched_trace_commit(rec);
sched_trace_search_end(rec, 1);
assert_search_end(rec->entries_tail, 1, 1, 0, 0, 1,
SCHED_TRACE_SEARCH_NO_STMT);
TEST_ASSERT_EQUAL_INT(2, rec->entries_tail->depth);
TEST_ASSERT_EQUAL_INT(2, rec->entries_tail->path.depth);
TEST_ASSERT_EQUAL_INT(XCE_TRACE_PATH_MEMBER,
rec->entries_tail->path.lvl[1].kind);
sched_trace_resreq_end(rec, 1, 0);
sched_trace_path_pop(rec);
sched_trace_path_push(rec, XCE_TRACE_PATH_MEMBER, 2, 2);
sched_trace_resreq_begin(rec, &fp.p, 0);
sched_trace_plan_discarded(rec, &plan);
sched_trace_search_end(rec, 0);
assert_search_end(rec->entries_tail, 1, 0, 1, 0, 0,
SCHED_TRACE_SEARCH_ALL_DISCARDED);
sched_trace_resreq_end(rec, 0, 0);
sched_trace_path_pop(rec);
sched_trace_resreq_end(rec, 0, 0);
sched_trace_path_pop(rec);
assert_req_counters(rec, 0, 1, 0);
TEST_ASSERT_EQUAL_INT(1, rec->counters.plans_tried);
TEST_ASSERT_EQUAL_INT(1, rec->counters.plans_discarded);
sched_trace_rec_free(rec);
}
/* ---- counters under truncation (9.3) ---------------------------------- */
/*
* One decision with every requirement outcome and every verdict kind:
* requirement 1: two plans discarded (all discarded);
* requirement 2: one plan tried; position 1 sees a host with a shortfall
* and a limit hit, a selection mismatch, an already-claimed host, an
* internal error and an accepted host; position 2 is unfilled, the
* claim is released (none placed);
* requirement 3: no plan generated.
* Returns the lines it makes at full level.
*/
static int drive_mixed(xce_sched_trace_rec_t* rec)
{
static fixture_primary_t fp;
static char names[5][8] = { "n01", "n02", "n03", "n04",
"n05" };
xce_sched_worker_t w[5];
int s[2] = { 1, 1 };
xce_alloc_plan_t plan = make_plan(s, 2);
int k;
for (k = 0; k < 5; k++) {
w[k] = make_worker(names[k], k);
}
make_primary(&fp, 2, 2);
sched_trace_path_push(rec, XCE_TRACE_PATH_SINGLE, 1, 1);
sched_trace_resreq_begin(rec, &fp.p, 0); /* 1 */
sched_trace_plan_discarded(rec, &plan); /* 2 */
sched_trace_plan_discarded(rec, &plan); /* 3 */
sched_trace_search_end(rec, 0); /* 4 */
sched_trace_resreq_end(rec, 0, 0); /* 5 */
sched_trace_path_pop(rec);
sched_trace_path_push(rec, XCE_TRACE_PATH_SINGLE, 1, 1);
sched_trace_resreq_begin(rec, &fp.p, 0); /* 6 */
sched_trace_plan_begin(rec, &plan); /* 7 */
sched_trace_pos_begin(rec, 1, 1); /* 8 */
sched_trace_host_begin(rec, NULL, &w[0]);
sched_trace_host_limit_hit(rec, "lim_host", "h=n01");
rec->host_row_verdicts |= XCE_TRACE_V_SHORTFALL;
sched_trace_host_verdict(rec, 0, 0); /* 9 */
sched_trace_host_begin(rec, NULL, &w[1]);
sched_trace_host_verdict(rec, XCE_TRACE_V_SELECT, 0); /* 10 */
sched_trace_host_begin(rec, NULL, &w[2]);
sched_trace_host_verdict(rec, XCE_TRACE_V_CLAIMED, 0); /* 11 */
sched_trace_host_begin(rec, NULL, &w[3]);
sched_trace_host_verdict(rec, XCE_TRACE_V_INTERNAL, -5); /* 12 */
sched_trace_host_begin(rec, NULL, &w[4]);
sched_trace_host_verdict(rec, XCE_TRACE_V_ACCEPTED, 0); /* 13 */
sched_trace_pos_end(rec, XCE_TRACE_POS_FILLED, "n05"); /* 14 */
sched_trace_pos_begin(rec, 2, 1); /* 15 */
sched_trace_pos_end(rec, XCE_TRACE_POS_UNFILLED, NULL); /* 16 */
sched_trace_rollback_host(rec, "n05"); /* 17 */
sched_trace_plan_end(rec, 0, 2); /* 18 */
sched_trace_search_end(rec, 0); /* 19 */
sched_trace_resreq_end(rec, 0, 0); /* 20 */
sched_trace_path_pop(rec);
sched_trace_path_push(rec, XCE_TRACE_PATH_SINGLE, 1, 1);
sched_trace_resreq_begin(rec, &fp.p, 0); /* 21 */
sched_trace_no_plan(rec, XCE_TRACE_NOPLAN_NONE_GENERATED); /* 22 */
sched_trace_search_end(rec, 0); /* 23 */
sched_trace_resreq_end(rec, 0, 0); /* 24 */
sched_trace_path_pop(rec);
return 24;
}
static void assert_mixed_counters(const xce_sched_trace_rec_t* rec)
{
const sched_trace_counters_t* c = &rec->counters;
TEST_ASSERT_EQUAL_INT(1, c->plans_tried);
TEST_ASSERT_EQUAL_INT(2, c->plans_discarded);
assert_req_counters(rec, 1, 1, 1);
TEST_ASSERT_EQUAL_INT(1, c->host_evals[vi(XCE_TRACE_V_ACCEPTED)]);
TEST_ASSERT_EQUAL_INT(1, c->host_evals[vi(XCE_TRACE_V_SELECT)]);
TEST_ASSERT_EQUAL_INT(1, c->host_evals[vi(XCE_TRACE_V_SHORTFALL)]);
TEST_ASSERT_EQUAL_INT(1, c->host_evals[vi(XCE_TRACE_V_LIMIT)]);
TEST_ASSERT_EQUAL_INT(1, c->host_evals[vi(XCE_TRACE_V_CLAIMED)]);
TEST_ASSERT_EQUAL_INT(1, c->host_evals[vi(XCE_TRACE_V_INTERNAL)]);
}
static void test_counters_exact_after_truncation(void)
{
const int b = XCE_TRACE_LINE_BOUND_MIN;
xce_sched_trace_rec_t* rec;
int n;
/* untruncated, full level */
rec = new_rec(XCE_TRACE_LINE_BOUND_DEFAULT);
TEST_ASSERT_NOT_NULL(rec);
n = drive_mixed(rec);
TEST_ASSERT_EQUAL_INT(n, rec->nentries);
TEST_ASSERT_EQUAL_INT(0, rec->counters.omitted_lines);
assert_mixed_counters(rec);
sched_trace_rec_free(rec);
/* truncated before the decision starts: nothing stored, same counts */
rec = new_rec(b);
TEST_ASSERT_NOT_NULL(rec);
emit_lines(rec, b);
TEST_ASSERT_EQUAL_INT(n, drive_mixed(rec));
TEST_ASSERT_EQUAL_INT(b, rec->nentries);
TEST_ASSERT_EQUAL_INT(n, rec->counters.omitted_lines);
assert_mixed_counters(rec);
sched_trace_rec_free(rec);
/* truncated part way through */
rec = new_rec(b);
TEST_ASSERT_NOT_NULL(rec);
emit_lines(rec, b - 10);
drive_mixed(rec);
TEST_ASSERT_EQUAL_INT(b, rec->nentries);
TEST_ASSERT_EQUAL_INT(n - 10, rec->counters.omitted_lines);
assert_mixed_counters(rec);
sched_trace_rec_free(rec);
/* summary level, truncated: the five host lines and the position end
* become one summary line per position, and the counts are the same */
rec = new_rec_level(b, XCE_TRACE_LEVEL_SUMMARY);
TEST_ASSERT_NOT_NULL(rec);
emit_lines(rec, b);
drive_mixed(rec);
TEST_ASSERT_EQUAL_INT(b, rec->nentries);
TEST_ASSERT_EQUAL_INT(n - 5, rec->counters.omitted_lines);
assert_mixed_counters(rec);
sched_trace_rec_free(rec);
}
/* ---- summary level: position summaries (5.9, 5.10) -------------------- */
static const sched_trace_limit_tally_t*
find_tally(const sched_trace_entry_t* e, const char* limit, const char* scope)
{
int k;
for (k = 0; k < e->u.pos_summary.nlimits; k++) {
const sched_trace_limit_tally_t* t = &e->u.pos_summary.limits[k];
if ((strcmp(t->limit, limit) == 0) && (strcmp(t->scope, scope) == 0)) {
return t;
}
}
return NULL;
}
static void test_summary_position_tallies(void)
{
xce_sched_trace_rec_t* rec
= new_rec_level(XCE_TRACE_LINE_BOUND_DEFAULT, XCE_TRACE_LEVEL_SUMMARY);
const sched_trace_entry_t* e;
const sched_trace_limit_tally_t* t;
char names[7][8]
= { "n01", "n02", "n03", "n04", "n05", "n06", "n07" };
char lim_user[16] = "lim_user";
char scope_u[16] = "u=alice";
xce_sched_worker_t w[7];
int s[2] = { 2, 2 };
xce_alloc_plan_t plan = make_plan(s, 2);
int k;
TEST_ASSERT_NOT_NULL(rec);
for (k = 0; k < 7; k++) {
w[k] = make_worker(names[k], k);
}
sched_trace_plan_begin(rec, &plan); /* 0 */
sched_trace_pos_begin(rec, 1, 2); /* 1 */
/* n01: shortfall and a host-limit hit, counted once under each */
sched_trace_host_begin(rec, NULL, &w[0]);
rec->host_row_verdicts |= XCE_TRACE_V_SHORTFALL;
sched_trace_host_limit_hit(rec, "lim_host", "u=alice,h=n01");
sched_trace_host_verdict(rec, 0, 0);
/* n02: the same user limit hit on two rows counts once */
sched_trace_host_begin(rec, NULL, &w[1]);
sched_trace_host_limit_hit(rec, lim_user, scope_u);
sched_trace_host_limit_hit(rec, lim_user, scope_u);
sched_trace_host_verdict(rec, 0, 0);
/* n03: the user limit again; n04: pre-filter; n05 claimed */
sched_trace_host_begin(rec, NULL, &w[2]);
sched_trace_host_limit_hit(rec, lim_user, scope_u);
sched_trace_host_verdict(rec, 0, 0);
sched_trace_host_begin(rec, NULL, &w[3]);
sched_trace_host_verdict(rec, XCE_TRACE_V_SELECT, 0);
sched_trace_host_begin(rec, NULL, &w[4]);
sched_trace_host_verdict(rec, XCE_TRACE_V_CLAIMED, 0);
sched_trace_host_begin(rec, NULL, &w[5]);
sched_trace_host_verdict(rec, XCE_TRACE_V_ACCEPTED, 0);
sched_trace_pos_end(rec, XCE_TRACE_POS_FILLED, names[5]); /* 2 */
sched_trace_pos_begin(rec, 2, 2); /* 3 */
/* n02 again: a limit hit whose host then ends in an internal error
* is not a limit-exceeded verdict and is not tallied */
sched_trace_host_begin(rec, NULL, &w[1]);
sched_trace_host_limit_hit(rec, lim_user, scope_u);
sched_trace_host_verdict(rec, XCE_TRACE_V_INTERNAL, -5);
sched_trace_host_begin(rec, NULL, &w[6]);
sched_trace_host_limit_hit(rec, lim_user, scope_u);
sched_trace_host_verdict(rec, 0, 0);
sched_trace_pos_scan_error(rec, -5);
sched_trace_pos_end(rec, XCE_TRACE_POS_UNFILLED, NULL); /* 4 */
sched_trace_plan_end(rec, 0, 2); /* 5 */
/* no host lines and no position-end lines at summary level */
TEST_ASSERT_EQUAL_INT(6, rec->nentries);
TEST_ASSERT_EQUAL_INT(0, rec->counters.omitted_lines);
TEST_ASSERT_EQUAL_INT(0, rec->depth);
e = entry_at(rec, 2);
TEST_ASSERT_EQUAL_INT(SCHED_TRACE_E_POS_SUMMARY, e->type);
TEST_ASSERT_EQUAL_INT(1, e->depth); /* closes the position */
TEST_ASSERT_EQUAL_INT(1, e->u.pos_summary.pos);
TEST_ASSERT_EQUAL_INT(XCE_TRACE_POS_FILLED, e->u.pos_summary.how);
TEST_ASSERT_EQUAL_STRING("n06", e->u.pos_summary.host);
TEST_ASSERT_TRUE(e->u.pos_summary.host != names[5]);
TEST_ASSERT_EQUAL_INT(1, e->u.pos_summary.counts[vi(XCE_TRACE_V_ACCEPTED)]);
TEST_ASSERT_EQUAL_INT(1, e->u.pos_summary.counts[vi(XCE_TRACE_V_SELECT)]);
TEST_ASSERT_EQUAL_INT(1,
e->u.pos_summary.counts[vi(XCE_TRACE_V_SHORTFALL)]);
TEST_ASSERT_EQUAL_INT(3, e->u.pos_summary.counts[vi(XCE_TRACE_V_LIMIT)]);
TEST_ASSERT_EQUAL_INT(1, e->u.pos_summary.counts[vi(XCE_TRACE_V_CLAIMED)]);
TEST_ASSERT_EQUAL_INT(0,
e->u.pos_summary.counts[vi(XCE_TRACE_V_INTERNAL)]);
TEST_ASSERT_EQUAL_INT(2, e->u.pos_summary.nlimits);
t = find_tally(e, "lim_host", "u=alice,h=n01");
TEST_ASSERT_NOT_NULL(t);
TEST_ASSERT_EQUAL_INT(1, t->count);
t = find_tally(e, "lim_user", "u=alice");
TEST_ASSERT_NOT_NULL(t);
TEST_ASSERT_EQUAL_INT(2, t->count);
TEST_ASSERT_TRUE(t->limit != lim_user);
TEST_ASSERT_TRUE(t->scope != scope_u);
e = entry_at(rec, 4); /* the tallies restart with each position */
TEST_ASSERT_EQUAL_INT(SCHED_TRACE_E_POS_SUMMARY, e->type);
TEST_ASSERT_EQUAL_INT(2, e->u.pos_summary.pos);
TEST_ASSERT_EQUAL_INT(XCE_TRACE_POS_SCAN_ERROR, e->u.pos_summary.how);
TEST_ASSERT_EQUAL_INT(-5, e->u.pos_summary.err);
TEST_ASSERT_NULL(e->u.pos_summary.host);
TEST_ASSERT_EQUAL_INT(0, e->u.pos_summary.counts[vi(XCE_TRACE_V_ACCEPTED)]);
TEST_ASSERT_EQUAL_INT(1, e->u.pos_summary.counts[vi(XCE_TRACE_V_LIMIT)]);
TEST_ASSERT_EQUAL_INT(1,
e->u.pos_summary.counts[vi(XCE_TRACE_V_INTERNAL)]);
TEST_ASSERT_EQUAL_INT(1, e->u.pos_summary.nlimits);
t = find_tally(e, "lim_user", "u=alice");
TEST_ASSERT_NOT_NULL(t);
TEST_ASSERT_EQUAL_INT(1, t->count);
/* the decision-wide counts agree with the two summaries */
TEST_ASSERT_EQUAL_INT(4, rec->counters.host_evals[vi(XCE_TRACE_V_LIMIT)]);
TEST_ASSERT_EQUAL_INT(
1, rec->counters.host_evals[vi(XCE_TRACE_V_SHORTFALL)]);
TEST_ASSERT_EQUAL_INT(
1, rec->counters.host_evals[vi(XCE_TRACE_V_INTERNAL)]);
/* every string is a copy */
strcpy(lim_user, "zz");
strcpy(scope_u, "zz");
strcpy(names[5], "zz");
e = entry_at(rec, 2);
TEST_ASSERT_NOT_NULL(find_tally(e, "lim_user", "u=alice"));
TEST_ASSERT_EQUAL_STRING("n06", e->u.pos_summary.host);
sched_trace_rec_free(rec);
}
/* A limit hit sets the host's LIMIT row verdict at either level; outside a
* host scope (the cluster-wide check) it is not a host's hit. At full
* level no tallies are kept. */
static void test_limit_hit_sets_row_verdict(void)
{
xce_sched_trace_rec_t* rec = new_rec(XCE_TRACE_LINE_BOUND_DEFAULT);
char n1[8] = "n01";
xce_sched_worker_t w1 = make_worker(n1, 0);
TEST_ASSERT_NOT_NULL(rec);
sched_trace_check_begin(rec, NULL);
sched_trace_host_limit_hit(rec, "lim_user", "u=alice");
TEST_ASSERT_EQUAL_UINT(0, rec->host_row_verdicts);
sched_trace_check_end(rec, 1, true);
sched_trace_pos_begin(rec, 1, 1);
sched_trace_host_begin(rec, NULL, &w1);
sched_trace_host_limit_hit(rec, "lim_host", "h=n01");
TEST_ASSERT_EQUAL_UINT(XCE_TRACE_V_LIMIT, rec->host_row_verdicts);
sched_trace_host_verdict(rec, 0, 0);
TEST_ASSERT_EQUAL_INT(SCHED_TRACE_E_HOST, rec->entries_tail->type);
TEST_ASSERT_EQUAL_UINT(XCE_TRACE_V_LIMIT,
rec->entries_tail->u.host.verdicts);
sched_trace_pos_end(rec, XCE_TRACE_POS_UNFILLED, NULL);
TEST_ASSERT_EQUAL_INT(SCHED_TRACE_E_POS_END, rec->entries_tail->type);
sched_trace_rec_free(rec);
}
/* Summary level past the line bound: the position summary is one omitted
* line and the builder's tallies are released. */
static void test_summary_truncated(void)
{
const int b = XCE_TRACE_LINE_BOUND_MIN;
xce_sched_trace_rec_t* rec = new_rec_level(b, XCE_TRACE_LEVEL_SUMMARY);
char n1[8] = "n01";
xce_sched_worker_t w1 = make_worker(n1, 0);
TEST_ASSERT_NOT_NULL(rec);
emit_lines(rec, b);
sched_trace_pos_begin(rec, 1, 1);
sched_trace_host_begin(rec, NULL, &w1);
sched_trace_host_limit_hit(rec, "lim_host", "h=n01");
sched_trace_host_verdict(rec, 0, 0);
sched_trace_pos_end(rec, XCE_TRACE_POS_UNFILLED, NULL);
TEST_ASSERT_EQUAL_INT(b, rec->nentries);
TEST_ASSERT_EQUAL_INT(2, rec->counters.omitted_lines);
TEST_ASSERT_EQUAL_INT(1, rec->counters.host_evals[vi(XCE_TRACE_V_LIMIT)]);
TEST_ASSERT_EQUAL_INT(0, rec->depth);
sched_trace_rec_free(rec); /* ASAN: no leaked builder tallies */
}
/* ---- rollback merging (5.8) ------------------------------------------- */
static void test_rollback_merges_consecutive_calls(void)
{
xce_sched_trace_rec_t* rec = new_rec(XCE_TRACE_LINE_BOUND_DEFAULT);
const sched_trace_entry_t* e;
char h[3][8] = { "n01", "n02", "n03" };
char many[20][8];
int s[3] = { 1, 1, 1 };
xce_alloc_plan_t plan = make_plan(s, 3);
int k;
TEST_ASSERT_NOT_NULL(rec);
sched_trace_plan_begin(rec, &plan); /* 0 */
sched_trace_pos_begin(rec, 3, 1); /* 1 */
sched_trace_pos_end(rec, XCE_TRACE_POS_UNFILLED, NULL); /* 2 */
for (k = 0; k < 3; k++) {
sched_trace_rollback_host(rec, h[k]); /* 3 */
}
sched_trace_plan_end(rec, 0, 3); /* 4 */
sched_trace_plan_begin(rec, &plan); /* 5 */
sched_trace_pos_begin(rec, 2, 1); /* 6 */
sched_trace_pos_end(rec, XCE_TRACE_POS_UNFILLED, NULL); /* 7 */
sched_trace_rollback_host(rec, h[2]); /* 8 */
sched_trace_plan_end(rec, 0, 2); /* 9 */
sched_trace_plan_begin(rec, &plan); /* 10 */
for (k = 0; k < 20; k++) {
snprintf(many[k], sizeof(many[k]), "m%02d", k);
sched_trace_rollback_host(rec, many[k]); /* 11 */
}
sched_trace_plan_end(rec, 0, 1); /* 12 */
TEST_ASSERT_EQUAL_INT(13, rec->nentries);
e = entry_at(rec, 3);
TEST_ASSERT_EQUAL_INT(SCHED_TRACE_E_ROLLBACK, e->type);
TEST_ASSERT_EQUAL_INT(1, e->depth); /* inside the plan */
TEST_ASSERT_EQUAL_INT(3, e->u.rollback.nhosts);
TEST_ASSERT_EQUAL_STRING("n01", e->u.rollback.hosts[0]);
TEST_ASSERT_EQUAL_STRING("n02", e->u.rollback.hosts[1]);
TEST_ASSERT_EQUAL_STRING("n03", e->u.rollback.hosts[2]);
TEST_ASSERT_TRUE(e->u.rollback.hosts[0] != h[0]);
e = entry_at(rec, 8);
TEST_ASSERT_EQUAL_INT(SCHED_TRACE_E_ROLLBACK, e->type);
TEST_ASSERT_EQUAL_INT(1, e->u.rollback.nhosts);
TEST_ASSERT_EQUAL_STRING("n03", e->u.rollback.hosts[0]);
e = entry_at(rec, 11);
TEST_ASSERT_EQUAL_INT(SCHED_TRACE_E_ROLLBACK, e->type);
TEST_ASSERT_EQUAL_INT(20, e->u.rollback.nhosts);
for (k = 0; k < 20; k++) {
TEST_ASSERT_EQUAL_STRING(many[k], e->u.rollback.hosts[k]);
}
strcpy(h[0], "zz");
TEST_ASSERT_EQUAL_STRING("n01", entry_at(rec, 3)->u.rollback.hosts[0]);
sched_trace_rec_free(rec);
}
/* Any other model call between two released hosts starts a new entry. */
static void test_rollback_run_broken_by_other_calls(void)
{
xce_sched_trace_rec_t* rec = new_rec_level(XCE_TRACE_LINE_BOUND_DEFAULT,
XCE_TRACE_LEVEL_SUMMARY);
char n1[8] = "n01";
xce_sched_worker_t w1 = make_worker(n1, 0);
TEST_ASSERT_NOT_NULL(rec);
sched_trace_rollback_host(rec, "n01");
sched_trace_host_begin(rec, NULL, &w1); /* stores nothing */
sched_trace_host_verdict(rec, XCE_TRACE_V_SELECT, 0); /* summary: none */
sched_trace_rollback_host(rec, "n02");
sched_trace_alt_skipped(rec, 1, 2);
sched_trace_rollback_host(rec, "n03");
TEST_ASSERT_EQUAL_INT(4, rec->nentries);
TEST_ASSERT_EQUAL_INT(1, entry_at(rec, 0)->u.rollback.nhosts);
TEST_ASSERT_EQUAL_INT(1, entry_at(rec, 1)->u.rollback.nhosts);
TEST_ASSERT_EQUAL_STRING("n02", entry_at(rec, 1)->u.rollback.hosts[0]);
TEST_ASSERT_EQUAL_INT(1, entry_at(rec, 3)->u.rollback.nhosts);
sched_trace_rec_free(rec);
}
/* Past the bound a merged run is one omitted line, not one per host. */
static void test_rollback_truncated_is_one_line(void)
{
const int b = XCE_TRACE_LINE_BOUND_MIN;
xce_sched_trace_rec_t* rec = new_rec(b);
size_t bytes;
TEST_ASSERT_NOT_NULL(rec);
emit_lines(rec, b - 1);
sched_trace_rollback_host(rec, "n01"); /* the b-th line, stored */
sched_trace_rollback_host(rec, "n02");
TEST_ASSERT_EQUAL_INT(b, rec->nentries);
TEST_ASSERT_EQUAL_INT(2, rec->entries_tail->u.rollback.nhosts);
TEST_ASSERT_EQUAL_INT(0, rec->counters.omitted_lines);
sched_trace_alt_skipped(rec, 1, 1); /* omitted: 1 */
bytes = sched_trace_rec_bytes(rec);
sched_trace_rollback_host(rec, "n03"); /* omitted: 2 */
sched_trace_rollback_host(rec, "n04");
sched_trace_rollback_host(rec, "n05");
TEST_ASSERT_EQUAL_INT(2, rec->counters.omitted_lines);
TEST_ASSERT_EQUAL_INT(2, rec->entries_tail->u.rollback.nhosts);
TEST_ASSERT_EQUAL_UINT(bytes, sched_trace_rec_bytes(rec));
sched_trace_rec_free(rec);
}
int main(void)
{
UNITY_BEGIN();
RUN_TEST(test_constants_match_requirements);
RUN_TEST(test_wants_null_ctl_is_false);
RUN_TEST(test_wants_all_matches_every_job);
RUN_TEST(test_wants_empty_selection_matches_nothing);
RUN_TEST(test_wants_plain_job_matches_only_that_job);
RUN_TEST(test_wants_element_matches_only_that_element);
RUN_TEST(test_wants_whole_array_matches_every_element);
RUN_TEST(test_wants_unknown_yet_future_ids);
RUN_TEST(test_rec_new_null_ctl_returns_null);
RUN_TEST(test_rec_new_copies_header_from_ctl);
RUN_TEST(test_rec_new_null_policy_stays_null);
RUN_TEST(test_rec_new_does_not_keep_ctl_selection);
RUN_TEST(test_arena_strdup_copies_string);
RUN_TEST(test_arena_memdup_copies_numbers);
RUN_TEST(test_arena_alloc_is_zeroed_and_aligned);
RUN_TEST(test_rec_bytes_reports_record_and_arena);
RUN_TEST(test_rec_free_null_is_noop);
RUN_TEST(test_finish_records_kind_and_error);
RUN_TEST(test_finish_rejects_non_no_decision_kinds);
RUN_TEST(test_single_requirement_path_and_balance);
RUN_TEST(test_alternative_member_nesting_stores_full_path);
RUN_TEST(test_compound_container_path);
RUN_TEST(test_path_push_beyond_two_levels_stays_balanced);
RUN_TEST(test_requirement_level_entries);
RUN_TEST(test_attempt_reasons_empty_lists);
RUN_TEST(test_check_and_plan_entries);
RUN_TEST(test_check_failed_and_no_plan_reasons);
RUN_TEST(test_position_and_host_entries);
RUN_TEST(test_pos_end_scan_error_direct);
RUN_TEST(test_every_entry_kind_is_one_entry);
RUN_TEST(test_budget_edges);
RUN_TEST(test_budget_counts_every_kind_and_keeps_state);
RUN_TEST(test_budget_cut_inside_a_scope);
RUN_TEST(test_search_end_none_generated);
RUN_TEST(test_search_end_all_discarded);
RUN_TEST(test_search_end_none_placed);
RUN_TEST(test_search_end_plan_internal_error_has_no_statement);
RUN_TEST(test_search_end_not_tried_after_discarded);
RUN_TEST(test_search_end_nonzero_rc_has_no_statement);
RUN_TEST(test_search_end_counts_per_requirement);
RUN_TEST(test_counters_exact_after_truncation);
RUN_TEST(test_summary_position_tallies);
RUN_TEST(test_limit_hit_sets_row_verdict);
RUN_TEST(test_summary_truncated);
RUN_TEST(test_rollback_merges_consecutive_calls);
RUN_TEST(test_rollback_run_broken_by_other_calls);
RUN_TEST(test_rollback_truncated_is_one_line);
return UNITY_END();
}