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 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019jaMf9QEwpugH2W9KAuc7f
This commit is contained in:
parent
31d6606067
commit
38595099b9
2 changed files with 366 additions and 1 deletions
|
|
@ -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 <cmd>`, 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);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue