From bd77eea0a8aa546ac654df5d9d145a5d9a28b506 Mon Sep 17 00:00:00 2001 From: Rongsong Shen Date: Wed, 2 Sep 2026 20:01:06 +0000 Subject: [PATCH] test(scheduler-sim-repl-verbosity): task 9.1 - RED tests for help/break discoverability Adds failing tests for Requirement 7 (Discoverable Help and Breakpoint Event Types): help's four labeled sections in correct order, one consistent column width across every listed command, info config's placement alongside its info siblings, set units's placement alongside set speed/output/verify, the break line's complete BP_KIND_TABLE-derived event-type listing, and break's own unrecognized-event-type error listing the same complete set. Each new test hardcodes BP_KIND_TABLE's current 12 tokens rather than referencing the not-yet-implemented repl_bp_kind_names() accessor, so RED comes from genuine assertion failures against today's behavior rather than a link error. New tests for Requirements 7.1-7.4 land in a new test_repl_help.c rather than test_repl_execution_control.c (949 lines going in, within reach of this spec's own ~1000-line split trigger already flagged at task 2.1) - proactive split per this spec's established practice, documented in design.md. break's Requirement 7.5 error-message test lands in test_repl_breakpoints_inspection.c exactly as originally planned, alongside the pre-existing test_break_unrecognized_event_type_rejected (left unmodified). Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01YAWMhfRJFnmmnG8h97yUDd --- .../scheduler-sim-repl-verbosity/design.md | 3 + .../scheduler-sim-repl-verbosity/tasks.md | 2 +- src/scheduler/testsuite/meson.build | 36 ++ .../test_repl_breakpoints_inspection.c | 62 ++ src/scheduler/testsuite/test_repl_help.c | 555 ++++++++++++++++++ 5 files changed, 657 insertions(+), 1 deletion(-) create mode 100644 src/scheduler/testsuite/test_repl_help.c diff --git a/.kiro/specs/scheduler-sim-repl-verbosity/design.md b/.kiro/specs/scheduler-sim-repl-verbosity/design.md index a9ff9ba6..a3c70313 100644 --- a/.kiro/specs/scheduler-sim-repl-verbosity/design.md +++ b/.kiro/specs/scheduler-sim-repl-verbosity/design.md @@ -131,6 +131,9 @@ flowchart TD - `src/scheduler/testsuite/test_repl_resource_table.c` (new, per the Correction above) — `info worker`'s/`info resource`'s resource-listing table/grouping layout, `units_mode`-aware rendering, and `info worker`'s assigned-job-listing jobid-range-compression adoption (Requirements 6.4, 6.5, 6.6, 4.5). - `src/scheduler/testsuite/test_repl_snapshot_params.c` — extended with `set units raw|human|both` parsing/validation tests (Requirement 6.2, 6.3), alongside its existing `set speed`/`set output`/`set verify` tests. +**Correction (post-review, task 9.1)**: `help`-listing structure tests (Requirement 7.1-7.4) do NOT land in `test_repl_execution_control.c` as this section originally directed (line 123 above). That file was already at 949 lines going into task 9.1 — within reach of the ~1000-line split trigger this same Correction note (task 2.1) told this task to watch for — and the new section/column/event-type-listing tests would have pushed it to ~1500 lines. They instead live in a new `src/scheduler/testsuite/test_repl_help.c` (555 lines), duplicating pre-existing fixture helpers (`capture_stdout_around`) per this file family's established convention, decided proactively before landing rather than after a review round — the same practice tasks 2.1, 3.1, and 8.1 already established. `test_repl_execution_control.c` remains untouched by task 9.1 and stays the designated home only for the Requirement 5 no-regression checks and the prompt-content tests (Requirement 5.5) it already carries. `break`'s own enriched unrecognized-event-type error test (Requirement 7.5) landed in `test_repl_breakpoints_inspection.c` exactly as line 128 above already specified — no correction needed there. +- `src/scheduler/testsuite/test_repl_help.c` (new, per the Correction above) — `help`'s four labeled sections in order, one consistent column width across all listed commands, `info config`'s placement in State Inspection, `set units`'s placement in Snapshot & Parameters, and the `break` line's complete `BP_KIND_TABLE`-derived event-type listing (Requirements 7.1, 7.2, 7.3, 7.4). + ## System Flows See the Architecture Pattern & Boundary Map diagram above — it is the only non-trivial flow this feature introduces (a per-round branching decision with two exit points: silent extension, or flush-and-restart). Per-event classification (Requirement 1) is a simple, non-branching gate and does not warrant a separate diagram. The three later-added components (REPL Prompt Context, Resource Value Formatter, Help & Breakpoint Discoverability) are likewise simple, single-purpose lookups/formatters with no multi-step state machine of their own — a name-list check, an adaptive-unit loop, and a table lookup, respectively — so none of them warrant a separate diagram either. diff --git a/.kiro/specs/scheduler-sim-repl-verbosity/tasks.md b/.kiro/specs/scheduler-sim-repl-verbosity/tasks.md index 7bc9ca37..619de266 100644 --- a/.kiro/specs/scheduler-sim-repl-verbosity/tasks.md +++ b/.kiro/specs/scheduler-sim-repl-verbosity/tasks.md @@ -98,7 +98,7 @@ - _Boundary: Resource Value Formatter, Jobid Range Compressor_ - [ ] 9. (P) Discoverable `help` listing and `break` event types — Depends: 7.2 -- [ ] 9.1 (P) Write failing tests for the reorganized `help` listing and `break`'s event-type discoverability +- [x] 9.1 (P) Write failing tests for the reorganized `help` listing and `break`'s event-type discoverability - Cover: `help`'s output groups commands into the four labeled sections (execution control, breakpoints, state inspection, snapshot and parameters), uses one consistent column width throughout, lists `info config` alongside the other `info` subcommands, and lists `set units` alongside `set speed`/`set output`/`set verify` in the snapshot-and-parameters section; `help`'s `break` line and `break`'s unrecognized-event-type error both list the complete, current set of valid event types from the same source; no command's existence, argument parsing, or other output changes - Observable: the new test cases fail against today's unordered, hand-padded `help` listing (with `info config` disconnected from its siblings) and `break`'s error message, which names no valid alternatives - _Requirements: 7.1, 7.2, 7.3, 7.4, 7.5, 7.6_ diff --git a/src/scheduler/testsuite/meson.build b/src/scheduler/testsuite/meson.build index 644caef8..4834a7ef 100644 --- a/src/scheduler/testsuite/meson.build +++ b/src/scheduler/testsuite/meson.build @@ -1067,6 +1067,39 @@ sched_repl_resource_table_test = executable( dependencies: [scheduler_test_deps, libedit_dep], install: false) +# Contract test for the interactive simulation REPL's Help & Breakpoint +# Discoverability component (scheduler-sim-repl-verbosity spec, task 9.1, +# Requirements 7.1-7.4): `help`'s listing groups commands into four labeled +# sections in a fixed order, aligns every command's description to one +# consistent column position throughout, lists `info config` together with +# its `info` siblings in the state-inspection section, lists `set units` +# alongside `set speed`/`set output`/`set verify` in the snapshot-and- +# parameters section, and enumerates the complete BP_KIND_TABLE event-type +# set on the `break` line. Same white-box #include pattern as +# sched_repl_resource_table_test above (core.c, core_clock.c, core_events.c, +# core_session.c, decision.c, plugin.c, plugins/builtin_fcfs.c, recording.c, +# recording_session.c, sim.c, sim_loop.c, sim_record.c, sim_verify.c, +# snapshot.c/snapshot_wire.c/snapshot_load.c, repl.c, repl_fmt.c, +# repl_rollup.c, repl_break.c, repl_info.c, repl_params.c) even though this +# file's own tests call only repl_print_help() directly -- no +# xce_sched_sim_driver_t/replay fixture needed. Split into its own file +# rather than appended to test_repl_execution_control.c -- that file was +# already 949 lines (pre-existing, over this project's 800-line cap) before +# this task began; landing this task's own section/column/discoverability +# tests there would have pushed it well past the ~1000-line split trigger +# this exact file family already established (the same reasoning that split +# test_repl_round_rollup.c/test_repl_event_echo.c/test_repl_resource_table.c +# out of their own siblings). `break`'s own unrecognized-event-type error +# message test (Requirement 7.5) lives in +# test_repl_breakpoints_inspection.c instead, per design.md's own file- +# placement guidance for this component. +sched_repl_help_test = executable( + 'sched_repl_help_test', + ['test_repl_help.c'], + include_directories:[incdir], + dependencies: [scheduler_test_deps, libedit_dep], + install: false) + # Contract test for the interactive simulation REPL's snapshot-capture and # simulation-parameter command slice (scheduler-core spec, task 12.3, Req 5 # AC 22 "Snapshots"/"Simulation parameters"): `snapshot [FILE]` writes a @@ -1542,6 +1575,9 @@ test('scheduler repl breakpoints inspection test', test('scheduler repl resource table test', sched_repl_resource_table_test, suite: 'scheduler', env: ['ASAN_OPTIONS=detect_leaks=0']) +test('scheduler repl help test', + sched_repl_help_test, suite: 'scheduler', + env: ['ASAN_OPTIONS=detect_leaks=0']) test('scheduler repl snapshot params test', sched_repl_snapshot_params_test, suite: 'scheduler', env: ['ASAN_OPTIONS=detect_leaks=0']) diff --git a/src/scheduler/testsuite/test_repl_breakpoints_inspection.c b/src/scheduler/testsuite/test_repl_breakpoints_inspection.c index dd1534da..89e1009f 100644 --- a/src/scheduler/testsuite/test_repl_breakpoints_inspection.c +++ b/src/scheduler/testsuite/test_repl_breakpoints_inspection.c @@ -351,6 +351,66 @@ static void test_break_unrecognized_event_type_rejected(void) printf("test_break_unrecognized_event_type_rejected: PASS\n"); } +/* BP_KIND_TABLE's complete token set (repl_break.c), duplicated here in + * table order -- scheduler-sim-repl-verbosity spec, task 9.1, Requirement + * 7.5: `break`'s unrecognized-event-type error message must list the + * complete, current set of valid event types, from the same source + * (BP_KIND_TABLE, via task 9.2's not-yet-existing repl_bp_kind_names() + * accessor) that help's own `break` line (test_repl_help.c) lists. Kept as + * a plain literal list here rather than a call into that accessor + * deliberately: this is a RED-phase test written before task 9.2 adds it -- + * referencing it now would make this file fail to LINK rather than fail an + * assertion, the wrong kind of RED failure for this task. */ +static const char* BP_KIND_TOKENS_9_1[] = { + "CONFIG_PUSH", "JOB_PUSH", "WORKERS_PUSH", + "RESOURCES_PUSH", "RESVALS_FULL", "RESVALS_DELTA", + "JOB_STATE_CHANGE", "JOB_UPDATE", "QUEUE_UPDATE", + "JOB_RUSAGE", "BEGIN_ROUND", "DECISION", +}; +#define NUM_BP_KIND_TOKENS_9_1 \ + (sizeof(BP_KIND_TOKENS_9_1) / sizeof(BP_KIND_TOKENS_9_1[0])) + +static void +test_break_unrecognized_event_type_error_lists_all_valid_event_types(void) +{ + xce_sched_sim_driver_t sim; + char* captured; + size_t i; + + memset(&sim, 0, sizeof(sim)); + /* Same precondition every Group A test in this file establishes -- see + * test_break_creates_breakpoint_with_filters()'s own comment above. */ + 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); + + captured = capture_dispatch(&sim, "break BOGUS_EVENT_TYPE"); + + TEST_ASSERT_NOT_NULL_MESSAGE( + strstr(captured, "unrecognized event type"), + "the existing 'unrecognized event type' error text must still " + "appear (Requirement 7.6 -- no other command output changes)"); + + for (i = 0; i < NUM_BP_KIND_TOKENS_9_1; i++) { + char msg[96]; + + snprintf(msg, sizeof(msg), + "break's unrecognized-event-type error must list '%s' " + "among the complete set of valid event types (Requirement " + "7.5)", + BP_KIND_TOKENS_9_1[i]); + TEST_ASSERT_NOT_NULL_MESSAGE(strstr(captured, BP_KIND_TOKENS_9_1[i]), + msg); + } + + TEST_ASSERT_EQUAL_INT(0, sim.repl_state->nbreakpoints); + + free(captured); + + printf("test_break_unrecognized_event_type_error_lists_all_valid_" + "event_types: PASS\n"); +} + static void test_break_malformed_filter_rejected(void) { xce_sched_sim_driver_t sim; @@ -2059,6 +2119,8 @@ int main(void) RUN_TEST(test_break_creates_breakpoint_with_filters); RUN_TEST(test_break_unrecognized_event_type_rejected); + RUN_TEST( + test_break_unrecognized_event_type_error_lists_all_valid_event_types); RUN_TEST(test_break_malformed_filter_rejected); RUN_TEST(test_delete_by_id_removes_one_breakpoint); RUN_TEST(test_delete_all_removes_all_breakpoints); diff --git a/src/scheduler/testsuite/test_repl_help.c b/src/scheduler/testsuite/test_repl_help.c new file mode 100644 index 00000000..70ec3059 --- /dev/null +++ b/src/scheduler/testsuite/test_repl_help.c @@ -0,0 +1,555 @@ +/* + * test_repl_help.c - contract test for the interactive simulation REPL's + * Help & Breakpoint Discoverability component (scheduler-sim-repl-verbosity + * spec, task 9.1, Requirements 7.1-7.4): `help`'s listing groups commands + * into four labeled sections, aligns every command's description to one + * consistent column position throughout (not independently hand-padded per + * line), lists `info config` together with the other `info` subcommands in + * the state-inspection section, lists `set units` alongside `set speed`/ + * `set output`/`set verify` in the snapshot-and-parameters section, and + * enumerates the complete set of valid `break ` values on its + * `break` line. `break`'s own unrecognized-event-type error message + * (Requirement 7.5) is NOT covered here -- that lives in the sibling + * test_repl_breakpoints_inspection.c per design.md's own file-placement + * guidance for this component. + * + * Split into its own file rather than appended to + * test_repl_execution_control.c -- see design.md's own + * "**Correction (post-review, task 2.1)**" note (that file was already 949 + * lines, over this project's 800-line cap, before this task's own tests + * would have been added) and the analogous + * "**Correction (post-review, task 3.1)**"/"**Correction (post-review, task + * 8.1)**" notes that already split test_repl_event_echo.c/ + * test_repl_resource_table.c out of related files for the identical reason. + * This file duplicates (rather than shares via a new header) the specific + * small fixture helpers it needs, following this file family's established + * convention -- same pattern test_repl_event_echo.c's own header comment + * documents. + * + * Every test below drives `repl_print_help()` (repl.c, `static void`, + * signature `void(void)`) DIRECTLY, no `xce_sched_sim_driver_t`/replay + * fixture required -- `help` prints a fixed listing with no dependency on + * simulation state, so the usual capnp-fixture/fork/waitpid machinery this + * file family's other siblings need is unnecessary here. The white-box + * #include set below is still the SAME full set those siblings use, because + * repl.c itself (pulled in whole) transitively references symbols defined + * across core.c/sim.c/sim_loop.c/repl_break.c/repl_info.c/repl_params.c/etc. + * at link time, even though this file's own tests call none of them. + * + * THIS TASK (9.1) IS RED-PHASE ONLY: `repl_print_help()` (repl.c) still + * prints one flat, hand-padded, unlabeled list today, with `info config` + * appended last (disconnected from its `info` siblings) and no `break` + * event-type listing at all -- see repl.c's current lines 248-273. Every + * test below therefore fails its own runtime assertions against today's + * code (missing section headers, inconsistent column alignment, `info + * config` outside the state-inspection bounds, and no BP_KIND_TABLE tokens + * on the `break` line), while still compiling and linking cleanly, since + * every symbol referenced already exists today. + * + * Assumed literal section-header text this file pins (design.md's own + * "Help & Breakpoint Discoverability" component, Responsibilities & + * Constraints): "Execution Control", "Breakpoints", "State Inspection", + * "Snapshot & Parameters" -- the exact four labels design.md itself names + * for task 9.2 to implement against. Per-command usage/argument text (e.g. + * "info job ", "break [key=value ...]") is assumed + * unchanged from today's repl.c (Requirement 7.6: no command's argument + * parsing changes) -- only used for the NEW "set units" line is exact + * argument syntax deliberately NOT pinned (existence and section placement + * only), since task 9.1 predates task 9.2's implementation choice of its + * exact wording. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "controller.capnp.h" +#include "kvmgr.h" +#include "scheduler.h" +#include "xstring.h" + +/* Pull in the real functions under test into this TU (white-box #include, + * same convention as test_repl_execution_control.c/test_repl_event_echo.c/ + * test_repl_resource_table.c). */ +#include "../src/core.c" +#include "../src/core_clock.c" +#include "../src/core_events.c" +#include "../src/core_session.c" +#include "../src/decision.c" +#include "../src/plugin.c" +#include "../src/plugins/builtin_fcfs.c" +#include "../src/recording.c" +#include "../src/recording_session.c" +#include "../src/repl.c" +#include "../src/repl_fmt.c" +#include "../src/repl_rollup.c" +#include "../src/repl_break.c" +#include "../src/repl_info.c" +#include "../src/repl_params.c" +#include "../src/sim.c" +#include "../src/sim_loop.c" +#include "../src/sim_picker.c" /* bug-1567: sched_sim_driver_run() now calls sim_bootstrap_picker() (sim_loop.c-cap sibling) as an extern -- undefined reference at link time otherwise */ +#include "../src/sim_record.c" +#include "../src/sim_verify.c" +#include "../src/snapshot.c" +#include "../src/snapshot_load.c" +#include "../src/snapshot_wire.c" + +void kill_sched_job(void* arg) { (void)arg; } +void kill_sched_queue(void* arg) { (void)arg; } +void kill_sched_worker(void* arg) { (void)arg; } +void kill_sched_user(void* arg) { (void)arg; } +void kill_sched_project(void* arg) { (void)arg; } +void kill_sched_resval(xce_val_t* val) { (void)val; } + +/* resource-accounting.md's reconciliation (resource.c, not linked here -- + * this file only #includes core_events.c). core_events.c's + * XCE_CORE_EVENT_JOB_RUSAGE handler calls this whenever job->accounts != + * NULL; link-satisfying stub only, same rationale as every other stub in + * this block (see test_job_update.c's identical stub). */ +int sched_reconcile_job_resource_usage(xce_sched_core_t* c, + xce_job_account_t* a, + xce_job_rusages_t* r) +{ + (void)c; (void)a; (void)r; return 0; +} + +void sched_worker_release_host_ctx(xce_sched_worker_t* w) { (void)w; } + +int sched_fill_limit_requests(xce_scheduler_t* sched, xvec_idval_t* vec, + xlist_t* limits) +{ + (void)sched; + (void)vec; + (void)limits; + return 0; +} + +/* sched_sim_driver_run()'s (sim_loop.c, scheduler-sim-decision-parity task + * 2.1) new round-boundary call to the relocated sched_account_jobs() + * (core.h) -- a no-op stand-in here since this file does not link + * scheduler.c/resource.c, same rationale test_sim_equivalence.c's own copy + * documents. Never actually invoked by this file's own tests (they call + * repl_print_help() only), but required for the TU to link -- sim_loop.c + * (pulled in whole by the white-box #include set above) references it. */ +int sched_account_jobs(xce_sched_core_t* core) +{ + (void)core; + return 0; +} + +/* Real reimplementation of schedobj.c's sched_resolve_job_picker -- same + * copy test_sim_alt_events.c/test_sim_verify.c/test_repl_execution_control.c/ + * test_repl_event_echo.c each carry (schedobj.c is not linked here: it would + * need plugin_register(), which requires a genuine xce_scheduler_t* neither + * this file's tests nor sim_loop.c's own core-scoped registration path have, + * Req 5 AC 17). Never actually invoked by this file's own tests -- link-time + * requirement only, same rationale as sched_account_jobs() above. */ +int sched_resolve_job_picker(xce_sched_core_t* core, xce_sched_plugin_t** out) +{ + xce_sched_plugin_t* plugin = NULL; + int rc = 0; + + if ((core == NULL) || (out == NULL)) { + return -EINVAL; + } + + *out = NULL; + + if (core->job_pick_plugin == NULL) { + return -EINVAL; + } + + lock(core->plugins, RTTI_LOCK_READ); + rc = key_find(core->plugins, core->job_pick_plugin, (void**)&plugin); + unlock(core->plugins); + + if (rc <= 0) { + return (rc < 0) ? rc : -ENOENT; + } + + if ((plugin == NULL) || (plugin->picker_iface == NULL) + || (plugin->picker_iface->next_job == NULL) + || (plugin->picker_iface->begin_session == NULL) + || (plugin->picker_iface->end_session == NULL) + || (plugin->picker_iface->on_dispatch == NULL) + || (plugin->picker_iface->on_pend == NULL)) { + return -EINVAL; + } + + *out = plugin; + + return 0; +} + +void setUp(void) { return; } +void tearDown(void) { return; } + +/* ---------------------------------------------------------------------- */ +/* Fixture helpers -- duplicated from test_repl_execution_control.c/ */ +/* test_repl_event_echo.c per this file family's established split */ +/* convention (see header comment above): copied, not shared via a new */ +/* header. */ +/* ---------------------------------------------------------------------- */ + +/* Same dup()/dup2()-around-STDOUT_FILENO capture technique + * test_repl_execution_control.c's own capture_stdout_around() establishes. */ +static char* capture_stdout_around(void (*fn)(void)) +{ + char tmp[] = "/tmp/xce_replhelp_stdoutcap_XXXXXX"; + int fd = mkstemp(tmp); + int saved_stdout; + off_t len; + char* buf; + + TEST_ASSERT_TRUE_MESSAGE(fd >= 0, "mkstemp for stdout capture must " + "succeed"); + + fflush(stdout); + saved_stdout = dup(STDOUT_FILENO); + TEST_ASSERT_TRUE_MESSAGE(saved_stdout >= 0, "dup(STDOUT_FILENO) must " + "succeed"); + TEST_ASSERT_TRUE_MESSAGE(dup2(fd, STDOUT_FILENO) >= 0, + "dup2 redirect of STDOUT_FILENO must succeed"); + + fn(); + + fflush(stdout); + dup2(saved_stdout, STDOUT_FILENO); + close(saved_stdout); + + len = lseek(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(fd, 0, SEEK_SET) == 0); + if (len > 0) { + ssize_t nread = read(fd, buf, (size_t)len); + TEST_ASSERT_TRUE(nread == len); + } + close(fd); + unlink(tmp); + + return buf; +} + +static void run_print_help(void) { repl_print_help(); } + +/* + * description_column_after - locates the FIRST occurrence of `cmd_field` + * that starts a line (i.e. only whitespace, or nothing, precedes it back to + * the start of `text` or the preceding '\n' -- so a coincidental + * mid-description occurrence of the same text is not mistaken for the + * command entry itself), then returns the 0-based column (character offset + * from that line's start) of the first non-whitespace character following + * `cmd_field` -- i.e. where that line's description text begins. Used to + * prove Requirement 7.2's "one consistent column position computed + * consistently" claim: every command line's description must begin at the + * SAME column, regardless of how long that line's own command/argument text + * is. Deliberately does not assume any particular indent width (2 spaces, + * 4 spaces, ...) -- only that indentation is spaces/tabs. + */ +static int description_column_after(const char* text, const char* cmd_field) +{ + size_t flen = strlen(cmd_field); + const char* p = text; + const char* found = NULL; + + while ((p = strstr(p, cmd_field)) != NULL) { + const char* q = p; + + while ((q > text) + && ((*(q - 1) == ' ') || (*(q - 1) == '\t'))) { + q--; + } + if ((q == text) || (*(q - 1) == '\n')) { + found = p; + break; + } + p += flen; + } + + TEST_ASSERT_NOT_NULL_MESSAGE(found, cmd_field); + + { + const char* line_start = found; + const char* d; + + while ((line_start > text) && (*(line_start - 1) != '\n')) { + line_start--; + } + + d = found + flen; + while ((*d == ' ') || (*d == '\t')) { + d++; + } + + return (int)(d - line_start); + } +} + +/* BP_KIND_TABLE's complete token set (repl_break.c), duplicated here in + * table order -- the exact same "same source" list Requirement 7.4/7.5 says + * `help` and `break`'s error message must both fully enumerate. Kept as a + * plain literal list (not a call into a not-yet-existing + * repl_bp_kind_names() accessor) deliberately: this is a RED-phase test + * written before task 9.2 adds that accessor -- referencing it now would + * make this file fail to LINK rather than fail its assertions, which this + * task's own instructions call out as the wrong kind of RED failure. */ +static const char* BP_KIND_TOKENS[] = { + "CONFIG_PUSH", "JOB_PUSH", "WORKERS_PUSH", + "RESOURCES_PUSH", "RESVALS_FULL", "RESVALS_DELTA", + "JOB_STATE_CHANGE", "JOB_UPDATE", "QUEUE_UPDATE", + "JOB_RUSAGE", "BEGIN_ROUND", "DECISION", +}; +#define NUM_BP_KIND_TOKENS \ + (sizeof(BP_KIND_TOKENS) / sizeof(BP_KIND_TOKENS[0])) + +/* ---------------------------------------------------------------------- */ +/* Test 1: `help`'s listing groups commands into four labeled sections, in */ +/* the fixed order design.md's own component description names, with each */ +/* command's line falling within its correct section's bounds -- including */ +/* `info config` landing inside State Inspection next to its `info` */ +/* siblings (Requirement 7.3), and `set units` landing inside Snapshot & */ +/* Parameters next to `set speed`/`set output`/`set verify` (Requirement */ +/* 7.1/7.4's section-organization half). */ +/* ---------------------------------------------------------------------- */ + +static void test_help_groups_commands_into_four_labeled_sections_in_order( + void) +{ + char* captured = capture_stdout_around(run_print_help); + char* exec_hdr; + char* bp_hdr; + char* state_hdr; + char* snap_hdr; + + exec_hdr = strstr(captured, "Execution Control"); + bp_hdr = strstr(captured, "Breakpoints"); + state_hdr = strstr(captured, "State Inspection"); + snap_hdr = strstr(captured, "Snapshot & Parameters"); + + TEST_ASSERT_NOT_NULL_MESSAGE( + exec_hdr, "help must print an 'Execution Control' section label " + "(Requirement 7.1)"); + TEST_ASSERT_NOT_NULL_MESSAGE( + bp_hdr, "help must print a 'Breakpoints' section label " + "(Requirement 7.1)"); + TEST_ASSERT_NOT_NULL_MESSAGE( + state_hdr, "help must print a 'State Inspection' section label " + "(Requirement 7.1)"); + TEST_ASSERT_NOT_NULL_MESSAGE( + snap_hdr, "help must print a 'Snapshot & Parameters' section label " + "(Requirement 7.1)"); + + TEST_ASSERT_TRUE_MESSAGE(exec_hdr < bp_hdr, + "Execution Control must precede Breakpoints"); + TEST_ASSERT_TRUE_MESSAGE(bp_hdr < state_hdr, + "Breakpoints must precede State Inspection"); + TEST_ASSERT_TRUE_MESSAGE(state_hdr < snap_hdr, + "State Inspection must precede Snapshot & " + "Parameters"); + + /* Execution Control: step/continue/quit/help, all before Breakpoints */ + { + const char* fields[] = { "step [N]", "continue", "quit", "help" }; + size_t i; + + for (i = 0; i < (sizeof(fields) / sizeof(fields[0])); i++) { + char* pos = strstr(captured, fields[i]); + + TEST_ASSERT_NOT_NULL_MESSAGE(pos, fields[i]); + TEST_ASSERT_TRUE_MESSAGE( + (exec_hdr < pos) && (pos < bp_hdr), + "every Execution Control command must fall strictly " + "between the Execution Control and Breakpoints labels"); + } + } + + /* Breakpoints: break/info break/delete, all before State Inspection */ + { + const char* fields[] + = { "break ", "info break", "delete |all" }; + size_t i; + + for (i = 0; i < (sizeof(fields) / sizeof(fields[0])); i++) { + char* pos = strstr(captured, fields[i]); + + TEST_ASSERT_NOT_NULL_MESSAGE(pos, fields[i]); + TEST_ASSERT_TRUE_MESSAGE( + (bp_hdr < pos) && (pos < state_hdr), + "every Breakpoints command must fall strictly between the " + "Breakpoints and State Inspection labels"); + } + } + + /* State Inspection: every `info` subcommand, INCLUDING `info config` + * (Requirement 7.3) -- all before Snapshot & Parameters. */ + { + const char* fields[] = { + "info session", "info job ", "info jobs", + "info queue |all", "info worker |all", + "info resource |all", "info summary", "info config", + }; + size_t i; + + for (i = 0; i < (sizeof(fields) / sizeof(fields[0])); i++) { + char* pos = strstr(captured, fields[i]); + + TEST_ASSERT_NOT_NULL_MESSAGE(pos, fields[i]); + TEST_ASSERT_TRUE_MESSAGE( + (state_hdr < pos) && (pos < snap_hdr), + "every `info` subcommand -- including `info config` -- " + "must fall strictly between the State Inspection and " + "Snapshot & Parameters labels (Requirement 7.3)"); + } + } + + /* Snapshot & Parameters: snapshot/set speed/set output/set verify/set + * units, all after Snapshot & Parameters's own label (last section, so + * only a lower bound applies). `set units`'s exact argument spelling is + * deliberately not pinned here -- only its existence and placement. */ + { + const char* fields[] = { + "snapshot [FILE]", "set speed ", "set output |off", + "set verify on|off", "set units", + }; + size_t i; + + for (i = 0; i < (sizeof(fields) / sizeof(fields[0])); i++) { + char* pos = strstr(captured, fields[i]); + + TEST_ASSERT_NOT_NULL_MESSAGE(pos, fields[i]); + TEST_ASSERT_TRUE_MESSAGE( + snap_hdr < pos, + "every snapshot/parameter command -- including `set " + "units` -- must fall at or after the Snapshot & " + "Parameters label (Requirement 7.1/7.4)"); + } + } + + free(captured); + + printf("test_help_groups_commands_into_four_labeled_sections_in_order: " + "PASS\n"); +} + +/* ---------------------------------------------------------------------- */ +/* Test 2: every command's description is aligned to ONE consistent column */ +/* position, computed the same way throughout the whole listing -- not */ +/* independently hand-padded per line (Requirement 7.2). Covers all four */ +/* sections' worth of commands whose usage/argument text is unchanged from */ +/* today's repl.c (Requirement 7.6), so the ONLY reason their computed */ +/* description columns could differ is misalignment, not a wording change. */ +/* ---------------------------------------------------------------------- */ + +static void test_help_uses_one_consistent_column_width_throughout(void) +{ + char* captured = capture_stdout_around(run_print_help); + /* One representative field per command line whose usage/argument text + * is unchanged from today's repl.c (Requirement 7.6) -- spans all four + * sections. `set units` is deliberately excluded (its exact argument + * spelling is a task 9.2 implementation choice, not yet pinned). */ + const char* fields[] = { + "step [N]", + "continue", + "quit", + "help", + "break [key=value ...]", + "info break", + "delete |all", + "info session", + "info job ", + "info jobs", + "info queue |all", + "info worker |all", + "info resource |all", + "info summary", + "info config", + "snapshot [FILE]", + "set speed ", + "set output |off", + "set verify on|off", + }; + size_t n = sizeof(fields) / sizeof(fields[0]); + int col0 = description_column_after(captured, fields[0]); + size_t i; + + for (i = 1; i < n; i++) { + int col = description_column_after(captured, fields[i]); + char msg[160]; + + snprintf(msg, sizeof(msg), + "'%s' description column (%d) must match '%s' description " + "column (%d) -- one consistent column width throughout " + "(Requirement 7.2), not independently hand-padded per line", + fields[i], col, fields[0], col0); + TEST_ASSERT_EQUAL_INT_MESSAGE(col0, col, msg); + } + + free(captured); + + printf("test_help_uses_one_consistent_column_width_throughout: PASS\n"); +} + +/* ---------------------------------------------------------------------- */ +/* Test 3: help's `break` line enumerates the COMPLETE set of valid */ +/* `` values -- every BP_KIND_TABLE token appears between the */ +/* `break` entry and the next command entry (`info break`) (Requirement */ +/* 7.4). */ +/* ---------------------------------------------------------------------- */ + +static void test_help_break_line_lists_the_complete_event_type_set(void) +{ + char* captured = capture_stdout_around(run_print_help); + char* break_pos; + char* info_break_pos; + size_t i; + + break_pos = strstr(captured, "break "); + info_break_pos = strstr(captured, "info break"); + + TEST_ASSERT_NOT_NULL_MESSAGE(break_pos, + "help must still list the `break` command"); + TEST_ASSERT_NOT_NULL_MESSAGE(info_break_pos, + "help must still list `info break`"); + TEST_ASSERT_TRUE_MESSAGE(break_pos < info_break_pos, + "`break`'s entry must precede `info break`'s"); + + for (i = 0; i < NUM_BP_KIND_TOKENS; i++) { + char* tok_pos = strstr(captured, BP_KIND_TOKENS[i]); + + TEST_ASSERT_NOT_NULL_MESSAGE( + tok_pos, BP_KIND_TOKENS[i]); + TEST_ASSERT_TRUE_MESSAGE( + (break_pos < tok_pos) && (tok_pos < info_break_pos), + "every valid break token must appear as part of " + "help's `break` entry, between the `break` and `info break` " + "lines (Requirement 7.4)"); + } + + free(captured); + + printf( + "test_help_break_line_lists_the_complete_event_type_set: PASS\n"); +} + +int main(void) +{ + UNITY_BEGIN(); + + RUN_TEST(test_help_groups_commands_into_four_labeled_sections_in_order); + RUN_TEST(test_help_uses_one_consistent_column_width_throughout); + RUN_TEST(test_help_break_line_lists_the_complete_event_type_set); + + return UNITY_END(); +}