fix(scheduler): clock/resval/jobid-format fixes plus REPL live-operator gaps (task 12.5)

Bugs fixed, discovered across a day of live-operator testing against a
real deployed cluster:

- bug-1584: clock anchors never initialized to real wall-clock values,
  timestamps showed system uptime instead of Unix epoch.
- bug-1585: heap-use-after-free in the normal-mode driver's periodic
  resval update path -- core_events.c's RESVALS_FULL/_DELTA handlers do a
  shallow zarray_put() of the wire-decoded xce_val_t*, but driver.c's
  release_translated_payload() unconditionally freed the same decoded
  array right after, so the next periodic report for the same
  worker+resource triggered a UAF read via kill_sched_resval(). New
  regression test: test_resval_ownership.c.
- bug-1587/1588/1591: raw-ns/raw-uint64 jobid/timestamp prints across the
  REPL/SIM family converted to the same human-readable formatting already
  used elsewhere (fmt_jobid(), timestamp formatting).
- bug-1590: repl_info.c's XCE_VAL_NUMERIC checks corrected to
  Value_u_fvalue, fixing `info worker`/`info resource` falling back to
  0.00 for real numeric resvals.

task 12.5 (Req 5.22 amendment) -- five REPL gaps surfaced by a live
operator session, each with its own bug id:
- bug-1594: `continue`/`step` reaching EOF with no breakpoint hit now
  re-enters the REPL via xce_repl_on_replay_complete() instead of exiting
  the process; step/continue issued after replay is already complete
  re-prompt instead of silently exiting.
- bug-1595: `step`/`continue` now echo each input event (not just
  decisions) via repl_print_event_summary(), in real event/decision
  order.
- bug-1593: `info resource <name>|all` now reports resource definition
  metadata restricted to XCE_RESF_GLOBAL/XCE_RESF_SHARED resources, read
  from core->sharedInst, instead of aggregating host-based resources
  across every worker.
- bug-1596: repl_split_tokens() strips one matching pair of surrounding
  quote characters from each token.

See requirements.md's Req 5 AC 22 "Amendment (2026-08-19)" for the full
rationale on each of the five task 12.5 gaps.

Full scheduler suite green (41/42; the one remaining failure,
test_recordstart_symlink_escape_replies_error, is the pre-existing
unrelated bug-1560).
This commit is contained in:
Rongsong Shen 2026-08-19 09:48:46 +00:00
parent 515f4ca098
commit c9acd502c0
No known key found for this signature in database
GPG key ID: 5BCDAAE0123E9FE2
32 changed files with 2547 additions and 295 deletions

View file

