/* * 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 #include #include #include #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(); }