From afe22d3122629040b0e4d5a6edd4d01aa158a446 Mon Sep 17 00:00:00 2001 From: Rongsong Shen Date: Thu, 17 Sep 2026 14:27:28 +0000 Subject: [PATCH] feat(scheduler-sim-diagnostics): wire info limit into REPL dispatch repl_cmd_info() now recognizes "limit" as an info subcommand, routing to the already-implemented and tested repl_info_limit() with the same ntok<3 usage guard shape as the queue/worker/resource branches. Usage string and help text updated to list it. repl_info_limit()'s own body is untouched -- this is a pure, additive dispatch-wiring change. Two new tests drive it through the real repl_dispatch() entry point (not a direct call) for both `info limit ` and `info limit all`. `info limit ` is now reachable from a live REPL session -- scheduler-sim-diagnostics' first capability is functionally complete. Remaining: task 5 (determinism/regression validation). Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01RLSNJhNoULvVcUhXEFafQC --- .../specs/scheduler-sim-diagnostics/tasks.md | 2 +- src/scheduler/src/repl.c | 2 + src/scheduler/src/repl_info.c | 26 ++-- .../testsuite/test_repl_limit_table.c | 119 ++++++++++++++++++ 4 files changed, 140 insertions(+), 9 deletions(-) diff --git a/.kiro/specs/scheduler-sim-diagnostics/tasks.md b/.kiro/specs/scheduler-sim-diagnostics/tasks.md index 867bc560..73fa2902 100644 --- a/.kiro/specs/scheduler-sim-diagnostics/tasks.md +++ b/.kiro/specs/scheduler-sim-diagnostics/tasks.md @@ -42,7 +42,7 @@ - Observable: a limit matched against jobs on two distinct hosts shows two separate per-host usage lines each labeled with its own scope values, and a third configured-but-untouched host shows no line at all - _Requirements: 1.4, 2.1, 2.2, 2.3, 3.1, 3.2, 3.4_ -- [ ] 4. Register `info limit` as a recognized `info` subcommand +- [x] 4. Register `info limit` as a recognized `info` subcommand - New `else if (strcmp(tokens[1], "limit") == 0)` branch in `repl_cmd_info()` (`repl_info.c`) routes to `repl_info_limit()`; usage-string literal updated to list `limit` alongside the existing subcommands - `test_repl_limit_table.c` gains a full dispatch-round-trip test (via `repl_dispatch()`, not a direct call) for both `info limit ` and `info limit all` - Observable: `info limit ` and `info limit all` both work when typed as real REPL input (through `repl_dispatch()`), and `help`/usage output lists the new subcommand diff --git a/src/scheduler/src/repl.c b/src/scheduler/src/repl.c index e6c05087..3a21722b 100644 --- a/src/scheduler/src/repl.c +++ b/src/scheduler/src/repl.c @@ -305,6 +305,8 @@ static void repl_print_help(void) "Dump resource(s)."); fprintf(stdout, FIELD_FMT, "info summary", "Quick overview."); fprintf(stdout, FIELD_FMT, "info config", "Show sim parameters."); + fprintf(stdout, FIELD_FMT, "info limit |all", + "Show limit setting(s) and usage."); fprintf(stdout, "\nSnapshot & Parameters:\n"); fprintf(stdout, FIELD_FMT, "snapshot [FILE]", diff --git a/src/scheduler/src/repl_info.c b/src/scheduler/src/repl_info.c index 54133b58..43feed52 100644 --- a/src/scheduler/src/repl_info.c +++ b/src/scheduler/src/repl_info.c @@ -1483,9 +1483,12 @@ static void repl_print_limit_usage(xce_sched_sim_driver_t* sim, * 2.2, Req 1.1/1.2/1.5) -- scheduler-sim-diagnostics spec, Requirement Set * 1. Printing the Req 1.6 effective-host-scope note is task 2.3's job, and * printing live usage instances (Req 2.1-2.3, 3.1-3.4) is task 3's job. - * Not yet wired into repl_cmd_info() (task 4 adds that branch) -- static - * and currently uncalled from production code; exercised directly by - * test_repl_limit_table.c per this task's own scope. + * Wired into repl_cmd_info()'s own `else if (strcmp(tokens[1], "limit") == + * 0)` dispatch branch (task 4, Req 4.1/4.2) -- reachable both as real typed + * REPL input (via repl_dispatch(), test_repl_limit_table.c's own dispatch- + * round-trip tests) and directly (every other test in that file, per task + * 2.1/2.3/3.1/3.2's own pre-task-4 scope). Stays `static`: only ever called + * from within this file, same as every other `repl_info_*()` helper here. * * Requirement 1.7's "no limits configured" message is reserved for the * `all` path specifically -- its own acceptance criterion scopes it to @@ -1792,10 +1795,11 @@ static void repl_info_break(xce_sched_sim_driver_t* sim) * repl_cmd_info - Req 5 AC 22's "info ..." family: dispatches on the second * token to repl_info_break()/repl_info_session()/repl_info_config()/ * repl_info_job()/repl_info_queue()/repl_info_worker()/repl_info_resource()/ - * repl_info_summary() (all above). No `info` subcommand ever hands control - * back to the driver loop -- inspecting state is never a reason to advance - * the simulation. repl.c's repl_dispatch() calls this (repl_internal.h's - * own extern declaration). + * repl_info_summary()/repl_info_limit() (all above; repl_info_limit() wired + * in by task 4, scheduler-sim-diagnostics spec Req 4.1/4.2). No `info` + * subcommand ever hands control back to the driver loop -- inspecting state + * is never a reason to advance the simulation. repl.c's repl_dispatch() + * calls this (repl_internal.h's own extern declaration). */ bool repl_cmd_info(xce_sched_sim_driver_t* sim, const char* line) { @@ -1804,7 +1808,7 @@ bool repl_cmd_info(xce_sched_sim_driver_t* sim, const char* line) if (ntok < 2) { fprintf(stdout, "[sim] info: usage: info [arg]\n"); + "worker|resource|summary|limit> [arg]\n"); return false; } @@ -1842,6 +1846,12 @@ bool repl_cmd_info(xce_sched_sim_driver_t* sim, const char* line) } else { repl_info_resource(sim, tokens[2]); } + } else if (strcmp(tokens[1], "limit") == 0) { + if (ntok < 3) { + fprintf(stdout, "[sim] info limit: usage: info limit |all\n"); + } else { + repl_info_limit(sim, tokens[2]); + } } else { fprintf(stdout, "[sim] info: unrecognized subcommand '%s'\n", tokens[1]); } diff --git a/src/scheduler/testsuite/test_repl_limit_table.c b/src/scheduler/testsuite/test_repl_limit_table.c index 046006c3..562d8d7b 100644 --- a/src/scheduler/testsuite/test_repl_limit_table.c +++ b/src/scheduler/testsuite/test_repl_limit_table.c @@ -272,6 +272,33 @@ static char* capture_info_limit(xce_sched_sim_driver_t* sim, const char* arg) return capture_stdout_around(run_info_limit_for_capture); } +/* + * Captures the output of dispatching one REPL command line THROUGH THE REAL + * repl_dispatch() entry point -- task 4's own dispatch-round-trip + * requirement (unlike capture_info_limit() above, which calls + * repl_info_limit() directly, bypassing repl_cmd_info()'s tokenization and + * dispatch entirely). Same g_sim/g_line/run_for_capture()/capture() shape + * as capture_info_limit() above, and the same pattern + * test_repl_resource_table.c's/test_repl_breakpoints_inspection.c's own + * capture_dispatch() already establish for this file family. Never a + * "step"/"continue"/"quit" line (this helper does not expect + * repl_dispatch() to return true). + */ +static xce_sched_sim_driver_t* g_dispatch_sim; +static const char* g_dispatch_line; + +static void run_dispatch_for_capture(void) +{ + (void)repl_dispatch(g_dispatch_sim, g_dispatch_line); +} + +static char* capture_dispatch(xce_sched_sim_driver_t* sim, const char* line) +{ + g_dispatch_sim = sim; + g_dispatch_line = line; + return capture_stdout_around(run_dispatch_for_capture); +} + /* ---------------------------------------------------------------------- */ /* Limit configuration fixture helpers */ /* ---------------------------------------------------------------------- */ @@ -1492,6 +1519,94 @@ test_info_limit_named_shows_no_active_usage_instance_when_host_scoped_vres_exist "scoped_vres_exists_but_no_worker_populated: PASS\n"); } +/* + * Task 4 (Requirement Set 1, Req 4.1/4.2): the first tests in this file to + * drive `info limit `/`info limit all` through the REAL repl_dispatch() + * entry point (capture_dispatch(), above) instead of calling + * repl_info_limit() directly (capture_info_limit(), used by every test + * above this point) -- proving the `else if (strcmp(tokens[1], "limit") == + * 0)` dispatch branch this task adds to repl_cmd_info() (repl_info.c) + * actually routes real typed-in REPL input to repl_info_limit(), not just + * that repl_info_limit() itself works in isolation (already proven by every + * test above). Before this task's GREEN phase, repl_cmd_info() has no + * "limit" branch at all, so `tokens[1] == "limit"` falls into its own + * unrecognized-subcommand else branch ("[sim] info: unrecognized subcommand + * 'limit'") -- both assertions below are expected to fail at RED. + */ +static void test_info_limit_dispatch_named_reaches_repl_info_limit_via_real_dispatch( + void) +{ + xce_sched_sim_driver_t sim; + xce_sched_plugin_t plugin; + xce_scheduler_t group_sched; + char* captured; + int rc; + + memset(&sim, 0, sizeof(sim)); + rc = xce_sched_core_init(&(sim.core)); + TEST_ASSERT_EQUAL_INT(0, rc); + + register_limit_plugin(&sim, &plugin, &group_sched); + add_configured_limit(&plugin, "ug1_slots"); + + captured = capture_dispatch(&sim, "info limit ug1_slots"); + + TEST_ASSERT_NULL_MESSAGE( + strstr(captured, "unrecognized subcommand"), + "Req 4.1: 'info limit ' typed as real REPL input must be " + "recognized -- repl_cmd_info() must route it to repl_info_limit(), " + "not fall through to the unrecognized-subcommand branch"); + TEST_ASSERT_NOT_NULL_MESSAGE( + strstr(captured, "limit: ug1_slots"), + "Req 4.1: dispatching 'info limit ug1_slots' through repl_dispatch() " + "must produce the same fmt_limit() dump a direct repl_info_limit() " + "call already produces for this fixture"); + + free(captured); + + printf("test_info_limit_dispatch_named_reaches_repl_info_limit_via_real_" + "dispatch: PASS\n"); +} + +static void +test_info_limit_dispatch_all_reaches_repl_info_limit_via_real_dispatch(void) +{ + xce_sched_sim_driver_t sim; + xce_sched_plugin_t plugin; + xce_scheduler_t group_sched; + char* captured; + int rc; + + memset(&sim, 0, sizeof(sim)); + rc = xce_sched_core_init(&(sim.core)); + TEST_ASSERT_EQUAL_INT(0, rc); + + register_limit_plugin(&sim, &plugin, &group_sched); + add_configured_limit(&plugin, "limitA"); + add_configured_limit(&plugin, "limitB"); + + captured = capture_dispatch(&sim, "info limit all"); + + TEST_ASSERT_NULL_MESSAGE( + strstr(captured, "unrecognized subcommand"), + "Req 4.2: 'info limit all' typed as real REPL input must be " + "recognized -- repl_cmd_info() must route it to repl_info_limit(), " + "not fall through to the unrecognized-subcommand branch"); + TEST_ASSERT_NOT_NULL_MESSAGE( + strstr(captured, "limit: limitA"), + "Req 4.2: dispatching 'info limit all' through repl_dispatch() must " + "display every configured limit's setting, starting with limitA"); + TEST_ASSERT_NOT_NULL_MESSAGE( + strstr(captured, "limit: limitB"), + "Req 4.2: ... and limitB too -- every configured limit, not just " + "one"); + + free(captured); + + printf("test_info_limit_dispatch_all_reaches_repl_info_limit_via_real_" + "dispatch: PASS\n"); +} + int main(void) { UNITY_BEGIN(); @@ -1528,5 +1643,9 @@ int main(void) test_info_limit_named_shows_accounted_zero_for_host_with_capacity_but_never_charged); RUN_TEST( test_info_limit_named_shows_no_active_usage_instance_when_host_scoped_vres_exists_but_no_worker_populated); + RUN_TEST( + test_info_limit_dispatch_named_reaches_repl_info_limit_via_real_dispatch); + RUN_TEST( + test_info_limit_dispatch_all_reaches_repl_info_limit_via_real_dispatch); return UNITY_END(); }