From 38595099b9d7848428aa3d858bab18ae087f90b1 Mon Sep 17 00:00:00 2001 From: Rongsong Shen Date: Thu, 10 Sep 2026 04:36:42 +0000 Subject: [PATCH] test(xce-test-cli-ux): verify help/completion/context-switching hold together (task 4.4) Add one black-box regression test proving process-level help, in-session help content, and context-scoped tab completion work correctly when exercised together in one continuous session (context switches + completion + help lookups), not just individually as each was already covered by tasks 1.6/2.3/3.5. This closes the final task of the xce-test-cli-ux implementation plan (23/23). Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_019jaMf9QEwpugH2W9KAuc7f --- .kiro/specs/xce-test-cli-ux/tasks.md | 3 +- src/controller/testsuite/test_test_cli.c | 364 +++++++++++++++++++++++ 2 files changed, 366 insertions(+), 1 deletion(-) diff --git a/.kiro/specs/xce-test-cli-ux/tasks.md b/.kiro/specs/xce-test-cli-ux/tasks.md index 2fd08df3..3d087117 100644 --- a/.kiro/specs/xce-test-cli-ux/tasks.md +++ b/.kiro/specs/xce-test-cli-ux/tasks.md @@ -172,11 +172,12 @@ - _Depends: 2.4, 3.3, 1.7_ - _Done (requirement 13.7 MET): run twice independently (implementer + reviewer), each against separately-deployed live single-node controllers, sourcing a script file containing a destructive `qctrl -i` line from an otherwise-interactive session. With the confirmation toggle at its default, the prompt appeared on stderr with wording byte-identical to a directly-typed invocation of the same command, and decline reported "qctrl: not run (declined)" -- proving the gate fires for a sourced-file line exactly as it would for a typed one. After `set -y` (issued at the top-level context), re-sourcing the same file produced zero prompt text, and `set +y` afterward restored prompting for a directly-typed invocation, confirming no lingering session-state confusion. The reviewer additionally confirmed this structurally, not just behaviorally: `confirm_destructive()`'s only gating checks are `confirm_is_skip_set()` and `!xcetest_is_interactive()` (a read-only accessor over `g_interactive`, set once at startup from `isatty(STDIN_FILENO)` and never reassigned), and the only per-source-invocation state that exists (`g_source_depth`, used solely for cycle detection) has zero references anywhere near the gate -- there is no code path capable of distinguishing a sourced line from a typed one for this decision. Also verified the bonus interaction flagged by task 3.3's Implementation Note: with `set -e` active, a sourced file's declined destructive line does NOT trip abort-on-failure (a subsequent `echo` line in the same file still ran), confirming decline's `return 0` per `confirm.h`'s contract. No defect found. The already-logged bug-1797 (`cmd_qctrl`'s RPC-error-path queue-name leak) recurred via the `source` call path in both runs -- confirmed to be the identical pre-existing leak, not a new one; occurrence count bumped in buglog.json. No source files were changed; both deploys were fully cleaned up._ -- [ ] 4.4 Verify process-level help, tab completion, and consolidated help content end-to-end +- [x] 4.4 Verify process-level help, tab completion, and consolidated help content end-to-end - Confirm the help/version flags and in-session help behavior from 1.6 and 3.5, and the tab-completion behavior from 2.3, hold together in the full built client, not just in isolation - Observable completion: a black-box run confirms the help flag's output describes the in-session help command, a representative command's help output includes its generated flag block and hand-authored example, and tab completion in two different contexts offers the expected candidates - _Requirements: 4.1, 4.2, 4.3, 7.1, 7.2, 7.3, 7.4, 8.1, 8.2, 8.3, 8.4, 8.5, 8.6, 8.7, 11.2_ - _Depends: 1.6, 2.3, 3.5, 1.7_ + - _Done (requirements 4.1-4.3, 7.1-7.4, 8.1-8.7, 11.2 MET): no live cluster needed (purely client-side). Existing per-mechanism coverage from tasks 1.6/2.3/3.5 was confirmed solid, but no test chained context-switching + tab completion + help lookup together in one continuous session -- a genuine "holds together, not just in isolation" gap. Added one new black-box test, `test_help_completion_and_context_switching_hold_together_in_one_session` (test_test_cli.c), covering: top-level vs. client-context double-Tab on the identical "w" prefix producing different, context-correct candidate sets; an unambiguous Tab-completion executed as a real (safely no-op, pre-RPC-validated) command; a mid-session `help jobkill` showing its purpose description, generated flag block, and hand-authored example; a further switch to worker context; and a `help jobkill` lookup there correctly reported not-found with the corrected grammar, leaking no client-context text. Both implementer and reviewer independently reproduced all three observable-completion criteria via their own standalone driver runs against the real binary, and the reviewer additionally cross-checked the completion candidate sets directly against test.c's four command tables. No defect found; no product code changed, only the one new permanent test (1331/1331 tests, 0 ASAN diagnostics)._ ## Implementation Notes - Audited all 139 `printf(` call sites in test.c; 33 were genuine diagnostic/warning/failure-path messages misrouted to stdout (not covered by the already-correct `flagspec_reject`/`perror` mechanisms) and were converted to `fprintf(stderr, ...)` — concentrated in `cmd_expect_jobs`/`cmd_expect_job`/`cmd_wait_primary` (entirely unrouted; these predate the flagspec migration since they take positional args, not flags), plus `get_setting`, `cmd_help`'s not-found path (a shared `FILE* out` var, stderr only when not-found; bare `help`'s listing and a successful lookup stay stdout), `cmd_ctrlstatus`, `cmd_jobrun`, `cmd_jobsub`, `cmd_jobstart`/`jobfinish`/`jobfail`, `resupdate_report_one`/`cmd_resupdate`, `cmd_set`. Deliberately left as stdout (Requirement 3 doesn't apply): `cmd_resval`'s intentional always-succeeds "not found" design, and the entire async worker/scheduler RPC-handler subsystem (`handle_notify`/`run_job`/etc.) — these are inbound-request callbacks dispatched outside the REPL's `cmd_*` table, not "a command's own diagnostic or result data." Found but explicitly left alone as pre-existing and out of THIS task's scope (stream routing, not return-code correctness): `cmd_resupdate` still unconditionally `return 0`s even on an outright RPC failure or a per-resource rejection — worth a follow-up bug entry if this area is touched again. Requirement 3.4 (redirection-invariance) holds by construction: no code branches a stream CHOICE on `isatty`/`g_interactive` anywhere in the file. (task 3.6) diff --git a/src/controller/testsuite/test_test_cli.c b/src/controller/testsuite/test_test_cli.c index e09a5725..79ef68df 100644 --- a/src/controller/testsuite/test_test_cli.c +++ b/src/controller/testsuite/test_test_cli.c @@ -2852,6 +2852,368 @@ test_help_context_scoped_not_found_no_cross_context_hint(void) clitest_buf_free(&err); } +/* + * test_help_completion_and_context_switching_hold_together_in_one_session -- + * xce-test-cli-ux task 4.4, Requirements 4.1-4.3, 7.1-7.4, 8.1-8.7, 11.2. + * + * Every mechanism this test touches (process-level help/version, in-session + * `help `, tab completion) already has dedicated per-mechanism coverage + * elsewhere in this file (and, for process-level flags, in + * test_xcetest_process_entry.c from task 1.6). What none of those tests do + * is run several of these mechanisms back-to-back in ONE live session, + * interleaved with real context switches -- exactly task 4.4's own + * "hold together ... not just in isolation" framing. This test is that + * missing combined/integration run: + * + * 1. Top-level, ambiguous "w" (ambiguous across worker/waldump/ + * walcompare) is double-Tab listed; the candidate set is confirmed to + * include "worker" (a top-level-only command, not present at all in + * the client context) and to EXCLUDE "workerlist"/"wait-primary" + * (client-only). + * 2. Top-level, unambiguous "cl" -> Tab -> Enter completes to "client" in + * full and switches context (Requirement 7.1/7.2). + * 3. Client context, ambiguous "w" is double-Tab listed; the candidate + * set now includes "workerlist"/"waldump"/"walcompare"/"wait-primary" + * -- a DIFFERENT set than step 1's, for the IDENTICAL typed prefix, in + * the SAME running process (Requirement 7.1/7.4). + * 4. Client context, unambiguous "jobk" -> Tab -> Enter completes to + * "jobkill" and actually executes it bare (no live controller needed: + * cmd_jobkill's own missing-jobid validation returns before ever + * reaching the RPC/confirmation-gate code, exactly like task 2.3's + * "jstart" proof step). + * 5. Still in the client context -- and only NOW, after two context + * switches and two tab-completion demonstrations -- `help jobkill` is + * looked up. Its rendered text is confirmed to include the + * hand-authored purpose description (8.1), the flagspec_render_help() + * -generated "-y, --yes" flag line (8.2), the hand-authored example + * invocation (8.3), and the confirmation-gate/`set -y` scripting note + * (8.4) -- proving prior session state (context switches, completion) + * did not corrupt this content. + * 6. `..` then `worker` switches to a THIRD context (a context this + * session has not yet visited). + * 7. Worker context, ambiguous "j" (jstart/jfinish/jfail) is double-Tab + * listed; the candidate set is confirmed to include jstart/jfinish/ + * jfail and to EXCLUDE every client-only "job*" command (jobkill, + * jobrun, jobsub, jobinfo, jobckpt) -- a second, independent + * different-contexts-different-candidates proof (worker vs. client). + * 8. `help jobkill` in the worker context -- a name valid in the OLD + * (client) context but not this one -- is confirmed reported as not + * found, with the corrected grammar (11.2: "match", not "matchs"), + * never leaking client context's jobkill text into the not-found + * response. + * + * Buffers are freed and reinitialized between stages (the same idiom + * test_help_context_scoped_not_found_no_cross_context_hint above already + * uses across its own context switch) so each stage's assertions search + * only that stage's own freshly captured output, never a stale match left + * over from an earlier stage's text. + * + * Requirement 4.1-4.3 (process-level -h/--help/--version describing the + * in-session `help` command) is deliberately NOT re-exercised here: the + * task's own guidance treats a process-level flag invocation as inherently + * a separate process from an interactive session, and + * test_xcetest_process_entry.c (task 1.6) already covers it end-to-end + * against the real binary. This task's implementer report quotes that + * output directly instead of duplicating the check here. + */ +static void +test_help_completion_and_context_switching_hold_together_in_one_session(void) +{ + char xce_test_path[PATH_MAX]; + char* const argv[] = { "xce_test", "--norc", NULL }; + clitest_session_t sess; + clitest_buf_t out; + clitest_buf_t err; + int exit_code = -999; + const char ctrl_u = '\x15'; /* unix-line-discard */ + const char enter = '\n'; + + TEST_ASSERT_EQUAL_INT_MESSAGE( + 0, resolve_xce_test_path(xce_test_path, sizeof(xce_test_path)), + "must be able to resolve the sibling xce_test binary's path"); + + clitest_buf_init(&out); + clitest_buf_init(&err); + + /* disable_echo=false for the WHOLE session: it needs libedit's live + * completion feedback (steps 1, 3, 7) as well as ordinary command + * output (steps 2, 4, 5, 6, 8) -- task 2.3's own header comment + * confirms full-line sends behave identically either way, and its own + * combined tab-completion test already mixes both under + * disable_echo=false throughout. */ + TEST_ASSERT_EQUAL_INT_MESSAGE( + 0, clitest_spawn_ex(&sess, xce_test_path, argv, false), + "clitest_spawn_ex must launch xce_test behind a real pty with " + "kernel ECHO left on, so libedit's own tab-completion feedback is " + "observable"); + + TEST_ASSERT_TRUE_MESSAGE( + clitest_wait_for_text(&sess, &out, &err, "xce>", false, 5000), + "expected the initial 'xce>' interactive prompt on stdout"); + + /* --- Step 1: top-level, ambiguous "w" -> {worker, waldump, + * walcompare}. "worker" exists ONLY at top level (client has + * "workerlist" instead), so its presence here plus its absence in step + * 3's client-context listing is the clearest possible proof that + * candidates are context-scoped for the SAME typed prefix. */ + clitest_buf_free(&out); + clitest_buf_free(&err); + clitest_buf_init(&out); + clitest_buf_init(&err); + TEST_ASSERT_EQUAL_INT(0, clitest_send_partial(&sess, "w")); + TEST_ASSERT_EQUAL_INT(0, clitest_send_tab(&sess)); + TEST_ASSERT_EQUAL_INT(0, clitest_send_tab(&sess)); + TEST_ASSERT_TRUE_MESSAGE( + clitest_wait_for_text(&sess, &out, &err, "walcompare", false, 3000), + "top-level ambiguous 'w' must list 'walcompare' among the " + "candidates"); + TEST_ASSERT_NOT_NULL_MESSAGE( + strstr(out.data, "worker"), + "top-level ambiguous 'w' must list 'worker' among the candidates " + "(Requirement 7.1/7.3)"); + TEST_ASSERT_NOT_NULL_MESSAGE( + strstr(out.data, "waldump"), + "top-level ambiguous 'w' must list 'waldump' among the " + "candidates"); + TEST_ASSERT_NULL_MESSAGE( + strstr(out.data, "workerlist"), + "top-level ambiguous 'w' must NOT offer 'workerlist' -- that " + "command exists only in the client context (Requirement 7.4)"); + TEST_ASSERT_NULL_MESSAGE( + strstr(out.data, "wait-primary"), + "top-level ambiguous 'w' must NOT offer 'wait-primary' -- that " + "command exists only in the client context (Requirement 7.4)"); + + /* Clear the unaltered "w" input line (Requirement 7.3) and confirm the + * session is still usable by switching context next. */ + TEST_ASSERT_EQUAL_INT(0, clitest_send_raw(&sess, &ctrl_u, 1)); + + /* --- Step 2: top-level, unambiguous "cl" -> Tab -> Enter completes in + * full to "client" and switches context (Requirement 7.1/7.2). */ + clitest_buf_free(&out); + clitest_buf_free(&err); + clitest_buf_init(&out); + clitest_buf_init(&err); + TEST_ASSERT_EQUAL_INT(0, clitest_send_partial(&sess, "cl")); + TEST_ASSERT_EQUAL_INT(0, clitest_send_tab(&sess)); + TEST_ASSERT_EQUAL_INT(0, clitest_send_raw(&sess, &enter, 1)); + TEST_ASSERT_TRUE_MESSAGE( + clitest_wait_for_text(&sess, &out, &err, "xce/client>", false, 5000), + "single unambiguous match 'cl' must complete in full to 'client' " + "and switch context (Requirement 7.1/7.2)"); + + /* --- Step 3: client context, ambiguous "w" -> {workerlist, waldump, + * walcompare, wait-primary} -- a DIFFERENT candidate set than step 1's, + * for the IDENTICAL typed prefix, in the SAME running process. */ + clitest_buf_free(&out); + clitest_buf_free(&err); + clitest_buf_init(&out); + clitest_buf_init(&err); + TEST_ASSERT_EQUAL_INT(0, clitest_send_partial(&sess, "w")); + TEST_ASSERT_EQUAL_INT(0, clitest_send_tab(&sess)); + TEST_ASSERT_EQUAL_INT(0, clitest_send_tab(&sess)); + TEST_ASSERT_TRUE_MESSAGE( + clitest_wait_for_text(&sess, &out, &err, "wait-primary", false, + 3000), + "client-context ambiguous 'w' must list 'wait-primary' among the " + "candidates -- proving the SAME prefix offers a DIFFERENT " + "candidate in this context than at top level (Requirement 7.1/7.4)"); + TEST_ASSERT_NOT_NULL_MESSAGE( + strstr(out.data, "workerlist"), + "client-context ambiguous 'w' must list 'workerlist' (not bare " + "'worker', which is not a client command) among the candidates"); + TEST_ASSERT_NOT_NULL_MESSAGE( + strstr(out.data, "waldump"), + "client-context ambiguous 'w' must also list 'waldump'"); + TEST_ASSERT_NOT_NULL_MESSAGE( + strstr(out.data, "walcompare"), + "client-context ambiguous 'w' must also list 'walcompare'"); + + TEST_ASSERT_EQUAL_INT(0, clitest_send_raw(&sess, &ctrl_u, 1)); + + /* --- Step 4: client context, unambiguous "jobk" -> Tab -> Enter + * completes in full to "jobkill" and actually runs it bare. No live + * controller is needed: cmd_jobkill's own missing-'-j' validation + * returns (to stderr) before ever reaching the RPC/confirmation-gate + * code, exactly like task 2.3's "jstart" proof step above. */ + clitest_buf_free(&out); + clitest_buf_free(&err); + clitest_buf_init(&out); + clitest_buf_init(&err); + TEST_ASSERT_EQUAL_INT(0, clitest_send_partial(&sess, "jobk")); + TEST_ASSERT_EQUAL_INT(0, clitest_send_tab(&sess)); + TEST_ASSERT_EQUAL_INT(0, clitest_send_raw(&sess, &enter, 1)); + TEST_ASSERT_TRUE_MESSAGE( + clitest_wait_for_text(&sess, &out, &err, + "you need to specify jobid with -j", true, + 3000), + "single unambiguous match 'jobk' must complete in full to " + "'jobkill' in the client context (a command absent from both the " + "top-level and worker contexts), proven by hitting cmd_jobkill's " + "own bare-invocation validation message (Requirement 7.1/7.2/7.4)"); + + /* --- Step 5: NOW, mid-session (after two context switches and two + * tab-completion demonstrations), look up jobkill's full help text. + * Requirements 8.1-8.4: purpose description, generated flag block, + * hand-authored example, and confirmation-gate scripting note. */ + clitest_buf_free(&out); + clitest_buf_free(&err); + clitest_buf_init(&out); + clitest_buf_init(&err); + TEST_ASSERT_EQUAL_INT(0, clitest_send_line(&sess, "help jobkill")); + /* Each phrase gets its OWN clitest_wait_for_text() call (not a single + * wait followed by plain strstr) -- cmd_help composes this output from + * several separate prints (description -> generated flagspec_render_ + * help() block -> hand-authored tail), which this harness's poll loop + * can observe arriving across more than one read() chunk. This matches + * test_help_jobkill_shows_generated_flag_and_confirmation_note's own + * established pattern above, not the single-wait-then-strstr shortcut + * used for the double-Tab candidate listings elsewhere in this test + * (those really are one burst -- this multi-part composition is not, + * confirmed by this exact test failing on a bare strstr on first + * write). */ + TEST_ASSERT_TRUE_MESSAGE( + clitest_wait_for_text(&sess, &out, &err, + "Sends a signal to a running job", false, + 5000), + "Requirement 8.1: mid-session 'help jobkill' must still include " + "its purpose description"); + TEST_ASSERT_TRUE_MESSAGE( + clitest_wait_for_text(&sess, &out, &err, "-y, --yes", false, 5000), + "Requirement 8.2: mid-session 'help jobkill' must still show the " + "generated -y/--yes flag line"); + TEST_ASSERT_TRUE_MESSAGE( + clitest_wait_for_text(&sess, &out, &err, "example: jobkill", false, + 5000), + "Requirement 8.3: mid-session 'help jobkill' must still retain " + "its example invocation"); + TEST_ASSERT_TRUE_MESSAGE( + clitest_wait_for_text(&sess, &out, &err, + "destructive action and prompts for " + "confirmation", + false, 5000), + "Requirement 8.4: mid-session 'help jobkill' must still mention " + "the confirmation gate as a related scripting note"); + TEST_ASSERT_TRUE_MESSAGE( + clitest_wait_for_text(&sess, &out, &err, "set -y", false, 5000), + "the confirmation note must still point at `set -y` as the " + "session-wide bypass, even mid-session"); + + /* --- Step 6: switch to a THIRD context this session has not yet + * visited (worker), via '..' then 'worker' as plainly-typed lines. */ + clitest_buf_free(&out); + clitest_buf_free(&err); + clitest_buf_init(&out); + clitest_buf_init(&err); + TEST_ASSERT_EQUAL_INT(0, clitest_send_line(&sess, "..")); + TEST_ASSERT_TRUE_MESSAGE( + clitest_wait_for_text(&sess, &out, &err, "xce>", false, 5000), + "expected to return to the top-level context after '..'"); + TEST_ASSERT_EQUAL_INT(0, clitest_send_line(&sess, "worker")); + TEST_ASSERT_TRUE_MESSAGE( + clitest_wait_for_text(&sess, &out, &err, "xce/worker>", false, 5000), + "expected to enter the worker context"); + + /* --- Step 7: worker context, ambiguous "j" -> {jstart, jfinish, + * jfail}, and NONE of the client-only "job*" commands -- a second, + * independent different-contexts-different-candidates proof. */ + clitest_buf_free(&out); + clitest_buf_free(&err); + clitest_buf_init(&out); + clitest_buf_init(&err); + TEST_ASSERT_EQUAL_INT(0, clitest_send_partial(&sess, "j")); + TEST_ASSERT_EQUAL_INT(0, clitest_send_tab(&sess)); + TEST_ASSERT_EQUAL_INT(0, clitest_send_tab(&sess)); + TEST_ASSERT_TRUE_MESSAGE( + clitest_wait_for_text(&sess, &out, &err, "jfinish", false, 3000), + "worker-context ambiguous 'j' must list 'jfinish' among the " + "candidates"); + TEST_ASSERT_NOT_NULL_MESSAGE(strstr(out.data, "jstart"), + "worker-context ambiguous 'j' must also " + "list 'jstart'"); + TEST_ASSERT_NOT_NULL_MESSAGE(strstr(out.data, "jfail"), + "worker-context ambiguous 'j' must also " + "list 'jfail'"); + TEST_ASSERT_NULL_MESSAGE( + strstr(out.data, "jobkill"), + "worker-context ambiguous 'j' must NOT offer 'jobkill' -- that " + "command exists only in the client context (Requirement 7.4)"); + TEST_ASSERT_NULL_MESSAGE( + strstr(out.data, "jobrun"), + "worker-context ambiguous 'j' must NOT offer 'jobrun' either " + "(client-only)"); + TEST_ASSERT_NULL_MESSAGE( + strstr(out.data, "jobsub"), + "worker-context ambiguous 'j' must NOT offer 'jobsub' either " + "(client-only)"); + TEST_ASSERT_NULL_MESSAGE( + strstr(out.data, "jobinfo"), + "worker-context ambiguous 'j' must NOT offer 'jobinfo' either " + "(client-only)"); + TEST_ASSERT_NULL_MESSAGE( + strstr(out.data, "jobckpt"), + "worker-context ambiguous 'j' must NOT offer 'jobckpt' either " + "(client-only)"); + + TEST_ASSERT_EQUAL_INT(0, clitest_send_raw(&sess, &ctrl_u, 1)); + + /* --- Step 8: 'help jobkill' in the worker context -- a name valid in + * the OLD (client) context but not this one -- must be reported not + * found, with the corrected grammar (Requirement 11.2), never leaking + * client context's jobkill text into the not-found response. This is + * the same not-found behavior test_help_context_scoped_not_found_no_ + * cross_context_hint proves from the top-level context; here it is + * proven from the worker context, mid-session, after everything above + * has already run. */ + clitest_buf_free(&out); + clitest_buf_free(&err); + clitest_buf_init(&out); + clitest_buf_init(&err); + TEST_ASSERT_EQUAL_INT(0, clitest_send_line(&sess, "help jobkill")); + TEST_ASSERT_TRUE_MESSAGE( + clitest_wait_for_text(&sess, &out, &err, + "No commands match `jobkill'", true, 5000), + "Requirement 8.7/11.2: from the worker context (jobkill's OLD " + "context, client, is not the active one), 'help jobkill' must be " + "reported as not found there, with corrected grammar"); + TEST_ASSERT_NULL_MESSAGE( + strstr(err.data, "matchs"), + "the corrected not-found message must not contain the old " + "'matchs' typo anywhere in the captured output"); + TEST_ASSERT_NULL_MESSAGE( + strstr(out.data, "No commands match"), + "the not-found diagnostic must not also appear on stdout"); + TEST_ASSERT_NULL_MESSAGE( + strstr(out.data, "Sends a signal to a running job"), + "the worker-context not-found response must never leak client " + "context's jobkill help content"); + TEST_ASSERT_NULL_MESSAGE( + strstr(err.data, "Sends a signal to a running job"), + "the worker-context not-found response must never leak client " + "context's jobkill help content, on stderr either"); + + TEST_ASSERT_EQUAL_INT(0, clitest_send_line(&sess, "quit")); + TEST_ASSERT_EQUAL_INT_MESSAGE( + 0, clitest_wait_for_exit(&sess, &out, &err, 5000, &exit_code), + "xce_test did not exit within the harness's safety timeout after " + "'quit'"); + /* Step 4's deliberate bare "jobkill" latches g_session_failed for the + * rest of the session (same mechanism, and same deliberate choice of + * proof command, as test_tab_completion_context_scoped_single_and_ + * multi_match above) even though the REPL itself kept running to + * completion through every later step. */ + TEST_ASSERT_EQUAL_INT_MESSAGE( + 1, exit_code, + "xce_test should exit 1 -- the deliberate bare-'jobkill' proof " + "step in step 4 latches g_session_failed even though the REPL " + "itself kept running to completion through every later step"); + + clitest_kill_and_reap(&sess); + clitest_close(&sess); + clitest_buf_free(&out); + clitest_buf_free(&err); +} + /* * test_expect_jobs_malformed_count_diagnostic_on_stderr_not_stdout -- * xce-test-cli-ux task 3.6, Requirement 3.1/3.2/3.3/3.4: `expect-jobs` given @@ -3074,6 +3436,8 @@ void test_test_cli(void) RUN_TEST(test_help_reslist_shows_json_example); RUN_TEST(test_help_unknown_command_shows_corrected_grammar); RUN_TEST(test_help_context_scoped_not_found_no_cross_context_hint); + RUN_TEST( + test_help_completion_and_context_switching_hold_together_in_one_session); RUN_TEST(test_expect_jobs_malformed_count_diagnostic_on_stderr_not_stdout); RUN_TEST(test_wait_primary_invalid_timeout_diagnostic_on_stderr_not_stdout); RUN_TEST(test_set_list_success_result_on_stdout_not_stderr);