feat(scheduler-sim-decision-trace): wire the trace commands into the REPL
Task 5.2: trace is routed with the raw line and info trace through info; help gains a Trace section; set output rejects open trace files; quit and EOF shut tracing down; the continue-mode rollup reports a traced round on its own; repl.h gains the interactive-only trace gates. The baseline transcript changes only in the help and info-usage lines. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
8931833132
commit
6bac16812c
9 changed files with 820 additions and 3 deletions
|
|
@ -154,4 +154,58 @@ static inline void xce_repl_check_replay_complete(xce_sched_sim_driver_t* sim)
|
|||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Decision trace gates (scheduler-sim-decision-trace spec, task 5.2;
|
||||
* design.md "SimIntegration"; Requirements 7.1-7.3, 11.3). The entry
|
||||
* points live in repl_trace_emit.c and are declared here, like the
|
||||
* xce_repl_on_* hooks above, so this header stays self-contained. Each
|
||||
* gate forwards only when the REPL is interactive, so a non-interactive
|
||||
* simulation never publishes trace settings or prints a trace (11.3):
|
||||
* - xce_repl_trace_session_begin: after a successful core begin-session,
|
||||
* before reconciliation; makes the next settings current and
|
||||
* publishes them to the scheduling core.
|
||||
* - xce_repl_trace_session_end: after the decision stream ends;
|
||||
* withdraws the published settings.
|
||||
* - xce_repl_check_trace: a decision carrying a trace record, after the
|
||||
* echo gate and before the breakpoint check; takes d->trace.
|
||||
* - xce_repl_check_trace_only: an XCE_DECISION_TRACE decision (a traced
|
||||
* selection with no decision), handled before anything else.
|
||||
*/
|
||||
extern void repl_trace_session_begin(xce_sched_sim_driver_t* sim);
|
||||
extern void repl_trace_session_end(xce_sched_sim_driver_t* sim);
|
||||
extern void repl_trace_on_decision(xce_sched_sim_driver_t* sim,
|
||||
xce_decision_t* d);
|
||||
extern void repl_trace_on_trace_only(xce_sched_sim_driver_t* sim,
|
||||
xce_decision_t* d);
|
||||
|
||||
static inline void xce_repl_trace_session_begin(xce_sched_sim_driver_t* sim)
|
||||
{
|
||||
if (sim->interactive) {
|
||||
repl_trace_session_begin(sim);
|
||||
}
|
||||
}
|
||||
|
||||
static inline void xce_repl_trace_session_end(xce_sched_sim_driver_t* sim)
|
||||
{
|
||||
if (sim->interactive) {
|
||||
repl_trace_session_end(sim);
|
||||
}
|
||||
}
|
||||
|
||||
static inline void xce_repl_check_trace(xce_sched_sim_driver_t* sim,
|
||||
xce_decision_t* d)
|
||||
{
|
||||
if (sim->interactive && (d->trace != NULL)) {
|
||||
repl_trace_on_decision(sim, d);
|
||||
}
|
||||
}
|
||||
|
||||
static inline void xce_repl_check_trace_only(xce_sched_sim_driver_t* sim,
|
||||
xce_decision_t* d)
|
||||
{
|
||||
if (sim->interactive && (d->trace != NULL)) {
|
||||
repl_trace_on_trace_only(sim, d);
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -304,6 +304,8 @@ static void repl_print_help(void)
|
|||
fprintf(stdout, FIELD_FMT, "info config", "Show sim parameters.");
|
||||
fprintf(stdout, FIELD_FMT, "info limit <name>|all",
|
||||
"Show limit setting(s) and usage.");
|
||||
fprintf(stdout, FIELD_FMT, "info trace <jobid>",
|
||||
"Re-print a job's latest decision trace.");
|
||||
|
||||
fprintf(stdout, "\nSnapshot & Parameters:\n");
|
||||
fprintf(stdout, FIELD_FMT, "snapshot [FILE]",
|
||||
|
|
@ -315,6 +317,25 @@ static void repl_print_help(void)
|
|||
fprintf(stdout, FIELD_FMT, "set units raw|human|both",
|
||||
"Show resource values raw, human-scaled, or both (default: "
|
||||
"both).");
|
||||
|
||||
/* scheduler-sim-decision-trace (1.11): settings apply from the next
|
||||
* session; the option lines below belong to both `trace on` forms. */
|
||||
fprintf(stdout, "\nTrace:\n");
|
||||
fprintf(stdout, FIELD_FMT, "trace on all [OPTIONS]",
|
||||
"Trace every job's decisions.");
|
||||
fprintf(stdout, FIELD_FMT, "trace on job <jobid> ... [OPTIONS]",
|
||||
"Trace the listed jobs (id, id[k] or array id).");
|
||||
fprintf(stdout, FIELD_FMT, " level summary|full",
|
||||
"Host detail: counts per position or every host (default: "
|
||||
"summary).");
|
||||
fprintf(stdout, FIELD_FMT, " lines N",
|
||||
"Line bound per trace, 50-100000 (default: 1000).");
|
||||
fprintf(stdout, FIELD_FMT, " file PATH",
|
||||
"Also append each trace to PATH (last option).");
|
||||
fprintf(stdout, FIELD_FMT, "trace off",
|
||||
"Stop tracing from the next session.");
|
||||
fprintf(stdout, FIELD_FMT, "trace status",
|
||||
"Show trace settings and stored-trace memory.");
|
||||
}
|
||||
|
||||
/*
|
||||
|
|
@ -329,6 +350,7 @@ static void repl_print_help(void)
|
|||
*/
|
||||
static void repl_cmd_quit(xce_sched_sim_driver_t* sim)
|
||||
{
|
||||
repl_trace_shutdown(sim); /* flush and close trace files (8.6) */
|
||||
if (sim->record_file != NULL) {
|
||||
fflush(sim->record_file);
|
||||
fclose(sim->record_file);
|
||||
|
|
@ -458,6 +480,9 @@ static bool repl_dispatch(xce_sched_sim_driver_t* sim, const char* line)
|
|||
if (strcmp(cmd, "set") == 0) {
|
||||
return repl_cmd_set(sim, line);
|
||||
}
|
||||
if (strcmp(cmd, "trace") == 0) {
|
||||
return repl_trace_cmd(sim, line); /* raw line: no token limits */
|
||||
}
|
||||
|
||||
fprintf(stdout, "[sim] unknown command: '%s' (type 'help' for a list)\n", cmd);
|
||||
|
||||
|
|
|
|||
|
|
@ -1725,7 +1725,7 @@ bool repl_cmd_info(xce_sched_sim_driver_t* sim, const char* line)
|
|||
|
||||
if (ntok < 2) {
|
||||
fprintf(stdout, "[sim] info: usage: info <break|session|config|job|jobs|queue|"
|
||||
"worker|resource|summary|limit> [arg]\n");
|
||||
"worker|resource|summary|limit|trace> [arg]\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
@ -1769,6 +1769,8 @@ bool repl_cmd_info(xce_sched_sim_driver_t* sim, const char* line)
|
|||
} else {
|
||||
repl_info_limit(sim, tokens[2]);
|
||||
}
|
||||
} else if (strcmp(tokens[1], "trace") == 0) {
|
||||
repl_cmd_info_trace(sim, tokens, ntok);
|
||||
} else {
|
||||
fprintf(stdout, "[sim] info: unrecognized subcommand '%s'\n", tokens[1]);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -245,6 +245,11 @@ static bool repl_set_output_file(xce_sched_sim_driver_t* sim, const char* path)
|
|||
uint64_t ts;
|
||||
int rc;
|
||||
|
||||
if (repl_trace_file_conflicts(sim, path)) {
|
||||
fprintf(stdout, "[sim] set output: %s is an open trace file\n", path);
|
||||
return false; /* 8.4: the output file stays as it is */
|
||||
}
|
||||
|
||||
if (sim->record_file != NULL) {
|
||||
fflush(sim->record_file);
|
||||
fclose(sim->record_file);
|
||||
|
|
|
|||
|
|
@ -307,6 +307,22 @@ void repl_round_rollup_process(xce_sched_sim_driver_t* sim)
|
|||
return;
|
||||
}
|
||||
|
||||
if (rs->trace.round_traced) {
|
||||
/* scheduler-sim-decision-trace (7.3, 7.4): a traced round is
|
||||
* reported on its own and never starts or extends a range. The
|
||||
* flush is a no-op when emission already flushed, and covers a
|
||||
* round traced only in step mode. `idle` follows the flush rule,
|
||||
* so a pend-only round is not idle. */
|
||||
repl_round_collapse_flush(sim);
|
||||
repl_round_emit(round_no, round_no, round_ts, round_ts,
|
||||
dispatch_count, preempt_count, pend_jobids,
|
||||
n_pend_jobids,
|
||||
(dispatch_count == 0) && (preempt_count == 0)
|
||||
&& (n_pend_jobids == 0));
|
||||
free(pend_jobids);
|
||||
return;
|
||||
}
|
||||
|
||||
if (rs->collapse_active
|
||||
&& (rs->collapse_dispatch_count == dispatch_count)
|
||||
&& (rs->collapse_preempt_count == preempt_count)
|
||||
|
|
|
|||
|
|
@ -25,13 +25,23 @@ State Inspection:
|
|||
info summary Quick overview.
|
||||
info config Show sim parameters.
|
||||
info limit <name>|all Show limit setting(s) and usage.
|
||||
info trace <jobid> Re-print a job's latest decision trace.
|
||||
|
||||
Snapshot & Parameters:
|
||||
snapshot [FILE] Write a standalone snapshot.
|
||||
set output <FILE>|off Start/stop writing decisions.
|
||||
set verify on|off Toggle decision verification.
|
||||
set units raw|human|both Show resource values raw, human-scaled, or both (default: both).
|
||||
[sim] info: usage: info <break|session|config|job|jobs|queue|worker|resource|summary|limit> [arg]
|
||||
|
||||
Trace:
|
||||
trace on all [OPTIONS] Trace every job's decisions.
|
||||
trace on job <jobid> ... [OPTIONS] Trace the listed jobs (id, id[k] or array id).
|
||||
level summary|full Host detail: counts per position or every host (default: summary).
|
||||
lines N Line bound per trace, 50-100000 (default: 1000).
|
||||
file PATH Also append each trace to PATH (last option).
|
||||
trace off Stop tracing from the next session.
|
||||
trace status Show trace settings and stored-trace memory.
|
||||
[sim] info: usage: info <break|session|config|job|jobs|queue|worker|resource|summary|limit|trace> [arg]
|
||||
[sim] config: output=(active, path unknown) verify=on events_processed=0 current_ts=1000 (1970-01-01T00:00:00Z) sessions=0
|
||||
[sim] t=1001 (1970-01-01T00:00:00Z) session=1
|
||||
[sim] event: WORKERS_PUSH nworkers=2 names=[w01,w02]
|
||||
|
|
|
|||
|
|
@ -2841,3 +2841,30 @@ sched_repl_trace_emit_test = executable(
|
|||
|
||||
test('scheduler repl trace emit test',
|
||||
sched_repl_trace_emit_test, suite: 'scheduler')
|
||||
|
||||
# scheduler-sim-decision-trace task 5.2: the trace commands wired into the
|
||||
# REPL (design.md "ReplIntegration", "SimIntegration" (repl.h gates),
|
||||
# "ReplTraceControl"; Requirements 1.11, 7.3, 7.4, 8.4, 8.6, 10.4, 10.5).
|
||||
# The help Trace section and alignment, `trace`/`info trace` routing,
|
||||
# `set output` rejecting an open trace file, quit/EOF shutdown, the
|
||||
# traced-round rollup and the interactive-only gates. Includes repl.c (for
|
||||
# its static help, dispatch and quit) and links every other scheduler
|
||||
# source except main.c. Leak detection is off like the other rollup tests:
|
||||
# repl_count_jobs() (repl.c) never destroys its iterator.
|
||||
sched_repl_trace_wiring_test_srcs = ['test_repl_trace_wiring.c']
|
||||
foreach f : scheduler_srcs
|
||||
if f != 'src/main.c' and f != 'src/repl.c'
|
||||
sched_repl_trace_wiring_test_srcs += ['../' + f]
|
||||
endif
|
||||
endforeach
|
||||
|
||||
sched_repl_trace_wiring_test = executable(
|
||||
'sched_repl_trace_wiring_test',
|
||||
sched_repl_trace_wiring_test_srcs,
|
||||
include_directories:[incdir],
|
||||
dependencies: [scheduler_deps, unity_dep],
|
||||
install: false)
|
||||
|
||||
test('scheduler repl trace wiring test',
|
||||
sched_repl_trace_wiring_test, suite: 'scheduler',
|
||||
env: ['ASAN_OPTIONS=detect_leaks=0'])
|
||||
|
|
|
|||
677
src/scheduler/testsuite/test_repl_trace_wiring.c
Normal file
677
src/scheduler/testsuite/test_repl_trace_wiring.c
Normal file
|
|
@ -0,0 +1,677 @@
|
|||
/*
|
||||
* test_repl_trace_wiring.c - the trace commands wired into the REPL
|
||||
* (scheduler-sim-decision-trace spec, task 5.2; design.md "ReplIntegration",
|
||||
* "SimIntegration" (repl.h gates), "ReplTraceControl", "System Flows";
|
||||
* Requirements 1.11, 7.3, 7.4, 8.4, 8.6, 10.4, 10.5).
|
||||
*
|
||||
* Links every scheduler source except main.c and repl.c, and includes
|
||||
* repl.c here so its static help printer, dispatcher and quit path are
|
||||
* reachable. The sim driver and REPL state are zeroed fixtures with a small
|
||||
* fixture job table; nothing is started.
|
||||
*
|
||||
* Covers:
|
||||
* - help: a trailing Trace section listing the trace commands, `info
|
||||
* trace` among the State Inspection lines, one description column for
|
||||
* every line including the new ones (1.11);
|
||||
* - routing: `trace` gets the raw line, so a command longer than the
|
||||
* tokenizer's 16 tokens / 64 characters is not truncated; `info trace`
|
||||
* reaches the store and the `info` usage line lists it (1.11, 10.5);
|
||||
* - `set output` rejects a trace file still open for writing, including
|
||||
* one kept open by the session in progress and a hard link to it, and
|
||||
* leaves the output file unchanged (8.4);
|
||||
* - quit and end of input shut tracing down (8.6);
|
||||
* - the continue-mode rollup reports a traced round on its own, with the
|
||||
* range-flush idle rule, and never starts a collapsed range from it
|
||||
* (7.3, 7.4);
|
||||
* - the repl.h gates forward only when the REPL is interactive.
|
||||
*/
|
||||
#define _GNU_SOURCE
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/wait.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <unity/unity.h>
|
||||
|
||||
#include "kvmgr.h"
|
||||
#include "sched/trace.h"
|
||||
#include "utils.h"
|
||||
|
||||
#include "../src/repl.c"
|
||||
|
||||
#define PEND_JOB MAKE_JOBID(1, 2, 1000, 0x0, (uint64_t)42)
|
||||
|
||||
static xce_sched_sim_driver_t g_sim;
|
||||
static xce_sched_repl_state_t g_rs;
|
||||
static xce_sched_job_t g_pend_job;
|
||||
static char g_dir[64];
|
||||
static char g_cwd[4096];
|
||||
static int g_report_fd = -1;
|
||||
|
||||
/* stdout capture */
|
||||
static int g_cap_fd = -1;
|
||||
static int g_cap_saved = -1;
|
||||
static char g_cap_path[64];
|
||||
|
||||
void setUp(void)
|
||||
{
|
||||
kvmgr_params_t p = { .key_type = KVMGR_KEY_INT,
|
||||
.nslots = 59,
|
||||
.delta = 16,
|
||||
.datakiller = NULL };
|
||||
|
||||
memset(&g_sim, 0, sizeof(g_sim));
|
||||
memset(&g_rs, 0, sizeof(g_rs));
|
||||
g_rs.units_mode = XCE_REPL_UNITS_BOTH;
|
||||
g_rs.mode = XCE_REPL_MODE_STEP;
|
||||
g_sim.interactive = true;
|
||||
g_sim.repl_state = &g_rs;
|
||||
g_sim.core.jobs = makeInstance(&xkvmgr_type, &p);
|
||||
TEST_ASSERT_NOT_NULL(g_sim.core.jobs);
|
||||
|
||||
TEST_ASSERT_NOT_NULL(getcwd(g_cwd, sizeof(g_cwd)));
|
||||
snprintf(g_dir, sizeof(g_dir), "/tmp/xce_trace_wiring_XXXXXX");
|
||||
TEST_ASSERT_NOT_NULL(mkdtemp(g_dir));
|
||||
TEST_ASSERT_EQUAL_INT(0, chdir(g_dir));
|
||||
}
|
||||
|
||||
static void fixture_fini(void)
|
||||
{
|
||||
repl_trace_shutdown(&g_sim);
|
||||
free(g_rs.collapse_pend_jobids);
|
||||
g_rs.collapse_pend_jobids = NULL;
|
||||
if (g_sim.record_file != NULL) {
|
||||
fclose(g_sim.record_file);
|
||||
g_sim.record_file = NULL;
|
||||
}
|
||||
if (g_sim.core.jobs != NULL) {
|
||||
destroy(g_sim.core.jobs);
|
||||
g_sim.core.jobs = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
void tearDown(void)
|
||||
{
|
||||
fixture_fini();
|
||||
g_sim.repl_state = NULL;
|
||||
unlink("a.trace");
|
||||
unlink("b.trace");
|
||||
unlink("rec.replay");
|
||||
TEST_ASSERT_EQUAL_INT(0, chdir(g_cwd));
|
||||
rmdir(g_dir);
|
||||
}
|
||||
|
||||
/* ---- helpers ------------------------------------------------------------- */
|
||||
|
||||
static void cap_begin(void)
|
||||
{
|
||||
snprintf(g_cap_path, sizeof(g_cap_path), "/tmp/xce_trace_wire_out_XXXXXX");
|
||||
g_cap_fd = mkstemp(g_cap_path);
|
||||
TEST_ASSERT_TRUE(g_cap_fd >= 0);
|
||||
fflush(stdout);
|
||||
g_cap_saved = dup(STDOUT_FILENO);
|
||||
TEST_ASSERT_TRUE(g_cap_saved >= 0);
|
||||
TEST_ASSERT_TRUE(dup2(g_cap_fd, STDOUT_FILENO) >= 0);
|
||||
}
|
||||
|
||||
/* Ends the capture and returns what was printed (caller frees). */
|
||||
static char* cap_end(void)
|
||||
{
|
||||
off_t len;
|
||||
char* buf;
|
||||
|
||||
fflush(stdout);
|
||||
dup2(g_cap_saved, STDOUT_FILENO);
|
||||
close(g_cap_saved);
|
||||
|
||||
len = lseek(g_cap_fd, 0, SEEK_CUR);
|
||||
TEST_ASSERT_TRUE(len >= 0);
|
||||
buf = (char*)calloc(1, (size_t)len + 1);
|
||||
TEST_ASSERT_NOT_NULL(buf);
|
||||
TEST_ASSERT_TRUE(lseek(g_cap_fd, 0, SEEK_SET) == 0);
|
||||
if (len > 0) {
|
||||
TEST_ASSERT_TRUE(read(g_cap_fd, buf, (size_t)len) == len);
|
||||
}
|
||||
close(g_cap_fd);
|
||||
unlink(g_cap_path);
|
||||
return buf;
|
||||
}
|
||||
|
||||
/* Dispatches `line` as the REPL would and returns its output. */
|
||||
static char* dispatch(const char* line)
|
||||
{
|
||||
cap_begin();
|
||||
TEST_ASSERT_FALSE(repl_dispatch(&g_sim, line));
|
||||
return cap_end();
|
||||
}
|
||||
|
||||
static void dispatch_quiet(const char* line) { free(dispatch(line)); }
|
||||
|
||||
static char* help_text(void)
|
||||
{
|
||||
cap_begin();
|
||||
repl_print_help();
|
||||
return cap_end();
|
||||
}
|
||||
|
||||
static void write_file(const char* path, const char* text)
|
||||
{
|
||||
FILE* f = fopen(path, "w");
|
||||
|
||||
TEST_ASSERT_NOT_NULL(f);
|
||||
fputs(text, f);
|
||||
fclose(f);
|
||||
}
|
||||
|
||||
static char* read_file(const char* path)
|
||||
{
|
||||
FILE* f = fopen(path, "r");
|
||||
char* buf;
|
||||
long len;
|
||||
|
||||
TEST_ASSERT_NOT_NULL(f);
|
||||
TEST_ASSERT_EQUAL_INT(0, fseek(f, 0, SEEK_END));
|
||||
len = ftell(f);
|
||||
TEST_ASSERT_TRUE(len >= 0);
|
||||
rewind(f);
|
||||
buf = (char*)calloc(1, (size_t)len + 1);
|
||||
TEST_ASSERT_NOT_NULL(buf);
|
||||
if (len > 0) {
|
||||
TEST_ASSERT_EQUAL_size_t((size_t)len, fread(buf, 1, (size_t)len, f));
|
||||
}
|
||||
fclose(f);
|
||||
return buf;
|
||||
}
|
||||
|
||||
/* The column at which the description of the help line whose command field
|
||||
* is exactly `field` begins (the field starts the line after indentation). */
|
||||
static int desc_column(const char* text, const char* field)
|
||||
{
|
||||
size_t flen = strlen(field);
|
||||
const char* p = text;
|
||||
|
||||
while ((p = strstr(p, field)) != NULL) {
|
||||
const char* ls = p;
|
||||
const char* d = p + flen;
|
||||
|
||||
while ((ls > text) && (*(ls - 1) == ' ')) {
|
||||
ls--;
|
||||
}
|
||||
if (((ls == text) || (*(ls - 1) == '\n')) && (*d == ' ')) {
|
||||
while ((ls > text) && (*(ls - 1) != '\n')) {
|
||||
ls--;
|
||||
}
|
||||
while (*d == ' ') {
|
||||
d++;
|
||||
}
|
||||
return (int)(d - ls);
|
||||
}
|
||||
p += flen;
|
||||
}
|
||||
TEST_FAIL_MESSAGE(field);
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* A traced decision record for `jobid` (DECISION kind, no entries). */
|
||||
static xce_sched_trace_rec_t* new_rec(uint64_t jobid)
|
||||
{
|
||||
xce_sched_trace_ctl_t ctl;
|
||||
xce_sched_trace_rec_t* rec;
|
||||
|
||||
memset(&ctl, 0, sizeof(ctl));
|
||||
ctl.all = true;
|
||||
ctl.level = XCE_TRACE_LEVEL_SUMMARY;
|
||||
ctl.line_bound = 1000;
|
||||
ctl.session_no = 3;
|
||||
ctl.position = 1;
|
||||
rec = sched_trace_rec_new(&ctl, jobid, XCE_TRACE_REC_DECISION);
|
||||
TEST_ASSERT_NOT_NULL(rec);
|
||||
return rec;
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
xce_decision_t d;
|
||||
xce_job_alloc_t alloc;
|
||||
} fixture_decision_t;
|
||||
|
||||
static void dispatch_of(fixture_decision_t* f, uint64_t jobid)
|
||||
{
|
||||
memset(f, 0, sizeof(*f));
|
||||
f->alloc.jobid = jobid;
|
||||
f->d.op = XCE_DECISION_DISPATCH;
|
||||
f->d.u.alloc = &(f->alloc);
|
||||
f->d.trace = new_rec(jobid);
|
||||
}
|
||||
|
||||
static void add_pend_job(void)
|
||||
{
|
||||
memset(&g_pend_job, 0, sizeof(g_pend_job));
|
||||
g_pend_job.jobid = PEND_JOB;
|
||||
g_pend_job.state = JOB_STAT_PEND;
|
||||
TEST_ASSERT_TRUE(
|
||||
key_add(g_sim.core.jobs, (void*)g_pend_job.jobid, &g_pend_job) >= 0);
|
||||
}
|
||||
|
||||
/* An active, non-idle collapsed range over rounds 3-4 with one dispatch
|
||||
* per round and no pend job. */
|
||||
static void open_dispatch_range(void)
|
||||
{
|
||||
g_rs.mode = XCE_REPL_MODE_CONTINUE;
|
||||
g_rs.collapse_active = true;
|
||||
g_rs.collapse_start_round = 3;
|
||||
g_rs.collapse_end_round = 4;
|
||||
g_rs.collapse_dispatch_count = 1;
|
||||
}
|
||||
|
||||
/* Runs the round-boundary rollup for round 5 with the given counts. */
|
||||
static char* rollup_round5(int ndispatch, bool traced)
|
||||
{
|
||||
g_rs.session_count = 5;
|
||||
g_rs.last_n_dispatch = ndispatch;
|
||||
g_rs.last_n_preempt = 0;
|
||||
g_rs.trace.round_traced = traced;
|
||||
cap_begin();
|
||||
repl_round_rollup_process(&g_sim);
|
||||
return cap_end();
|
||||
}
|
||||
|
||||
/* ---- help (1.11) --------------------------------------------------------- */
|
||||
|
||||
static void test_help_trace_section_is_last_and_lists_trace_commands(void)
|
||||
{
|
||||
char* h = help_text();
|
||||
char* snap = strstr(h, "\nSnapshot & Parameters:\n");
|
||||
char* trace = strstr(h, "\nTrace:\n");
|
||||
const char* cmds[] = { "trace on all", "trace on job <jobid>",
|
||||
"trace off", "trace status" };
|
||||
size_t i;
|
||||
|
||||
TEST_ASSERT_NOT_NULL(snap);
|
||||
TEST_ASSERT_NOT_NULL_MESSAGE(trace, "help must have a Trace section");
|
||||
TEST_ASSERT_TRUE_MESSAGE(snap < trace, "Trace must follow Snapshot");
|
||||
TEST_ASSERT_NULL_MESSAGE(strstr(trace + 1, "\n\n"),
|
||||
"Trace must be the last section");
|
||||
for (i = 0; i < sizeof(cmds) / sizeof(cmds[0]); i++) {
|
||||
char* pos = strstr(h, cmds[i]);
|
||||
|
||||
TEST_ASSERT_NOT_NULL_MESSAGE(pos, cmds[i]);
|
||||
TEST_ASSERT_TRUE_MESSAGE(pos > trace, cmds[i]);
|
||||
}
|
||||
TEST_ASSERT_NOT_NULL(strstr(trace, "level summary|full"));
|
||||
TEST_ASSERT_NOT_NULL(strstr(trace, "lines N"));
|
||||
TEST_ASSERT_NOT_NULL(strstr(trace, "file PATH"));
|
||||
free(h);
|
||||
}
|
||||
|
||||
static void test_help_info_trace_in_state_inspection(void)
|
||||
{
|
||||
char* h = help_text();
|
||||
char* state = strstr(h, "\nState Inspection:\n");
|
||||
char* snap = strstr(h, "\nSnapshot & Parameters:\n");
|
||||
char* info = strstr(h, "info trace <jobid>");
|
||||
|
||||
TEST_ASSERT_NOT_NULL(state);
|
||||
TEST_ASSERT_NOT_NULL(snap);
|
||||
TEST_ASSERT_NOT_NULL_MESSAGE(info, "help must list `info trace`");
|
||||
TEST_ASSERT_TRUE((state < info) && (info < snap));
|
||||
free(h);
|
||||
}
|
||||
|
||||
static void test_help_one_description_column_with_trace_lines(void)
|
||||
{
|
||||
char* h = help_text();
|
||||
const char* fields[] = {
|
||||
"step [N]", "break <EVENT_TYPE> [key=value ...]",
|
||||
"info limit <name>|all", "info trace <jobid>",
|
||||
"set units raw|human|both", "trace on all [OPTIONS]",
|
||||
"trace on job <jobid> ... [OPTIONS]", "level summary|full",
|
||||
"lines N", "file PATH",
|
||||
"trace off", "trace status",
|
||||
};
|
||||
int col0 = desc_column(h, fields[0]);
|
||||
size_t i;
|
||||
|
||||
for (i = 1; i < sizeof(fields) / sizeof(fields[0]); i++) {
|
||||
TEST_ASSERT_EQUAL_INT_MESSAGE(col0, desc_column(h, fields[i]),
|
||||
fields[i]);
|
||||
}
|
||||
free(h);
|
||||
}
|
||||
|
||||
/* ---- routing (1.11, 10.5) ------------------------------------------------ */
|
||||
|
||||
/* 30 job ids and a path longer than 63 bytes: far past the tokenizer's
|
||||
* 16 tokens / 64 characters, so only the raw line can carry them. */
|
||||
static void test_trace_routed_with_raw_line(void)
|
||||
{
|
||||
char line[2048];
|
||||
char path[256];
|
||||
size_t n;
|
||||
int i;
|
||||
char* out;
|
||||
xce_sched_trace_ctl_t* ctl;
|
||||
|
||||
snprintf(path, sizeof(path),
|
||||
"%s/a_trace_file_with_a_name_well_beyond_sixty_four_chars.trace",
|
||||
g_dir);
|
||||
n = (size_t)snprintf(line, sizeof(line), "trace on job");
|
||||
for (i = 0; i < 30; i++) {
|
||||
n += (size_t)snprintf(line + n, sizeof(line) - n, " 1-2-1000-%d", 100 + i);
|
||||
}
|
||||
snprintf(line + n, sizeof(line) - n, " level full lines 200 file %s",
|
||||
path);
|
||||
|
||||
out = dispatch(line);
|
||||
TEST_ASSERT_NOT_NULL_MESSAGE(strstr(out, "[sim] trace: enabled"), out);
|
||||
free(out);
|
||||
|
||||
ctl = repl_trace_settings_ctl(g_rs.trace.next);
|
||||
TEST_ASSERT_NOT_NULL(ctl);
|
||||
TEST_ASSERT_TRUE(ctl->nsel >= 30);
|
||||
TEST_ASSERT_EQUAL_INT(XCE_TRACE_LEVEL_FULL, ctl->level);
|
||||
TEST_ASSERT_EQUAL_UINT32(200, ctl->line_bound);
|
||||
TEST_ASSERT_EQUAL_STRING(path,
|
||||
repl_trace_settings_path(g_rs.trace.next));
|
||||
|
||||
out = dispatch("trace status");
|
||||
TEST_ASSERT_NOT_NULL(strstr(out, "[sim] trace status"));
|
||||
free(out);
|
||||
out = dispatch("trace off");
|
||||
TEST_ASSERT_NOT_NULL(strstr(out, "[sim] trace: disabled"));
|
||||
TEST_ASSERT_NULL(g_rs.trace.next);
|
||||
free(out);
|
||||
unlink(path);
|
||||
}
|
||||
|
||||
static void test_info_trace_routed_and_listed_in_usage(void)
|
||||
{
|
||||
char* out = dispatch("info trace");
|
||||
|
||||
TEST_ASSERT_NOT_NULL_MESSAGE(
|
||||
strstr(out, "[sim] info trace: usage: info trace"), out);
|
||||
free(out);
|
||||
|
||||
out = dispatch("info trace 1-2-1000-77");
|
||||
TEST_ASSERT_NULL_MESSAGE(strstr(out, "unrecognized subcommand"), out);
|
||||
TEST_ASSERT_NOT_NULL_MESSAGE(strstr(out, "[sim] info trace:"), out);
|
||||
free(out);
|
||||
|
||||
out = dispatch("info");
|
||||
TEST_ASSERT_EQUAL_STRING(
|
||||
"[sim] info: usage: info <break|session|config|job|jobs|queue|"
|
||||
"worker|resource|summary|limit|trace> [arg]\n",
|
||||
out);
|
||||
free(out);
|
||||
}
|
||||
|
||||
/* ---- set output (8.4) ---------------------------------------------------- */
|
||||
|
||||
static void assert_set_output_rejected(const char* path)
|
||||
{
|
||||
char want[256];
|
||||
FILE* before = g_sim.record_file;
|
||||
char* out;
|
||||
char* content;
|
||||
|
||||
char line[256];
|
||||
|
||||
snprintf(want, sizeof(want),
|
||||
"[sim] set output: %s is an open trace file\n", path);
|
||||
snprintf(line, sizeof(line), "set output %s", path);
|
||||
out = dispatch(line);
|
||||
TEST_ASSERT_EQUAL_STRING(want, out);
|
||||
free(out);
|
||||
TEST_ASSERT_TRUE(g_sim.record_file == before); /* output unchanged */
|
||||
content = read_file("a.trace");
|
||||
TEST_ASSERT_EQUAL_STRING("keep\n", content); /* not truncated */
|
||||
free(content);
|
||||
}
|
||||
|
||||
static void test_set_output_rejects_open_trace_file(void)
|
||||
{
|
||||
write_file("a.trace", "keep\n");
|
||||
g_sim.record_file = fopen("rec.replay", "w");
|
||||
TEST_ASSERT_NOT_NULL(g_sim.record_file);
|
||||
dispatch_quiet("trace on all file a.trace");
|
||||
TEST_ASSERT_NOT_NULL(repl_trace_settings_file(g_rs.trace.next));
|
||||
|
||||
assert_set_output_rejected("a.trace");
|
||||
TEST_ASSERT_EQUAL_INT(0, link("a.trace", "b.trace"));
|
||||
assert_set_output_rejected("b.trace");
|
||||
}
|
||||
|
||||
/* A file a command replaced during the session stays open, and rejected,
|
||||
* until the session ends (8.4 with 8.5). */
|
||||
static void test_set_output_rejects_file_kept_open_by_session(void)
|
||||
{
|
||||
write_file("a.trace", "keep\n");
|
||||
dispatch_quiet("trace on all file a.trace");
|
||||
repl_trace_session_begin(&g_sim);
|
||||
dispatch_quiet("trace off");
|
||||
TEST_ASSERT_NULL(g_rs.trace.next);
|
||||
|
||||
assert_set_output_rejected("a.trace");
|
||||
|
||||
repl_trace_session_end(&g_sim);
|
||||
TEST_ASSERT_FALSE(repl_trace_file_conflicts(&g_sim, "a.trace"));
|
||||
}
|
||||
|
||||
/* ---- quit and end of input (8.6) ----------------------------------------- */
|
||||
|
||||
/* Runs in the child at exit(), after the quit path ran: reports whether
|
||||
* tracing was shut down, then frees the fixture so the child exits clean. */
|
||||
static void report_at_exit(void)
|
||||
{
|
||||
char ok = ((g_rs.trace.next == NULL) && (g_rs.trace.cur == NULL)
|
||||
&& (g_sim.core.trace_ctl == NULL))
|
||||
? 'Y'
|
||||
: 'N';
|
||||
|
||||
(void)!write(g_report_fd, &ok, 1);
|
||||
close(g_report_fd);
|
||||
fixture_fini();
|
||||
}
|
||||
|
||||
static void assert_child_shuts_down(bool eof)
|
||||
{
|
||||
int fds[2];
|
||||
pid_t pid;
|
||||
int status = 0;
|
||||
char got = '?';
|
||||
|
||||
write_file("a.trace", "");
|
||||
dispatch_quiet("trace on all file a.trace");
|
||||
repl_trace_session_begin(&g_sim);
|
||||
TEST_ASSERT_NOT_NULL(g_sim.core.trace_ctl);
|
||||
TEST_ASSERT_EQUAL_INT(0, pipe(fds));
|
||||
|
||||
fflush(stdout);
|
||||
pid = fork();
|
||||
TEST_ASSERT_TRUE(pid >= 0);
|
||||
if (pid == 0) {
|
||||
int devnull = open("/dev/null", O_RDWR);
|
||||
|
||||
close(fds[0]);
|
||||
g_report_fd = fds[1];
|
||||
dup2(devnull, STDIN_FILENO);
|
||||
dup2(devnull, STDOUT_FILENO);
|
||||
atexit(report_at_exit);
|
||||
if (eof) {
|
||||
repl_command_loop(&g_sim); /* readline() hits EOF */
|
||||
} else {
|
||||
(void)repl_dispatch(&g_sim, "quit");
|
||||
}
|
||||
_exit(99); /* both paths must exit(0) */
|
||||
}
|
||||
close(fds[1]);
|
||||
TEST_ASSERT_EQUAL_INT(1, (int)read(fds[0], &got, 1));
|
||||
close(fds[0]);
|
||||
TEST_ASSERT_EQUAL_INT(pid, waitpid(pid, &status, 0));
|
||||
TEST_ASSERT_TRUE(WIFEXITED(status));
|
||||
TEST_ASSERT_EQUAL_INT(0, WEXITSTATUS(status));
|
||||
TEST_ASSERT_EQUAL_CHAR_MESSAGE('Y', got, "tracing still active at exit");
|
||||
}
|
||||
|
||||
static void test_quit_shuts_tracing_down(void) { assert_child_shuts_down(false); }
|
||||
|
||||
static void test_eof_shuts_tracing_down(void) { assert_child_shuts_down(true); }
|
||||
|
||||
/* ---- continue-mode rollup (7.3, 7.4) ------------------------------------- */
|
||||
|
||||
/* An outcome identical to the active range would extend it, but a traced
|
||||
* round flushes the range and is reported on its own. */
|
||||
static void test_rollup_traced_round_emitted_alone_after_flush(void)
|
||||
{
|
||||
char* out;
|
||||
char* range;
|
||||
char* alone;
|
||||
|
||||
open_dispatch_range();
|
||||
out = rollup_round5(1, true);
|
||||
range = strstr(out, "[sim] round 3-4 (2 round(s)");
|
||||
alone = strstr(out, "[sim] round 5-5 (1 round(s)");
|
||||
TEST_ASSERT_NOT_NULL_MESSAGE(range, out);
|
||||
TEST_ASSERT_NOT_NULL_MESSAGE(alone, out);
|
||||
TEST_ASSERT_TRUE(range < alone);
|
||||
TEST_ASSERT_NOT_NULL(strstr(alone, "1 dispatch, 0 pend, 0 preempt"));
|
||||
TEST_ASSERT_FALSE_MESSAGE(g_rs.collapse_active,
|
||||
"a traced round must not start a range");
|
||||
TEST_ASSERT_NULL(g_rs.collapse_pend_jobids);
|
||||
free(out);
|
||||
}
|
||||
|
||||
/* A traced round with no dispatch, preempt or pend job is idle, and is
|
||||
* reported even though it is a single round. */
|
||||
static void test_rollup_traced_idle_round_reported_as_idle(void)
|
||||
{
|
||||
char* out = rollup_round5(0, true);
|
||||
|
||||
TEST_ASSERT_NOT_NULL_MESSAGE(strstr(out, "[sim] round 5-5 (1 round(s)"),
|
||||
out);
|
||||
TEST_ASSERT_NOT_NULL_MESSAGE(strstr(out, "): idle\n"), out);
|
||||
TEST_ASSERT_FALSE(g_rs.collapse_active);
|
||||
free(out);
|
||||
}
|
||||
|
||||
/* The flush idle rule, not the allocation-failure fallback: a pend-only
|
||||
* traced round is not idle. */
|
||||
static void test_rollup_traced_pend_only_round_not_idle(void)
|
||||
{
|
||||
char* out;
|
||||
|
||||
add_pend_job();
|
||||
out = rollup_round5(0, true);
|
||||
TEST_ASSERT_NOT_NULL_MESSAGE(strstr(out, "0 dispatch, 1 pend, 0 preempt"),
|
||||
out);
|
||||
TEST_ASSERT_NULL_MESSAGE(strstr(out, "idle"), out);
|
||||
TEST_ASSERT_FALSE(g_rs.collapse_active);
|
||||
free(out);
|
||||
}
|
||||
|
||||
/* The round after a traced one starts a fresh range as before; an
|
||||
* untraced round is unaffected. */
|
||||
static void test_rollup_untraced_round_after_traced_starts_range(void)
|
||||
{
|
||||
char* out;
|
||||
|
||||
open_dispatch_range();
|
||||
free(rollup_round5(1, true));
|
||||
|
||||
g_rs.session_count = 6;
|
||||
g_rs.trace.round_traced = false; /* reset by the next session begin */
|
||||
cap_begin();
|
||||
repl_round_rollup_process(&g_sim);
|
||||
out = cap_end();
|
||||
TEST_ASSERT_EQUAL_STRING("", out);
|
||||
TEST_ASSERT_TRUE(g_rs.collapse_active);
|
||||
TEST_ASSERT_EQUAL_UINT64(6, g_rs.collapse_start_round);
|
||||
free(out);
|
||||
}
|
||||
|
||||
/* ---- repl.h gates -------------------------------------------------------- */
|
||||
|
||||
static void test_gates_noop_when_not_interactive(void)
|
||||
{
|
||||
fixture_decision_t f;
|
||||
char* out;
|
||||
|
||||
dispatch_quiet("trace on all");
|
||||
g_sim.interactive = false;
|
||||
dispatch_of(&f, PEND_JOB);
|
||||
|
||||
cap_begin();
|
||||
xce_repl_trace_session_begin(&g_sim);
|
||||
xce_repl_check_trace(&g_sim, &f.d);
|
||||
xce_repl_check_trace_only(&g_sim, &f.d);
|
||||
xce_repl_trace_session_end(&g_sim);
|
||||
out = cap_end();
|
||||
|
||||
TEST_ASSERT_EQUAL_STRING("", out);
|
||||
TEST_ASSERT_NULL(g_sim.core.trace_ctl); /* 11.3: never published */
|
||||
TEST_ASSERT_NULL(g_rs.trace.cur);
|
||||
TEST_ASSERT_FALSE(g_rs.trace.session_in_progress);
|
||||
TEST_ASSERT_NOT_NULL(f.d.trace); /* not consumed */
|
||||
sched_trace_rec_free(f.d.trace);
|
||||
free(out);
|
||||
}
|
||||
|
||||
static void test_gates_forward_when_interactive(void)
|
||||
{
|
||||
fixture_decision_t f;
|
||||
fixture_decision_t t;
|
||||
char* out;
|
||||
|
||||
dispatch_quiet("trace on all");
|
||||
xce_repl_trace_session_begin(&g_sim);
|
||||
TEST_ASSERT_NOT_NULL(g_sim.core.trace_ctl);
|
||||
TEST_ASSERT_TRUE(g_rs.trace.session_in_progress);
|
||||
|
||||
dispatch_of(&f, PEND_JOB);
|
||||
dispatch_of(&t, PEND_JOB);
|
||||
t.d.op = XCE_DECISION_TRACE;
|
||||
cap_begin();
|
||||
xce_repl_check_trace(&g_sim, &f.d);
|
||||
xce_repl_check_trace_only(&g_sim, &t.d);
|
||||
out = cap_end();
|
||||
TEST_ASSERT_NULL(f.d.trace); /* moved to the store */
|
||||
TEST_ASSERT_NULL(t.d.trace);
|
||||
TEST_ASSERT_TRUE(strlen(out) > 0);
|
||||
TEST_ASSERT_TRUE(g_rs.trace.round_traced);
|
||||
free(out);
|
||||
|
||||
/* no record: nothing printed */
|
||||
cap_begin();
|
||||
xce_repl_check_trace(&g_sim, &f.d);
|
||||
out = cap_end();
|
||||
TEST_ASSERT_EQUAL_STRING("", out);
|
||||
free(out);
|
||||
|
||||
xce_repl_trace_session_end(&g_sim);
|
||||
TEST_ASSERT_NULL(g_sim.core.trace_ctl);
|
||||
TEST_ASSERT_FALSE(g_rs.trace.session_in_progress);
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
UNITY_BEGIN();
|
||||
RUN_TEST(test_help_trace_section_is_last_and_lists_trace_commands);
|
||||
RUN_TEST(test_help_info_trace_in_state_inspection);
|
||||
RUN_TEST(test_help_one_description_column_with_trace_lines);
|
||||
RUN_TEST(test_trace_routed_with_raw_line);
|
||||
RUN_TEST(test_info_trace_routed_and_listed_in_usage);
|
||||
RUN_TEST(test_set_output_rejects_open_trace_file);
|
||||
RUN_TEST(test_set_output_rejects_file_kept_open_by_session);
|
||||
RUN_TEST(test_quit_shuts_tracing_down);
|
||||
RUN_TEST(test_eof_shuts_tracing_down);
|
||||
RUN_TEST(test_rollup_traced_round_emitted_alone_after_flush);
|
||||
RUN_TEST(test_rollup_traced_idle_round_reported_as_idle);
|
||||
RUN_TEST(test_rollup_traced_pend_only_round_not_idle);
|
||||
RUN_TEST(test_rollup_untraced_round_after_traced_starts_range);
|
||||
RUN_TEST(test_gates_noop_when_not_interactive);
|
||||
RUN_TEST(test_gates_forward_when_interactive);
|
||||
return UNITY_END();
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue