fix(scheduler): break jobid= filters accept every jobid form the REPL prints (bug-1926)

repl_u64_str_eq() compared the packed jobid's raw decimal string with
the filter text, so a breakpoint on the dashed jobid the REPL prints
never fired. The filter is now parsed once when the breakpoint is set
(repl_parse_jobsel_arg, moved to repl_fmt.c) and matched with
repl_jobsel_matches: dashed ids, X[k], X[a-b] and raw decimal ids all
work, a bare array-aligned id also matches its elements, and an
unparseable value is rejected instead of creating a dead breakpoint.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Rongsong Shen 2026-10-09 04:06:53 +00:00
parent d2a94ebc3e
commit 7aef6b85c9
Signed by untrusted user: rshen
GPG key ID: 80533536B0EB651C
6 changed files with 501 additions and 69 deletions

View file

@ -906,6 +906,296 @@ static void test_decision_breakpoint_matches_type_filter(void)
printf("test_decision_breakpoint_matches_type_filter: PASS\n");
}
/* ---------------------------------------------------------------------- */
/* bug-1926: a `jobid=` filter takes the jobid forms the REPL prints and */
/* `info job`/`info trace`/`trace on job` accept (the dashed id, X[k], */
/* X[a-b]) as well as the raw packed decimal id, and an unparseable value */
/* is rejected when the breakpoint is set. */
/* ---------------------------------------------------------------------- */
/* The array 0-0-0-32768 (sequence 1 << 15, so array-aligned) and its
* element k, the packed ids parse_jobsel()'s `X[k]` names. */
#define B1926_ARRAY_SEQ (1U << N_ARRAY_IDX_BITS)
static uint64_t b1926_elem(uint32_t seq, uint32_t k)
{
return MAKE_JOBID(0, 0, 0, TAG_JOB_ARRAY, seq | k);
}
static void b1926_sim_init(xce_sched_sim_driver_t* sim)
{
memset(sim, 0, sizeof(*sim));
/* makeString() needs xce_sched_core_init()'s xstring table */
TEST_ASSERT_EQUAL_INT(0, xce_sched_core_init(&(sim->core)));
sim->repl_state = repl_state_ensure(sim);
TEST_ASSERT_NOT_NULL(sim->repl_state);
}
/* true iff a PEND decision for `jobid` hits a breakpoint */
static bool b1926_pend_hits(xce_sched_sim_driver_t* sim, uint64_t jobid)
{
xce_pendreason_t reason;
xce_decision_t d;
memset(&reason, 0, sizeof(reason));
reason.jobid = jobid;
memset(&d, 0, sizeof(d));
d.op = XCE_DECISION_PEND;
d.u.reason = &reason;
return repl_find_decision_breakpoint(&(sim->core), sim->repl_state, &d)
!= NULL;
}
static void test_decision_breakpoint_jobid_dashed_form_matches(void)
{
xce_sched_sim_driver_t sim;
char printed[64];
char line[128];
b1926_sim_init(&sim);
/* the form the hit line prints: "decision: PEND jobid=0-0-0-104" */
fmt_jobid(printed, sizeof(printed), 104ULL);
TEST_ASSERT_EQUAL_STRING("0-0-0-104", printed);
snprintf(line, sizeof(line), "break DECISION jobid=%s", printed);
TEST_ASSERT_FALSE(repl_dispatch(&sim, line));
TEST_ASSERT_EQUAL_INT(1, sim.repl_state->nbreakpoints);
TEST_ASSERT_TRUE_MESSAGE(b1926_pend_hits(&sim, 104ULL),
"jobid=0-0-0-104 must match job 104");
TEST_ASSERT_FALSE_MESSAGE(b1926_pend_hits(&sim, 105ULL),
"jobid=0-0-0-104 must NOT match job 105");
TEST_ASSERT_FALSE_MESSAGE(b1926_pend_hits(&sim, 103ULL),
"jobid=0-0-0-104 must NOT match job 103");
printf("test_decision_breakpoint_jobid_dashed_form_matches: PASS\n");
}
static void test_decision_breakpoint_jobid_element_and_range_match(void)
{
xce_sched_sim_driver_t sim;
char printed[64];
char line[128];
/* X[k], copied from the REPL's own rendering of element 2 */
b1926_sim_init(&sim);
fmt_jobid(printed, sizeof(printed), b1926_elem(B1926_ARRAY_SEQ, 2));
TEST_ASSERT_EQUAL_STRING("0-0-0-32768[2]", printed);
snprintf(line, sizeof(line), "break DECISION jobid=%s", printed);
TEST_ASSERT_FALSE(repl_dispatch(&sim, line));
TEST_ASSERT_EQUAL_INT(1, sim.repl_state->nbreakpoints);
TEST_ASSERT_TRUE_MESSAGE(b1926_pend_hits(&sim,
b1926_elem(B1926_ARRAY_SEQ, 2)),
"X[2] must match element 2");
TEST_ASSERT_FALSE_MESSAGE(b1926_pend_hits(&sim,
b1926_elem(B1926_ARRAY_SEQ, 1)),
"X[2] must NOT match element 1");
TEST_ASSERT_FALSE_MESSAGE(b1926_pend_hits(&sim,
b1926_elem(B1926_ARRAY_SEQ, 3)),
"X[2] must NOT match element 3");
TEST_ASSERT_FALSE_MESSAGE(b1926_pend_hits(&sim, B1926_ARRAY_SEQ + 2),
"X[2] must NOT match the plain job whose "
"sequence equals element 2's");
TEST_ASSERT_FALSE_MESSAGE(
b1926_pend_hits(&sim, b1926_elem(2 * B1926_ARRAY_SEQ, 2)),
"X[2] must NOT match element 2 of another array");
/* X[a-b] */
b1926_sim_init(&sim);
TEST_ASSERT_FALSE(repl_dispatch(&sim, "break DECISION jobid=0-0-0-32768[1-3]"));
TEST_ASSERT_EQUAL_INT(1, sim.repl_state->nbreakpoints);
TEST_ASSERT_FALSE(b1926_pend_hits(&sim, b1926_elem(B1926_ARRAY_SEQ, 0)));
TEST_ASSERT_TRUE(b1926_pend_hits(&sim, b1926_elem(B1926_ARRAY_SEQ, 1)));
TEST_ASSERT_TRUE(b1926_pend_hits(&sim, b1926_elem(B1926_ARRAY_SEQ, 2)));
TEST_ASSERT_TRUE(b1926_pend_hits(&sim, b1926_elem(B1926_ARRAY_SEQ, 3)));
TEST_ASSERT_FALSE(b1926_pend_hits(&sim, b1926_elem(B1926_ARRAY_SEQ, 4)));
printf("test_decision_breakpoint_jobid_element_and_range_match: PASS\n");
}
/* A bare array-aligned dashed id selects what `trace on job` selects: the
* plain job with that id and every element of the array with that id. A
* raw packed decimal id keeps its pre-bug-1926 meaning: that exact id. */
static void test_decision_breakpoint_jobid_bare_array_id_and_raw(void)
{
xce_sched_sim_driver_t sim;
char line[128];
b1926_sim_init(&sim);
TEST_ASSERT_FALSE(repl_dispatch(&sim, "break DECISION jobid=0-0-0-32768"));
TEST_ASSERT_TRUE_MESSAGE(b1926_pend_hits(&sim, B1926_ARRAY_SEQ),
"a bare id matches the plain job");
TEST_ASSERT_TRUE_MESSAGE(b1926_pend_hits(&sim,
b1926_elem(B1926_ARRAY_SEQ, 0)),
"a bare array id matches element 0");
TEST_ASSERT_TRUE_MESSAGE(b1926_pend_hits(&sim,
b1926_elem(B1926_ARRAY_SEQ, 7)),
"a bare array id matches element 7");
TEST_ASSERT_FALSE(b1926_pend_hits(&sim, B1926_ARRAY_SEQ + 1));
TEST_ASSERT_FALSE(
b1926_pend_hits(&sim, b1926_elem(2 * B1926_ARRAY_SEQ, 0)));
/* an unaligned bare id is only a plain job (an element's packed id
* carries the array tag, so it never equals a plain id) */
b1926_sim_init(&sim);
TEST_ASSERT_FALSE(repl_dispatch(&sim, "break DECISION jobid=0-0-0-104"));
TEST_ASSERT_TRUE(b1926_pend_hits(&sim, 104ULL));
TEST_ASSERT_FALSE(b1926_pend_hits(&sim, b1926_elem(B1926_ARRAY_SEQ, 0)));
/* raw decimal: exactly that packed id, as before */
b1926_sim_init(&sim);
snprintf(line, sizeof(line), "break DECISION jobid=%llu",
(unsigned long long)B1926_ARRAY_SEQ);
TEST_ASSERT_FALSE(repl_dispatch(&sim, line));
TEST_ASSERT_EQUAL_INT(1, sim.repl_state->nbreakpoints);
TEST_ASSERT_TRUE(b1926_pend_hits(&sim, B1926_ARRAY_SEQ));
TEST_ASSERT_FALSE_MESSAGE(b1926_pend_hits(&sim,
b1926_elem(B1926_ARRAY_SEQ, 0)),
"a raw packed id names one exact job");
TEST_ASSERT_FALSE(b1926_pend_hits(&sim, B1926_ARRAY_SEQ + 1));
/* raw decimal of an element's packed id: that element */
b1926_sim_init(&sim);
snprintf(line, sizeof(line), "break DECISION jobid=%llu",
(unsigned long long)b1926_elem(B1926_ARRAY_SEQ, 5));
TEST_ASSERT_FALSE(repl_dispatch(&sim, line));
TEST_ASSERT_TRUE(b1926_pend_hits(&sim, b1926_elem(B1926_ARRAY_SEQ, 5)));
TEST_ASSERT_FALSE(b1926_pend_hits(&sim, b1926_elem(B1926_ARRAY_SEQ, 4)));
printf("test_decision_breakpoint_jobid_bare_array_id_and_raw: PASS\n");
}
static void test_break_invalid_jobid_rejected(void)
{
static const char* bad[] = {
"bogus", "0-0-0", "0-0-0-104[", "0-0-0-104x", "-5",
"0-0-0-32768[3-1]", /* reversed range */
"0-0-0-104[1]", /* element of an unaligned id */
"99999999999999999999999", /* wider than 64 bits */
"", /* jobid= with no value */
};
size_t i;
for (i = 0; i < sizeof(bad) / sizeof(bad[0]); i++) {
xce_sched_sim_driver_t sim;
char line[128];
char want[96];
char* captured;
b1926_sim_init(&sim);
snprintf(line, sizeof(line), "break DECISION type=PEND jobid=%s",
bad[i]);
captured = capture_dispatch(&sim, line);
TEST_ASSERT_EQUAL_INT_MESSAGE(0, sim.repl_state->nbreakpoints, line);
snprintf(want, sizeof(want), "invalid jobid '%s'", bad[i]);
TEST_ASSERT_NOT_NULL_MESSAGE(strstr(captured, want), captured);
TEST_ASSERT_NULL_MESSAGE(strstr(captured, "breakpoint 1 set"),
captured);
free(captured);
}
/* event kinds validate the value the same way */
{
xce_sched_sim_driver_t sim;
char* captured;
b1926_sim_init(&sim);
captured = capture_dispatch(&sim, "break JOB_STATE_CHANGE jobid=abc");
TEST_ASSERT_EQUAL_INT(0, sim.repl_state->nbreakpoints);
TEST_ASSERT_NOT_NULL(strstr(captured, "invalid jobid 'abc'"));
free(captured);
}
printf("test_break_invalid_jobid_rejected: PASS\n");
}
static void test_event_breakpoint_jobid_dashed_form_matches(void)
{
xce_sched_sim_driver_t sim;
xce_core_event_t ev;
xce_job_t job_a;
xce_job_t job_b;
xce_job_t* jobs[2];
/* JOB_STATE_CHANGE */
b1926_sim_init(&sim);
TEST_ASSERT_FALSE(
repl_dispatch(&sim, "break JOB_STATE_CHANGE jobid=0-0-0-42"));
memset(&ev, 0, sizeof(ev));
ev.type = XCE_CORE_EVENT_JOB_STATE_CHANGE;
ev.u.job_state_change.jobid = 42;
ev.u.job_state_change.new_state = JOB_STAT_RUN;
TEST_ASSERT_NOT_NULL(repl_find_event_breakpoint(sim.repl_state, &ev));
ev.u.job_state_change.jobid = 43;
TEST_ASSERT_NULL(repl_find_event_breakpoint(sim.repl_state, &ev));
/* JOB_UPDATE, an element */
b1926_sim_init(&sim);
TEST_ASSERT_FALSE(
repl_dispatch(&sim, "break JOB_UPDATE jobid=0-0-0-32768[4]"));
memset(&ev, 0, sizeof(ev));
ev.type = XCE_CORE_EVENT_JOB_UPDATE;
ev.u.job_update.jobid = b1926_elem(B1926_ARRAY_SEQ, 4);
TEST_ASSERT_NOT_NULL(repl_find_event_breakpoint(sim.repl_state, &ev));
ev.u.job_update.jobid = b1926_elem(B1926_ARRAY_SEQ, 5);
TEST_ASSERT_NULL(repl_find_event_breakpoint(sim.repl_state, &ev));
/* JOB_RUSAGE, a range */
b1926_sim_init(&sim);
TEST_ASSERT_FALSE(
repl_dispatch(&sim, "break JOB_RUSAGE jobid=0-0-0-32768[0-1]"));
memset(&ev, 0, sizeof(ev));
ev.type = XCE_CORE_EVENT_JOB_RUSAGE;
ev.u.job_rusage.jobid = b1926_elem(B1926_ARRAY_SEQ, 1);
TEST_ASSERT_NOT_NULL(repl_find_event_breakpoint(sim.repl_state, &ev));
ev.u.job_rusage.jobid = b1926_elem(B1926_ARRAY_SEQ, 2);
TEST_ASSERT_NULL(repl_find_event_breakpoint(sim.repl_state, &ev));
/* JOB_PUSH, any pushed job */
b1926_sim_init(&sim);
TEST_ASSERT_FALSE(repl_dispatch(&sim, "break JOB_PUSH jobid=0-0-0-4242"));
memset(&job_a, 0, sizeof(job_a));
memset(&job_b, 0, sizeof(job_b));
job_a.jobid = 7;
job_b.jobid = 4242;
jobs[0] = &job_a;
jobs[1] = &job_b;
memset(&ev, 0, sizeof(ev));
ev.type = XCE_CORE_EVENT_JOBS_PUSH;
ev.u.jobs_push.jobs = jobs;
ev.u.jobs_push.njobs = 2;
TEST_ASSERT_NOT_NULL(repl_find_event_breakpoint(sim.repl_state, &ev));
job_b.jobid = 4243;
TEST_ASSERT_NULL(repl_find_event_breakpoint(sim.repl_state, &ev));
printf("test_event_breakpoint_jobid_dashed_form_matches: PASS\n");
}
/* `info break` and the "set" line echo the filter as typed, not a
* re-rendering of the parsed selector. */
static void test_info_break_echoes_jobid_filter_as_typed(void)
{
xce_sched_sim_driver_t sim;
char* captured;
b1926_sim_init(&sim);
captured = capture_dispatch(&sim,
"break DECISION jobid=0-0-0-32768[1-3]");
TEST_ASSERT_NOT_NULL_MESSAGE(
strstr(captured,
"[sim] breakpoint 1 set: DECISION jobid=0-0-0-32768[1-3]\n"),
captured);
free(captured);
TEST_ASSERT_FALSE(repl_dispatch(&sim, "break DECISION jobid=104"));
captured = capture_dispatch(&sim, "info break");
TEST_ASSERT_NOT_NULL_MESSAGE(strstr(captured, "jobid=0-0-0-32768[1-3]"),
captured);
TEST_ASSERT_NOT_NULL_MESSAGE(strstr(captured, "jobid=104"), captured);
free(captured);
printf("test_info_break_echoes_jobid_filter_as_typed: PASS\n");
}
static void test_begin_round_breakpoint_bare_matches_filtered_does_not(void)
{
xce_sched_sim_driver_t sim;
@ -2306,6 +2596,53 @@ static void test_decision_breakpoint_pauses_after_decision_output(void)
printf("test_decision_breakpoint_pauses_after_decision_output: PASS\n");
}
/*
* bug-1926: a decision breakpoint set with the jobid exactly as the REPL
* prints it (fmt_jobid(), the dashed form) fires in a real
* sched_sim_driver_run(); before the fix only the raw decimal id matched.
* Same fixture and `step 1` script shape as the test above.
*/
static void test_decision_breakpoint_dashed_jobid_pauses_run(void)
{
char replay_path[PATH_MAX];
char aux_dir[PATH_MAX];
char script_path[PATH_MAX];
char capture_path[PATH_MAX];
char script[160];
char bp_marker[112];
char jobid_buf[64];
FILE* f;
char* captured;
f = write_header_and_snapshot(replay_path, sizeof(replay_path),
"_decdash");
TEST_ASSERT_NOT_NULL(f);
append_queue_update_event(f, 1000ULL, "normal");
append_jobs_push_event(f, 1500ULL, 555ULL, "normal");
append_replay_session(f, 2000ULL);
append_end_of_stream_marker(f);
fclose(f);
make_tmp_dir(aux_dir, sizeof(aux_dir), "_decdashaux");
snprintf(script_path, sizeof(script_path), "%s/script.txt", aux_dir);
snprintf(capture_path, sizeof(capture_path), "%s/capture.out", aux_dir);
fmt_jobid(jobid_buf, sizeof(jobid_buf), 555ULL);
snprintf(script, sizeof(script), "break DECISION jobid=%s\nstep 1\nquit\n",
jobid_buf);
write_text_file(script_path, script);
captured = run_group_d_and_capture(replay_path, script_path, capture_path);
snprintf(bp_marker, sizeof(bp_marker),
"hit breakpoint 1 @ decision: PEND jobid=%s", jobid_buf);
TEST_ASSERT_NOT_NULL_MESSAGE(strstr(captured, bp_marker), captured);
free(captured);
printf("test_decision_breakpoint_dashed_jobid_pauses_run: PASS\n");
}
/*
* bug-1595 (task 12.5): `step`/`continue` must echo each input event, in
* the order it is decoded, BEFORE the session's own resulting decision --
@ -2465,6 +2802,12 @@ int main(void)
RUN_TEST(test_event_breakpoint_matches_state_filter);
RUN_TEST(test_decision_breakpoint_matches_type_filter);
RUN_TEST(test_begin_round_breakpoint_bare_matches_filtered_does_not);
RUN_TEST(test_decision_breakpoint_jobid_dashed_form_matches);
RUN_TEST(test_decision_breakpoint_jobid_element_and_range_match);
RUN_TEST(test_decision_breakpoint_jobid_bare_array_id_and_raw);
RUN_TEST(test_break_invalid_jobid_rejected);
RUN_TEST(test_event_breakpoint_jobid_dashed_form_matches);
RUN_TEST(test_info_break_echoes_jobid_filter_as_typed);
RUN_TEST(test_info_job_reports_state_queue_priority);
RUN_TEST(test_info_job_unknown_reports_not_found);
@ -2486,6 +2829,7 @@ int main(void)
RUN_TEST(test_event_breakpoint_pauses_repl_at_matching_event);
RUN_TEST(test_decision_breakpoint_pauses_after_decision_output);
RUN_TEST(test_decision_breakpoint_dashed_jobid_pauses_run);
RUN_TEST(test_step_echoes_events_before_session_decisions);
RUN_TEST(test_replay_load_reports_total_session_count);

View file

@ -26,8 +26,8 @@
* (0-0-0-32768[1] and [2]) and a host-selection mismatch (203 selects
* mem>20000, which only w02 has).
*
* Breakpoint filters compare the raw decimal jobid (repl_break.c
* repl_u64_str_eq), so `break DECISION jobid=105` names 0-0-0-105.
* `break DECISION jobid=105` (a raw packed decimal id) names 0-0-0-105;
* since bug-1926 the dashed form 0-0-0-105 names it too.
*
* Scenarios:
* 1. step mode, all jobs, level full, file copy (baseline replay, five