feat(scheduler-sim-decision-trace): trace entry sequence and line budget

Task 1.4: requirement path push/pop with balanced begin/end entries
carrying the full nesting path, one entry per traced event kind, and a
line budget that stops storing at the bound and counts omitted lines.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Rongsong Shen 2026-10-08 08:05:44 +00:00
parent 383e2a90e4
commit 4f951f2b52
Signed by untrusted user: rshen
GPG key ID: 80533536B0EB651C
5 changed files with 1567 additions and 2 deletions

View file

@ -32,7 +32,7 @@
- Done when model unit tests pass under ASAN for selection matching, arena copying and size, and record finishing, and the isolation test passes with the new files scanned.
- _Requirements: 1.2, 1.3, 1.8, 2.4, 9.1, 11.2_
- [ ] 1.4 Build the trace entry sequence and the line budget, test first
- [x] 1.4 Build the trace entry sequence and the line budget, test first
- Path push and pop, with balanced requirement begin and end entries. Each entry stores its full nesting path: single, compound container, alternative i/n, or member j/m.
- Entry kinds, each rendering as exactly one line:
- requirement begin and end;
@ -340,3 +340,4 @@
- 1.3: `sched_trace_ctl_wants` whole-array selectors match elements only, not the bare key. A bare id in `trace on job <id>` can't be told apart from an array base for a job that doesn't exist yet, so 4.3's parser must emit both `{key,false}` and `{key,true}`, or the matcher must also accept `jobid == key`.
- 1.3: design's record header lists a "file tag", but `xce_sched_trace_ctl_t` has no such field; the 4.2/4.6 store/emit tasks must set it or drop it explicitly.
- 1.3: trace sources (sched_trace.c, sched_trace_capture.c) may not include <stdio.h> (core isolation test); capture copies raw strings/numbers and leaves formatting to the renderer.
- 1.4: `sched_trace_host_verdict(rec, 0, err)` means "derive from rows" (rec->host_row_verdicts shortfall/limit, else SELECT, phase per-host); an explicit SELECT is the pre-filter phase. 2.2's row capture must set host_row_verdicts and 3.3/3.4 hooks must follow this. `sched_trace_pos_scan_error` stores no entry; it turns the next UNFILLED position end into SCAN_ERROR. Plan numbers restart per requirement. trace.h forward-declares `struct xce_alloc_plan_s` (tag added in alloc_iter.h) to avoid the core.h include loop. `resreq_begin` may store a NULL req name for a NULL rusage; 4.1's renderer must tolerate it.

View file

@ -38,8 +38,12 @@
* Produced by xce_alloc_plan_iter_t.next(); the returned pointer is valid
* only until the following next() or fini() call on that same iterator
* (it is not independently heap-owned by the caller).
*
* Tagged so sched/trace.h can forward-declare it without including this
* header (which pulls in scheduler.h -> core.h, and core.h will carry the
* trace record pointer).
*/
typedef struct {
typedef struct xce_alloc_plan_s {
int nbuckets; /* number of hosts this plan needs (==
* len(bucket_slots)) */
int* bucket_slots; /* partition of maxTasks; bucket_slots[i] = slots

View file

@ -26,6 +26,13 @@
#include <stddef.h>
#include <stdint.h>
#include "resreq.h"
#include "sched/types.h"
/* Defined in sched/alloc_iter.h, which is not included here: it pulls in
* scheduler.h and so core.h. */
typedef struct xce_alloc_plan_s xce_alloc_plan_t;
/* Requirement 1.8: two detail levels; summary is the default. */
typedef enum {
XCE_TRACE_LEVEL_SUMMARY = 0,
@ -130,6 +137,140 @@ typedef struct {
int omitted_lines;
} sched_trace_counters_t;
/*
* Entry types (design.md "Logical Data Model"). Every stored entry renders
* as exactly one line in the budgeted sections (requirement, cluster-wide
* check, plan and host), so the number of stored entries is the number of
* budgeted lines. The check rows, host rows, search-end, position-summary
* and rollback entries are added by the tasks that capture them.
*/
typedef enum {
SCHED_TRACE_E_RESREQ_BEGIN = 0, /* requirement named by the path */
SCHED_TRACE_E_RESREQ_END, /* its outcome; one per begin */
SCHED_TRACE_E_ALT_SKIPPED, /* alternative with no content (3.6) */
SCHED_TRACE_E_ALT_RESULT, /* chosen alternative, or none (3.4) */
SCHED_TRACE_E_MEMBER_FAILED, /* merge failed after member j/m (3.3) */
SCHED_TRACE_E_ATTEMPT_REASONS, /* failure reasons of an attempt (3.5) */
SCHED_TRACE_E_CHECK_END, /* cluster-wide check result (4.8) */
SCHED_TRACE_E_NO_PLAN, /* no plan generated, and why (4.7) */
SCHED_TRACE_E_PLAN_BEGIN, /* plan tried (4.3) */
SCHED_TRACE_E_PLAN_DISCARDED, /* plan generated, discarded (4.9) */
SCHED_TRACE_E_PLAN_NOT_TRIED, /* plan generated, internal error (4.4)*/
SCHED_TRACE_E_PLAN_END, /* plan result (4.4) */
SCHED_TRACE_E_POS_BEGIN, /* plan position (4.4) */
SCHED_TRACE_E_POS_END, /* filled / unfilled / scan error /
* internal (4.4) */
SCHED_TRACE_E_HOST, /* host verdict (5.1-5.7, 5.10) */
SCHED_TRACE_E_COMMIT, /* committed plan (4.5) */
SCHED_TRACE_E_FINALIZE_FAILED, /* allocation build failed (4.5) */
SCHED_TRACE_E_NTYPES
} sched_trace_entry_type_t;
/* Where a host verdict came from (5.2). Only the selection, shortfall and
* limit kinds carry a phase; the other kinds use NONE. */
typedef enum {
SCHED_TRACE_PHASE_NONE = 0,
SCHED_TRACE_PHASE_PREFILTER, /* host-selection pre-filter */
SCHED_TRACE_PHASE_PERHOST /* per-host resource and limit check */
} sched_trace_phase_t;
/* One requested resource of a single requirement or member (3.2). */
typedef struct {
const char* name; /* arena copy */
float value;
int per; /* rusage_per_type_t */
} sched_trace_req_t;
/* One pend-reason code of an attempt's failure reasons and how often it
* occurred (3.5). */
typedef struct {
int code;
int count;
} sched_trace_reason_count_t;
/*
* One stored entry. All pointers point into the record's arena. `depth` is
* the nesting level the renderer indents by: a requirement, plan or
* position begin is stored at the current level and opens the next one;
* its end closes it and is stored at the begin's level. `path` is the full
* requirement path in effect when the entry was made (3.1).
*/
typedef struct sched_trace_entry_s {
struct sched_trace_entry_s* next;
sched_trace_entry_type_t type;
int depth;
sched_trace_path_t path;
union {
struct { /* RESREQ_BEGIN */
bool container; /* COMPOUND or ALT: no primary */
int nmembers; /* container only */
int min_tasks;
int max_tasks;
int nreq;
sched_trace_req_t* req;
} resreq_begin;
struct { /* RESREQ_END */
int rc; /* > 0 satisfied, 0 not, < 0 error */
int not_tried_after; /* members after this one, not tried */
} resreq_end;
struct { /* ALT_SKIPPED, ALT_RESULT (i == 0: none chosen) */
int i;
int n;
} alt;
struct { /* MEMBER_FAILED */
int j;
int m;
int err;
} member_failed;
struct { /* ATTEMPT_REASONS */
int ncodes;
sched_trace_reason_count_t* codes;
int nhosts;
const char** hosts;
int nres;
const char** res;
} reasons;
struct { /* CHECK_END */
int rc;
bool plans_follow;
} check_end;
struct { /* NO_PLAN */
xce_sched_trace_noplan_t why;
} no_plan;
struct { /* PLAN_BEGIN, PLAN_DISCARDED, PLAN_NOT_TRIED */
int plan_no; /* 1-based, in generation order per requirement */
int nbuckets; /* hosts the plan needs */
int* slots; /* tasks per position, nbuckets of them */
int err; /* PLAN_NOT_TRIED only */
} plan;
struct { /* PLAN_END */
int plan_no;
int rc;
int failed_pos; /* the position that could not be filled */
} plan_end;
struct { /* POS_BEGIN */
int pos;
int slots;
} pos_begin;
struct { /* POS_END */
int pos;
xce_sched_trace_pos_end_t how;
const char* host; /* filled: the accepted host */
int err; /* scan error */
} pos_end;
struct { /* HOST */
const char* host;
unsigned verdicts; /* xce_sched_trace_verdict_t bits */
sched_trace_phase_t phase;
int err;
} host;
struct { /* COMMIT, FINALIZE_FAILED */
int plan_no; /* the last plan begun */
int err; /* FINALIZE_FAILED only */
} commit;
} u;
} sched_trace_entry_t;
/* One arena chunk; the record frees the whole chain at once. */
typedef struct sched_trace_chunk_s {
struct sched_trace_chunk_s* next;
@ -162,6 +303,27 @@ struct xce_sched_trace_rec_s {
sched_trace_counters_t counters;
/* stored entries, in the order made; at most line_bound of them */
sched_trace_entry_t* entries;
sched_trace_entry_t* entries_tail;
int nentries;
/* builder state (allocator thread only, while the record is built) */
int depth; /* nesting level of the next entry */
int path_overflow; /* pushes beyond the two path levels, not kept */
int resreq_open; /* requirement begins without their end yet */
int plan_no; /* plans generated so far in this requirement */
int cur_plan_no; /* the last plan begun */
int cur_pos; /* the position begun last */
int scan_err; /* pos_scan_error() for the current position */
bool scan_err_set;
unsigned host_row_verdicts; /* SHORTFALL/LIMIT from the current host's
* unmet rows (set by the row capture) */
/* scope pointers, kept only until the matching scope ends and never
* dereferenced after it (TraceCapture's limit lookup) */
const xce_sched_obj_t* scope_obj;
const xce_sched_worker_t* scope_w;
/* per-record arena */
sched_trace_chunk_t* arena;
size_t arena_bytes; /* sum of chunk sizes, headers included */
@ -203,4 +365,93 @@ extern void* sched_trace_arena_memdup(xce_sched_trace_rec_t* rec,
extern char* sched_trace_arena_strdup(xce_sched_trace_rec_t* rec,
const char* s);
/*
* Entry sequence (design.md "TraceModel"). `rec` is never NULL (the
* SCHED_TRACE gate guarantees it). Calls nest in the order the allocator
* makes them: every path_push has one path_pop and every *_begin one
* *_end. Entries past rec->line_bound are not stored and each adds one to
* counters.omitted_lines (9.1, 9.2); the builder state (path, nesting,
* plan numbers, scope pointers) is kept up to date either way.
*/
/* Requirement path: SINGLE 1/1, COMPOUND 1/1 or ALT i/n as the container
* level, MEMBER j/m below it. Not an entry. */
extern void sched_trace_path_push(xce_sched_trace_rec_t* rec,
xce_sched_trace_path_kind_t kind, int i,
int n);
extern void sched_trace_path_pop(xce_sched_trace_rec_t* rec);
/* Begin and end of the requirement the current path names. `primary` is
* NULL for a container, whose `nmembers` is its member count. */
extern void sched_trace_resreq_begin(xce_sched_trace_rec_t* rec,
const xce_primary_resreq_t* primary,
int nmembers);
extern void sched_trace_resreq_end(xce_sched_trace_rec_t* rec, int rc,
int not_tried_after);
/* Merge failure after member j of m: members j+1..m not tried (3.3). */
extern void sched_trace_member_failed(xce_sched_trace_rec_t* rec, int j,
int m, int err);
/* Alternative i/n chosen, later ones not tried (3.4); 0: all failed. */
extern void sched_trace_alt_result(xce_sched_trace_rec_t* rec, int chosen_i,
int n);
/* Alternative i/n skipped: it has no requirement content (3.6). */
extern void sched_trace_alt_skipped(xce_sched_trace_rec_t* rec, int i,
int n);
/* Failure reasons of a compound attempt: per-code counts and the hosts and
* resources they name, all copied (3.5). TraceCapture builds the arrays
* from the attempt's pend-reason set. */
extern void
sched_trace_attempt_reasons(xce_sched_trace_rec_t* rec,
const sched_trace_reason_count_t* codes,
int ncodes, const char* const* hosts, int nhosts,
const char* const* res, int nres);
/* Cluster-wide check scope. check_begin stores no entry; check_end stores
* the result, which on failure says no placement plans were tried (4.8). */
extern void sched_trace_check_begin(xce_sched_trace_rec_t* rec,
const xce_sched_obj_t* obj);
extern void sched_trace_check_end(xce_sched_trace_rec_t* rec, int rc,
bool plans_follow);
extern void sched_trace_no_plan(xce_sched_trace_rec_t* rec,
xce_sched_trace_noplan_t why);
/* Plans, numbered in generation order within the current requirement. */
extern void sched_trace_plan_begin(xce_sched_trace_rec_t* rec,
const xce_alloc_plan_t* plan);
extern void sched_trace_plan_discarded(xce_sched_trace_rec_t* rec,
const xce_alloc_plan_t* plan);
extern void sched_trace_plan_not_tried(xce_sched_trace_rec_t* rec,
const xce_alloc_plan_t* plan, int err);
extern void sched_trace_plan_end(xce_sched_trace_rec_t* rec, int rc,
int failed_pos);
/* Plan positions. A pos_scan_error() stores no entry: it turns the
* position's following UNFILLED end into SCAN_ERROR with that error. */
extern void sched_trace_pos_begin(xce_sched_trace_rec_t* rec, int pos,
int slots);
extern void sched_trace_pos_end(xce_sched_trace_rec_t* rec,
xce_sched_trace_pos_end_t how,
const char* host);
extern void sched_trace_pos_scan_error(xce_sched_trace_rec_t* rec, int err);
/*
* Per-host scope: host_begin stores no entry; host_verdict stores the
* host's verdict and ends the scope. An explicit SELECT verdict comes from
* the host-selection pre-filter. `verdicts == 0` means "from the rows": the
* SHORTFALL/LIMIT bits the current host's unmet rows set, or SELECT when
* there were none (the per-host check's own selection branch); either way
* the phase is the per-host check (5.2).
*/
extern void sched_trace_host_begin(xce_sched_trace_rec_t* rec,
const xce_sched_obj_t* obj,
const xce_sched_worker_t* w);
extern void sched_trace_host_verdict(xce_sched_trace_rec_t* rec,
unsigned verdicts, int err);
/* The plan begun last was committed, or building its allocation failed. */
extern void sched_trace_commit(xce_sched_trace_rec_t* rec);
extern void sched_trace_finalize_failed(xce_sched_trace_rec_t* rec, int err);
#endif

View file

@ -11,12 +11,20 @@
* sim thread for a reconstructed record) and is immutable once attached to
* a decision, so nothing here locks.
*
* The entry sequence: the allocation chain's hooks append typed entries,
* each of which renders as exactly one line. At most `line_bound` entries
* are stored; every entry past that only adds one to the omitted-line
* count, and allocates nothing. The builder state (requirement path,
* nesting level, plan numbers, scope pointers) is kept whether or not an
* entry is stored, so a truncated record still nests correctly.
*
* No I/O and no logging: test_core_isolation.c scans this file for stdio
* and file I/O.
*/
#include <stdlib.h>
#include <string.h>
#include "sched/alloc_iter.h"
#include "sched/trace.h"
#include "utils.h"
@ -229,3 +237,460 @@ size_t sched_trace_rec_bytes(const xce_sched_trace_rec_t* rec)
return sizeof(*rec) + rec->arena_bytes;
}
/* ---- entry sequence ---------------------------------------------------- */
/*
* Appends an entry of `type` at the current nesting level with the current
* requirement path, or returns NULL when it is not stored: past the line
* budget (counted as one omitted line), or after an arena failure (the
* record is already marked incomplete and appending has stopped).
*/
static sched_trace_entry_t* entry_add(xce_sched_trace_rec_t* rec,
sched_trace_entry_type_t type)
{
sched_trace_entry_t* e;
if (rec->incomplete) {
return NULL;
}
if (rec->nentries >= rec->line_bound) {
rec->counters.omitted_lines++;
return NULL;
}
e = (sched_trace_entry_t*)sched_trace_arena_alloc(rec, sizeof(*e));
if (e == NULL) {
return NULL;
}
e->next = NULL;
e->type = type;
e->depth = rec->depth;
e->path = rec->path;
if (rec->entries_tail == NULL) {
rec->entries = e;
} else {
rec->entries_tail->next = e;
}
rec->entries_tail = e;
rec->nentries++;
return e;
}
/* A scope begin is stored at the current level and opens the next one. */
static sched_trace_entry_t* scope_begin(xce_sched_trace_rec_t* rec,
sched_trace_entry_type_t type)
{
sched_trace_entry_t* e = entry_add(rec, type);
rec->depth++;
return e;
}
/* A scope end closes the level and is stored at its begin's level. */
static sched_trace_entry_t* scope_end(xce_sched_trace_rec_t* rec,
sched_trace_entry_type_t type)
{
if (rec->depth > 0) {
rec->depth--;
}
return entry_add(rec, type);
}
/* Arena copy of `n` ints; NULL for none. */
static int* copy_ints(xce_sched_trace_rec_t* rec, const int* src, int n)
{
if ((src == NULL) || (n <= 0)) {
return NULL;
}
return (int*)sched_trace_arena_memdup(rec, src, sizeof(int) * (size_t)n);
}
/* Arena copy of `n` strings (each copied too); NULL for none. */
static const char** copy_strs(xce_sched_trace_rec_t* rec,
const char* const* src, int n)
{
const char** out;
int i;
if ((src == NULL) || (n <= 0)) {
return NULL;
}
out = (const char**)sched_trace_arena_alloc(rec, sizeof(char*) * (size_t)n);
if (out == NULL) {
return NULL;
}
for (i = 0; i < n; i++) {
out[i] = sched_trace_arena_strdup(rec, src[i]);
}
return out;
}
void sched_trace_path_push(xce_sched_trace_rec_t* rec,
xce_sched_trace_path_kind_t kind, int i, int n)
{
sched_trace_path_t* p = &rec->path;
if (p->depth >= 2) {
/* deeper than container > member: not kept, but balanced */
rec->path_overflow++;
return;
}
p->lvl[p->depth].kind = kind;
p->lvl[p->depth].i = i;
p->lvl[p->depth].n = n;
p->depth++;
}
void sched_trace_path_pop(xce_sched_trace_rec_t* rec)
{
if (rec->path_overflow > 0) {
rec->path_overflow--;
return;
}
if (rec->path.depth > 0) {
rec->path.depth--;
}
}
void sched_trace_resreq_begin(xce_sched_trace_rec_t* rec,
const xce_primary_resreq_t* primary,
int nmembers)
{
sched_trace_entry_t* e;
int n;
int k;
rec->resreq_open++;
rec->plan_no = 0; /* plans are numbered per requirement */
rec->cur_plan_no = 0;
e = scope_begin(rec, SCHED_TRACE_E_RESREQ_BEGIN);
if (e == NULL) {
return;
}
if (primary == NULL) {
e->u.resreq_begin.container = true;
e->u.resreq_begin.nmembers = nmembers;
return;
}
e->u.resreq_begin.container = false;
e->u.resreq_begin.min_tasks = primary->minTasks;
e->u.resreq_begin.max_tasks = primary->maxTasks;
n = primary->rusages.count;
if ((n <= 0) || (primary->rusages.data == NULL)) {
return;
}
e->u.resreq_begin.req = (sched_trace_req_t*)sched_trace_arena_alloc(
rec, sizeof(sched_trace_req_t) * (size_t)n);
if (e->u.resreq_begin.req == NULL) {
return;
}
for (k = 0; k < n; k++) {
const rusage_value_t* v
= (const rusage_value_t*)primary->rusages.data[k];
sched_trace_req_t* r = &e->u.resreq_begin.req[k];
if (v == NULL) {
continue;
}
r->name = sched_trace_arena_strdup(rec, v->res);
r->value = v->value;
r->per = (int)v->per_flag;
}
e->u.resreq_begin.nreq = n;
}
void sched_trace_resreq_end(xce_sched_trace_rec_t* rec, int rc,
int not_tried_after)
{
sched_trace_entry_t* e;
if (rec->resreq_open > 0) {
rec->resreq_open--;
}
e = scope_end(rec, SCHED_TRACE_E_RESREQ_END);
if (e == NULL) {
return;
}
e->u.resreq_end.rc = rc;
e->u.resreq_end.not_tried_after = not_tried_after;
}
void sched_trace_member_failed(xce_sched_trace_rec_t* rec, int j, int m,
int err)
{
sched_trace_entry_t* e = entry_add(rec, SCHED_TRACE_E_MEMBER_FAILED);
if (e == NULL) {
return;
}
e->u.member_failed.j = j;
e->u.member_failed.m = m;
e->u.member_failed.err = err;
}
void sched_trace_alt_result(xce_sched_trace_rec_t* rec, int chosen_i, int n)
{
sched_trace_entry_t* e = entry_add(rec, SCHED_TRACE_E_ALT_RESULT);
if (e == NULL) {
return;
}
e->u.alt.i = chosen_i;
e->u.alt.n = n;
}
void sched_trace_alt_skipped(xce_sched_trace_rec_t* rec, int i, int n)
{
sched_trace_entry_t* e = entry_add(rec, SCHED_TRACE_E_ALT_SKIPPED);
if (e == NULL) {
return;
}
e->u.alt.i = i;
e->u.alt.n = n;
}
void sched_trace_attempt_reasons(xce_sched_trace_rec_t* rec,
const sched_trace_reason_count_t* codes,
int ncodes, const char* const* hosts,
int nhosts, const char* const* res, int nres)
{
sched_trace_entry_t* e = entry_add(rec, SCHED_TRACE_E_ATTEMPT_REASONS);
if (e == NULL) {
return;
}
if ((codes != NULL) && (ncodes > 0)) {
e->u.reasons.codes = (sched_trace_reason_count_t*)
sched_trace_arena_memdup(rec, codes,
sizeof(*codes) * (size_t)ncodes);
if (e->u.reasons.codes != NULL) {
e->u.reasons.ncodes = ncodes;
}
}
e->u.reasons.hosts = copy_strs(rec, hosts, nhosts);
if (e->u.reasons.hosts != NULL) {
e->u.reasons.nhosts = nhosts;
}
e->u.reasons.res = copy_strs(rec, res, nres);
if (e->u.reasons.res != NULL) {
e->u.reasons.nres = nres;
}
}
void sched_trace_check_begin(xce_sched_trace_rec_t* rec,
const xce_sched_obj_t* obj)
{
rec->scope_obj = obj;
rec->scope_w = NULL;
}
void sched_trace_check_end(xce_sched_trace_rec_t* rec, int rc,
bool plans_follow)
{
sched_trace_entry_t* e;
rec->scope_obj = NULL;
e = entry_add(rec, SCHED_TRACE_E_CHECK_END);
if (e == NULL) {
return;
}
e->u.check_end.rc = rc;
e->u.check_end.plans_follow = plans_follow;
}
void sched_trace_no_plan(xce_sched_trace_rec_t* rec,
xce_sched_trace_noplan_t why)
{
sched_trace_entry_t* e = entry_add(rec, SCHED_TRACE_E_NO_PLAN);
if (e == NULL) {
return;
}
e->u.no_plan.why = why;
}
/* Fills a PLAN_BEGIN / PLAN_DISCARDED / PLAN_NOT_TRIED payload. */
static void plan_fill(xce_sched_trace_rec_t* rec, sched_trace_entry_t* e,
const xce_alloc_plan_t* plan, int plan_no, int err)
{
e->u.plan.plan_no = plan_no;
e->u.plan.err = err;
if (plan == NULL) {
return;
}
e->u.plan.nbuckets = plan->nbuckets;
e->u.plan.slots = copy_ints(rec, plan->bucket_slots, plan->nbuckets);
}
void sched_trace_plan_begin(xce_sched_trace_rec_t* rec,
const xce_alloc_plan_t* plan)
{
sched_trace_entry_t* e;
rec->cur_plan_no = ++rec->plan_no;
e = scope_begin(rec, SCHED_TRACE_E_PLAN_BEGIN);
if (e != NULL) {
plan_fill(rec, e, plan, rec->cur_plan_no, 0);
}
}
void sched_trace_plan_discarded(xce_sched_trace_rec_t* rec,
const xce_alloc_plan_t* plan)
{
sched_trace_entry_t* e;
rec->plan_no++;
e = entry_add(rec, SCHED_TRACE_E_PLAN_DISCARDED);
if (e != NULL) {
plan_fill(rec, e, plan, rec->plan_no, 0);
}
}
void sched_trace_plan_not_tried(xce_sched_trace_rec_t* rec,
const xce_alloc_plan_t* plan, int err)
{
sched_trace_entry_t* e;
rec->plan_no++;
e = entry_add(rec, SCHED_TRACE_E_PLAN_NOT_TRIED);
if (e != NULL) {
plan_fill(rec, e, plan, rec->plan_no, err);
}
}
void sched_trace_plan_end(xce_sched_trace_rec_t* rec, int rc, int failed_pos)
{
sched_trace_entry_t* e = scope_end(rec, SCHED_TRACE_E_PLAN_END);
if (e == NULL) {
return;
}
e->u.plan_end.plan_no = rec->cur_plan_no;
e->u.plan_end.rc = rc;
e->u.plan_end.failed_pos = failed_pos;
}
void sched_trace_pos_begin(xce_sched_trace_rec_t* rec, int pos, int slots)
{
sched_trace_entry_t* e;
rec->cur_pos = pos;
rec->scan_err = 0;
rec->scan_err_set = false;
e = scope_begin(rec, SCHED_TRACE_E_POS_BEGIN);
if (e == NULL) {
return;
}
e->u.pos_begin.pos = pos;
e->u.pos_begin.slots = slots;
}
void sched_trace_pos_scan_error(xce_sched_trace_rec_t* rec, int err)
{
rec->scan_err = err;
rec->scan_err_set = true;
}
void sched_trace_pos_end(xce_sched_trace_rec_t* rec,
xce_sched_trace_pos_end_t how, const char* host)
{
sched_trace_entry_t* e;
int err = 0;
if (rec->scan_err_set) {
/* the host scan stopped: the runner's UNFILLED is a scan error */
err = rec->scan_err;
if (how == XCE_TRACE_POS_UNFILLED) {
how = XCE_TRACE_POS_SCAN_ERROR;
}
}
rec->scan_err = 0;
rec->scan_err_set = false;
e = scope_end(rec, SCHED_TRACE_E_POS_END);
if (e == NULL) {
return;
}
e->u.pos_end.pos = rec->cur_pos;
e->u.pos_end.how = how;
e->u.pos_end.err = err;
e->u.pos_end.host = sched_trace_arena_strdup(rec, host);
}
void sched_trace_host_begin(xce_sched_trace_rec_t* rec,
const xce_sched_obj_t* obj,
const xce_sched_worker_t* w)
{
rec->scope_obj = obj;
rec->scope_w = w;
rec->host_row_verdicts = 0;
}
void sched_trace_host_verdict(xce_sched_trace_rec_t* rec, unsigned verdicts,
int err)
{
sched_trace_entry_t* e;
sched_trace_phase_t phase = SCHED_TRACE_PHASE_NONE;
const char* name
= (rec->scope_w != NULL) ? rec->scope_w->name : NULL;
if (verdicts == 0) {
/* from the rows the per-host check recorded; none means the
* check's own selection branch rejected the host */
verdicts = rec->host_row_verdicts & (XCE_TRACE_V_SHORTFALL
| XCE_TRACE_V_LIMIT);
if (verdicts == 0) {
verdicts = XCE_TRACE_V_SELECT;
}
phase = SCHED_TRACE_PHASE_PERHOST;
} else if ((verdicts & XCE_TRACE_V_SELECT) != 0) {
phase = SCHED_TRACE_PHASE_PREFILTER;
} else if ((verdicts & (XCE_TRACE_V_SHORTFALL | XCE_TRACE_V_LIMIT)) != 0) {
phase = SCHED_TRACE_PHASE_PERHOST;
}
/* the host scope ends here */
rec->scope_obj = NULL;
rec->scope_w = NULL;
rec->host_row_verdicts = 0;
e = entry_add(rec, SCHED_TRACE_E_HOST);
if (e == NULL) {
return;
}
e->u.host.host = sched_trace_arena_strdup(rec, name);
e->u.host.verdicts = verdicts;
e->u.host.phase = phase;
e->u.host.err = err;
}
void sched_trace_commit(xce_sched_trace_rec_t* rec)
{
sched_trace_entry_t* e = entry_add(rec, SCHED_TRACE_E_COMMIT);
if (e == NULL) {
return;
}
e->u.commit.plan_no = rec->cur_plan_no;
}
void sched_trace_finalize_failed(xce_sched_trace_rec_t* rec, int err)
{
sched_trace_entry_t* e = entry_add(rec, SCHED_TRACE_E_FINALIZE_FAILED);
if (e == NULL) {
return;
}
e->u.commit.plan_no = rec->cur_plan_no;
e->u.commit.err = err;
}

View file

@ -13,6 +13,14 @@
* - 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).
*
* Links only src/sched_trace.c (plus libxce for the jobid helpers).
*/
#include <stdlib.h>
@ -20,6 +28,7 @@
#include <unity/unity.h>
#include "sched/alloc_iter.h"
#include "sched/trace.h"
#include "utils.h"
@ -406,6 +415,827 @@ static void test_finish_rejects_non_no_decision_kinds(void)
sched_trace_rec_free(rec);
}
/* ---- entry sequence (task 1.4) ---------------------------------------- */
static xce_sched_trace_rec_t* new_rec(int line_bound)
{
xce_sched_trace_ctl_t ctl = make_ctl(NULL, 0);
ctl.level = XCE_TRACE_LEVEL_FULL;
ctl.line_bound = line_bound;
return sched_trace_rec_new(&ctl, PLAIN_ID(5), XCE_TRACE_REC_DECISION);
}
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 };
make_primary(&fp, 1, 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_check_end(rec, 1, true); /* 4 */
sched_trace_no_plan(rec, XCE_TRACE_NOPLAN_DECLINED); /* 5 */
sched_trace_plan_discarded(rec, &plan); /* 6 */
sched_trace_plan_not_tried(rec, &plan, -12); /* 7 */
sched_trace_plan_begin(rec, &plan); /* 8 */
sched_trace_pos_begin(rec, 1, 1); /* 9 */
sched_trace_host_begin(rec, NULL, &w);
sched_trace_host_verdict(rec, XCE_TRACE_V_ACCEPTED, 0); /* 10 */
sched_trace_pos_end(rec, XCE_TRACE_POS_FILLED, hname); /* 11 */
sched_trace_plan_end(rec, 1, 0); /* 12 */
sched_trace_commit(rec); /* 13 */
sched_trace_finalize_failed(rec, -5); /* 14 */
sched_trace_resreq_end(rec, 1, 0); /* 15 */
sched_trace_path_pop(rec);
sched_trace_member_failed(rec, 1, 1, -12); /* 16 */
sched_trace_attempt_reasons(rec, &rc, 1, NULL, 0, NULL, 0); /* 17 */
sched_trace_resreq_end(rec, 0, 0); /* 18 */
sched_trace_path_pop(rec);
sched_trace_alt_result(rec, 0, 2); /* 19 */
return 19;
}
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]++;
}
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);
}
int main(void)
{
UNITY_BEGIN();
@ -428,5 +1258,19 @@ int main(void)
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);
return UNITY_END();
}