@ -455,25 +455,61 @@ static void test_should_pause_never_fires_in_continue_mode(void)
/* -- Observable (a), proved by output ORDER, not merely presence. */
/* ---------------------------------------------------------------------- */
static xce_sched_sim_driver_t g_t2_sim;
static sched_sim_args_t g_t2_args;
static int g_t2_rc;
static void run_driver_for_t2_capture(void)
/*
* task 12.5 (bug-1594): a completed --interactive run now re-enters the
* REPL (xce_repl_on_replay_complete(), repl.c) instead of returning, so a
* "continue"-to-EOF script that has no further scripted line will hit
* stdin EOF inside that re-entered repl_command_loop() -- which
* repl_command_loop() has always treated as an implicit `quit` (exit(0)).
* Before task 12.5 this test ran the driver IN-PROCESS (capture_stdout_
* around()) because sched_sim_driver_run() genuinely returned once EOF was
* reached; now it must fork, exactly like Test 3 below, since exit(0)
* would otherwise tear down this whole unity binary mid-suite.
*/
static void child_prepause_then_quit(const char* replay_path,
const char* script_path,
const char* capture_path)
{
g_t2_rc = sched_sim_driver_run(&g_t2_sim, &g_t2_args);
xce_sched_sim_driver_t sim;
sched_sim_args_t args;
if (freopen(script_path, "r", stdin) == NULL) {
_exit(120);
}
if (freopen(capture_path, "w", stdout) == NULL) {
_exit(121);
}
memset(&sim, 0, sizeof(sim));
memset(&args, 0, sizeof(args));
args.replay_path = replay_path;
args.sim_start_ts = SCHED_SIM_START_TS_LATEST;
args.sim_speed = 0;
args.interactive = true;
(void)sched_sim_driver_run(&sim, &args);
/* Unreachable in a correct implementation -- the script's trailing
* `quit` (or, absent that, stdin EOF) must already have called
* exit(0) from inside the post-replay-completion pause. */
fflush(stdout);
_exit(111);
}
static void test_pre_replay_pause_blocks_before_any_event_is_processed(void)
{
char replay_path[PATH_MAX];
char script_dir[PATH_MAX];
char aux_dir[PATH_MAX];
char script_path[PATH_MAX];
char capture_path[PATH_MAX];
char pend_marker[80];
char jobid_buf[64];
FILE* f;
pid_t pid;
int status;
char* captured;
char* ready_pos;
char* pend_pos;
FILE* saved_stdin_check;
f = write_header_and_snapshot(replay_path, sizeof(replay_path),
"_prepause");
@ -485,30 +521,43 @@ static void test_pre_replay_pause_blocks_before_any_event_is_processed(void)
append_end_of_stream_marker(f);
fclose(f);
make_tmp_dir(script_dir, sizeof(script_dir), "_prepausescript");
snprintf(script_path, sizeof(script_path), "%s/script.txt", script_dir);
/* A single "continue" -- 12.1's own scope means continue never
* re-pauses, so the run reaches EOF cleanly without reading stdin
* again. */
write_text_file(script_path, "continue\n");
make_tmp_dir(aux_dir, sizeof(aux_dir), "_prepauseaux");
snprintf(script_path, sizeof(script_path), "%s/script.txt", aux_dir);
snprintf(capture_path, sizeof(capture_path), "%s/capture.out", aux_dir);
/* "continue" runs to EOF (12.1's own scope: continue never re-pauses
* mid-stream absent a breakpoint); task 12.5 then re-enters the REPL
* at replay completion, so an explicit trailing `quit` is required to
* end the child deterministically. */
write_text_file(script_path, "continue\nquit\n");
saved_stdin_check = freopen(script_path, "r", stdin);
TEST_ASSERT_NOT_NULL_MESSAGE(saved_stdin_check,
"freopen of scripted stdin must succeed");
pid = fork();
TEST_ASSERT_TRUE_MESSAGE(pid >= 0, "fork() must succeed");
memset(&g_t2_sim, 0, sizeof(g_t2_sim));
memset(&g_t2_args, 0, sizeof(g_t2_args));
g_t2_args.replay_path = replay_path;
g_t2_args.sim_start_ts = SCHED_SIM_START_TS_LATEST;
g_t2_args.sim_speed = 0;
g_t2_args.interactive = true;
if (pid == 0) {
child_prepause_then_quit(replay_path, script_path, capture_path);
_exit(112); /* unreachable -- child_prepause_then_quit never
* returns */
}
captured = capture_stdout_around(run_driver_for_t2_capture);
TEST_ASSERT_EQUAL_INT_MESSAGE(pid, waitpid(pid, &status, 0),
"waitpid must reap the REPL child");
TEST_ASSERT_TRUE_MESSAGE(WIFEXITED(status),
"the REPL child must exit normally via quit's "
"exit(0), not crash/signal");
TEST_ASSERT_EQUAL_INT_MESSAGE(0, WEXITSTATUS(status),
"quit's exit(0) is the only correct exit "
"path");
TEST_ASSERT_EQUAL_INT(0, g_t2_rc);
captured = read_whole_file(capture_path);
/* bug-1591: sim_print_decision() (sim_loop.c) now renders jobids via
* fmt_jobid(), not raw %llu -- this marker must match that same
* human-readable form. */
fmt_jobid(jobid_buf, sizeof(jobid_buf), 4242ULL);
snprintf(pend_marker, sizeof(pend_marker), "PEND jobid=%s", jobid_buf);
ready_pos = strstr(captured, "[sim] ready. type 'help' for commands.");
pend_pos = strstr(captured, "PEND jobid=4242");
pend_pos = strstr(captured, pend_marker);
TEST_ASSERT_NOT_NULL_MESSAGE(ready_pos,
"pre-replay pause banner must be printed");
@ -526,6 +575,204 @@ static void test_pre_replay_pause_blocks_before_any_event_is_processed(void)
"PASS\n");
}
/*
* task 12.5 (bug-1594) regression: `continue` reaching EOF with no
* breakpoints must NOT exit the process -- it must re-enter the REPL so
* the operator can inspect final state, and only `quit` (or stdin EOF,
* pre-existing documented behavior) may end it.
*/
static void child_continue_then_inspect(const char* replay_path,
const char* script_path,
const char* capture_path)
{
xce_sched_sim_driver_t sim;
sched_sim_args_t args;
if (freopen(script_path, "r", stdin) == NULL) {
_exit(120);
}
if (freopen(capture_path, "w", stdout) == NULL) {
_exit(121);
}
memset(&sim, 0, sizeof(sim));
memset(&args, 0, sizeof(args));
args.replay_path = replay_path;
args.sim_start_ts = SCHED_SIM_START_TS_LATEST;
args.sim_speed = 0;
args.interactive = true;
(void)sched_sim_driver_run(&sim, &args);
fflush(stdout);
_exit(111); /* unreachable -- the script's `quit` must already have
* exited */
}
static void test_continue_with_no_breakpoints_stays_alive_after_replay_complete(
void)
{
char replay_path[PATH_MAX];
char aux_dir[PATH_MAX];
char script_path[PATH_MAX];
char capture_path[PATH_MAX];
FILE* f;
pid_t pid;
int status;
char* captured;
f = write_header_and_snapshot(replay_path, sizeof(replay_path),
"_contnoexit");
TEST_ASSERT_NOT_NULL(f);
append_queue_update_event(f, 1000ULL, "normal");
append_jobs_push_event(f, 1500ULL, 9090ULL, "normal");
append_replay_session(f, 2000ULL);
append_end_of_stream_marker(f);
fclose(f);
make_tmp_dir(aux_dir, sizeof(aux_dir), "_contnoexitaux");
snprintf(script_path, sizeof(script_path), "%s/script.txt", aux_dir);
snprintf(capture_path, sizeof(capture_path), "%s/capture.out", aux_dir);
/* No breakpoints set. After `continue` runs to EOF, the REPL must
* still be alive to process `info summary` before `quit`. */
write_text_file(script_path, "continue\ninfo summary\nquit\n");
pid = fork();
TEST_ASSERT_TRUE_MESSAGE(pid >= 0, "fork() must succeed");
if (pid == 0) {
child_continue_then_inspect(replay_path, script_path, capture_path);
_exit(112);
}
TEST_ASSERT_EQUAL_INT_MESSAGE(pid, waitpid(pid, &status, 0),
"waitpid must reap the REPL child");
TEST_ASSERT_TRUE_MESSAGE(
WIFEXITED(status),
"the child must exit normally via quit's exit(0) -- a hang or "
"crash means the REPL did not survive replay completion");
TEST_ASSERT_EQUAL_INT(0, WEXITSTATUS(status));
captured = read_whole_file(capture_path);
TEST_ASSERT_NOT_NULL_MESSAGE(
strstr(captured, "[sim] replay complete."),
"replay completion must still print its banner");
TEST_ASSERT_NOT_NULL_MESSAGE(
strstr(captured, "1 jobs (1 pending, 0 running)"),
"info summary issued AFTER replay completion must still execute "
"against final state -- proves the REPL did not exit on EOF "
"(bug-1594)");
free(captured);
printf("test_continue_with_no_breakpoints_stays_alive_after_replay_"
"complete: PASS\n");
}
/*
* task 12.5 (bug-1594) regression: `step`/`continue` issued AFTER replay
* is already complete must re-prompt with an informational message, not
* be treated as "hand control back to a driver loop" (there is none left).
*/
static void child_continue_twice_after_complete(const char* replay_path,
const char* script_path,
const char* capture_path)
{
xce_sched_sim_driver_t sim;
sched_sim_args_t args;
if (freopen(script_path, "r", stdin) == NULL) {
_exit(120);
}
if (freopen(capture_path, "w", stdout) == NULL) {
_exit(121);
}
memset(&sim, 0, sizeof(sim));
memset(&args, 0, sizeof(args));
args.replay_path = replay_path;
args.sim_start_ts = SCHED_SIM_START_TS_LATEST;
args.sim_speed = 0;
args.interactive = true;
(void)sched_sim_driver_run(&sim, &args);
fflush(stdout);
_exit(111);
}
static void test_continue_after_replay_complete_reprompts_instead_of_exiting(
void)
{
char replay_path[PATH_MAX];
char aux_dir[PATH_MAX];
char script_path[PATH_MAX];
char capture_path[PATH_MAX];
FILE* f;
pid_t pid;
int status;
char* captured;
f = write_header_and_snapshot(replay_path, sizeof(replay_path),
"_contafter");
TEST_ASSERT_NOT_NULL(f);
append_queue_update_event(f, 1000ULL, "normal");
append_jobs_push_event(f, 1500ULL, 8080ULL, "normal");
append_replay_session(f, 2000ULL);
append_end_of_stream_marker(f);
fclose(f);
make_tmp_dir(aux_dir, sizeof(aux_dir), "_contafteraux");
snprintf(script_path, sizeof(script_path), "%s/script.txt", aux_dir);
snprintf(capture_path, sizeof(capture_path), "%s/capture.out", aux_dir);
/* Second `continue`/`step` are issued AFTER replay is already
* complete -- both must re-prompt, not attempt to resume anything. */
write_text_file(script_path, "continue\ncontinue\nstep\nquit\n");
pid = fork();
TEST_ASSERT_TRUE_MESSAGE(pid >= 0, "fork() must succeed");
if (pid == 0) {
child_continue_twice_after_complete(replay_path, script_path,
capture_path);
_exit(112);
}
TEST_ASSERT_EQUAL_INT_MESSAGE(pid, waitpid(pid, &status, 0),
"waitpid must reap the REPL child");
TEST_ASSERT_TRUE_MESSAGE(WIFEXITED(status),
"the child must exit normally via quit's "
"exit(0)");
TEST_ASSERT_EQUAL_INT(0, WEXITSTATUS(status));
captured = read_whole_file(capture_path);
{
const char* needle = "[sim] replay already complete -- no more "
"events to process. Use 'quit' to exit.";
char* first = strstr(captured, needle);
char* second = (first != NULL)
? strstr(first + 1, needle)
: NULL;
TEST_ASSERT_NOT_NULL_MESSAGE(
first, "a `continue` issued after replay completion must "
"print the re-prompt message, not silently resume/exit "
"(bug-1594)");
TEST_ASSERT_NOT_NULL_MESSAGE(
second, "a `step` issued after replay completion must ALSO "
"print the re-prompt message");
}
free(captured);
printf("test_continue_after_replay_complete_reprompts_instead_of_"
"exiting: PASS\n");
}
/* ---------------------------------------------------------------------- */
/* Test 3: "step 1" against a three-session fixture processes EXACTLY one */
/* session before re-pausing -- proved end to end via a forked child that */
@ -572,6 +819,10 @@ static void test_step_1_processes_exactly_one_session_then_quit_exits(void)
char aux_dir[PATH_MAX];
char script_path[PATH_MAX];
char capture_path[PATH_MAX];
char pend_marker_111[80];
char pend_marker_222[80];
char pend_marker_333[80];
char jobid_buf[64];
FILE* f;
pid_t pid;
int status;
@ -620,12 +871,25 @@ static void test_step_1_processes_exactly_one_session_then_quit_exits(void)
captured = read_whole_file(capture_path);
TEST_ASSERT_NOT_NULL_MESSAGE(strstr(captured, "PEND jobid=111"),
/* bug-1591: sim_print_decision() (sim_loop.c) now renders jobids via
* fmt_jobid(), not raw %llu -- these markers must match that same
* human-readable form. */
fmt_jobid(jobid_buf, sizeof(jobid_buf), 111ULL);
snprintf(pend_marker_111, sizeof(pend_marker_111), "PEND jobid=%s",
jobid_buf);
fmt_jobid(jobid_buf, sizeof(jobid_buf), 222ULL);
snprintf(pend_marker_222, sizeof(pend_marker_222), "PEND jobid=%s",
jobid_buf);
fmt_jobid(jobid_buf, sizeof(jobid_buf), 333ULL);
snprintf(pend_marker_333, sizeof(pend_marker_333), "PEND jobid=%s",
jobid_buf);
TEST_ASSERT_NOT_NULL_MESSAGE(strstr(captured, pend_marker_111),
"session 1's decision must have run");
TEST_ASSERT_NULL_MESSAGE(strstr(captured, "PEND jobid=222"),
TEST_ASSERT_NULL_MESSAGE(strstr(captured, pend_marker_222),
"session 2 must NEVER have run -- step 1 "
"must pause strictly after session 1");
TEST_ASSERT_NULL_MESSAGE(strstr(captured, "PEND jobid=333"),
TEST_ASSERT_NULL_MESSAGE(strstr(captured, pend_marker_333),
"session 3 must NEVER have run either");
free(captured);
@ -642,6 +906,8 @@ int main(void)
RUN_TEST(test_should_pause_never_fires_in_continue_mode);
RUN_TEST(test_pre_replay_pause_blocks_before_any_event_is_processed);
RUN_TEST(test_step_1_processes_exactly_one_session_then_quit_exits);
RUN_TEST(test_continue_with_no_breakpoints_stays_alive_after_replay_complete);
RUN_TEST(test_continue_after_replay_complete_reprompts_instead_of_exiting);
return UNITY_END();
}