From 19bc819204516aa78f23bcd1255b5ba2f8f61d9d Mon Sep 17 00:00:00 2001 From: Rongsong Shen Date: Wed, 26 Aug 2026 16:57:09 +0000 Subject: [PATCH] test(xce-test-rc-file): task 3.2 end-to-end rc-file failure handling Extends test_rcfile_integration.sh with 2 scenarios proving Requirements 3.1-3.4: a failing rc-file command in piped/scripted mode aborts before any of the piped script's own commands run, exiting nonzero; the same failing command in an interactive session still reports the failure, continues executing the rc file's remaining lines, and reaches the normal prompt -- while still carrying the session's sticky nonzero exit-code verdict (g_session_failed is never cleared, matching how any other failing-command-then-quit interactive session already behaves). Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01AgYRU3zDTqNGV5Jk8aiKLg --- .kiro/specs/xce-test-rc-file/tasks.md | 2 +- .../testsuite/test_rcfile_integration.sh | 101 +++++++++++++++++- 2 files changed, 97 insertions(+), 6 deletions(-) diff --git a/.kiro/specs/xce-test-rc-file/tasks.md b/.kiro/specs/xce-test-rc-file/tasks.md index 9452bf27..5ada180c 100644 --- a/.kiro/specs/xce-test-rc-file/tasks.md +++ b/.kiro/specs/xce-test-rc-file/tasks.md @@ -40,7 +40,7 @@ - _Requirements: 1.1, 1.2, 1.4, 1.5, 2.1, 2.3_ - _Depends: 2.2_ -- [ ] 3.2 Verify rc-file failure handling matches the session's existing abort/continue behavior end-to-end +- [x] 3.2 Verify rc-file failure handling matches the session's existing abort/continue behavior end-to-end - Scenario: a failing rc-file command in scripted/piped mode aborts before any of the piped script's own commands run, and the process exits with a failure status - Scenario: the same failing rc-file command in an interactive session still reaches the normal prompt afterward - Observable: an integration test run demonstrates both scenarios pass against the real built binary diff --git a/src/controller/testsuite/test_rcfile_integration.sh b/src/controller/testsuite/test_rcfile_integration.sh index 895e180e..61c97444 100755 --- a/src/controller/testsuite/test_rcfile_integration.sh +++ b/src/controller/testsuite/test_rcfile_integration.sh @@ -15,12 +15,11 @@ # test_rcfile_takes_effect_interactive() Req 1.1, 1.2, 1.4, 1.5, 2.3 # test_norc_skips_rcfile() Req 2.1, 2.3 # -# Task 3.2 (a LATER task, NOT implemented by this file) is expected to append -# below, reusing the same setup_rc_home/cleanup_rc_home/run_piped/ -# run_interactive_pty helpers and the same pass()/failcase() accounting: -# test_rcfile_failure_aborts_piped() Req 3.1, 3.3, 3.4 +# Task 3.2 scope (implemented here), reusing the same setup_rc_home-style +# fixture pattern/cleanup_rc_home/run_piped/run_interactive_pty helpers and +# the same pass()/failcase() accounting: +# test_rcfile_failure_aborts_piped() Req 3.1, 3.3, 3.4 # test_rcfile_failure_continues_interactive() Req 3.1, 3.2, 3.4 -# and both would be added to the scenario list in main() below. # # Exit 0 == every scenario PASSed; nonzero == at least one scenario FAILed. @@ -38,6 +37,14 @@ XCE_TEST="${XCE_TEST_BIN:-$REPO_ROOT/src/xce.build/controller/xce_test}" OVERRIDE_MASTERS="rchost-a:19991,rchost-b:19992" NOT_SET="" +# A command name guaranteed absent from every xce_test command table (checked +# against every `{ "name", cmd_fn, ... }` entry in test.c) -- used as an rc-file +# line that deterministically fails via execute_line()'s find_command() miss +# (": No such command for xce test." on stderr, execute_line returns -1), +# the simplest reliable way to force a command failure (design.md Testing +# Strategy / task 3.2). +RCFILE_BOGUS_CMD="xce_rcfile_test_bogus_cmd_does_not_exist" + RUN_TIMEOUT_S="${XCE_RCFILE_TEST_TIMEOUT_S:-20}" FAILURES=0 @@ -155,6 +162,26 @@ cleanup_rc_home() { [ -n "$dir" ] && [ -d "$dir" ] && rm -rf "$dir" } +# Creates a scratch $HOME with $HOME/config/xce_test/rc containing a +# deliberately-failing line (RCFILE_BOGUS_CMD, an unrecognized command name) +# FOLLOWED by an OVERRIDE_MASTERS `set masters` line. One fixture serves both +# task 3.2 scenarios: the piped-abort scenario only needs a failing line to be +# present (rcfile_load stops at the first nonzero runner result, so the +# trailing `set masters` line is never reached -- design.md's Architecture +# Pattern & Boundary Map "Stopped -- yes --> Exit" edge); the interactive +# scenario relies on that SAME trailing line still running afterward, to +# prove Requirement 3.2's "continue executing the remaining rc-file commands" +# rather than merely "didn't crash". Echoes "|", +# mirroring setup_rc_home(). +setup_rc_home_failing() { + local dir + dir="$(mktemp -d "${TMPDIR:-/tmp}/xce-rcfile-it-fail.XXXXXX")" || return 1 + mkdir -p "$dir/config/xce_test" || return 1 + printf '%s\nset masters %s\n' "$RCFILE_BOGUS_CMD" "$OVERRIDE_MASTERS" \ + >"$dir/config/xce_test/rc" + printf '%s|%s\n' "$dir" "$dir/config" +} + # Extracts the current-value column of the `masters` line from a `set` # command's output (cmd_set's "%-12s:%s default: %s\n" format). Robust to the # exact column padding and to a value containing an embedded space (the @@ -258,6 +285,68 @@ test_norc_skips_rcfile() { cleanup_rc_home "$homedir" } +# ── Scenarios (task 3.2) ───────────────────────────────────────────────────── + +# A failing rc-file command in scripted/piped mode must abort BEFORE any of +# the piped script's own commands run, exiting nonzero (Req 3.1, 3.3, 3.4). +# The piped script's own `echo PIPED_SCRIPT_MARKER` is the observable that +# proves the read loop never got to read the piped script at all -- not just +# that the run "failed" for some other reason. +test_rcfile_failure_aborts_piped() { + local name="rcfile_failure_aborts_piped" + local fixture homedir xdg out rc + fixture="$(setup_rc_home_failing)" || { failcase "$name" "setup_rc_home_failing failed"; return; } + homedir="${fixture%%|*}"; xdg="${fixture##*|}" + + out="$(run_piped "$homedir" "$xdg" $'echo PIPED_SCRIPT_MARKER\nquit')" + rc=$? + + if [ "$rc" -eq 0 ]; then + failcase "$name" "xce_test exited 0 (expected nonzero -- a failing rc-file command should fail the session, Req 3.4); output:"$'\n'"$out" + elif grep -qF 'PIPED_SCRIPT_MARKER' <<<"$out"; then + failcase "$name" "piped script's own PIPED_SCRIPT_MARKER output was present -- rc-file abort did not happen before the read loop consumed the piped script (Req 3.3); output:"$'\n'"$out" + elif ! grep -qF "$RCFILE_BOGUS_CMD" <<<"$out"; then + failcase "$name" "rc-file command failure was not reported (expected '$RCFILE_BOGUS_CMD: No such command...', Req 3.1); output:"$'\n'"$out" + else + pass "$name" + fi + cleanup_rc_home "$homedir" +} + +# The same failing rc-file command in an interactive (TTY-simulated) session +# must NOT abort: the rc file's remaining lines still run (proving "continue", +# not just "didn't crash" -- Req 3.2) and the session still reaches the normal +# interactive prompt afterward. Per Req 3.4 the same session-verdict/exit-code +# accounting still applies as for any other failing command, so -- exactly as +# for a failing command typed directly at the prompt -- the process's own +# final exit code stays nonzero even though the session itself continued +# normally through to `quit`; a manual mutation check (rc file with no failing +# line) confirms this flips to exit 0, so this is asserting real behavior, not +# vacuously true. +test_rcfile_failure_continues_interactive() { + local name="rcfile_failure_continues_interactive" + local fixture homedir xdg out rc mval + fixture="$(setup_rc_home_failing)" || { failcase "$name" "setup_rc_home_failing failed"; return; } + homedir="${fixture%%|*}"; xdg="${fixture##*|}" + + out="$(run_interactive_pty "$homedir" "$xdg" $'set\nquit')" + rc=$? + mval="$(masters_value_in_output "$out")" + + if [ "$rc" -eq 0 ]; then + failcase "$name" "xce_test exited 0 (expected nonzero -- a failing rc-file command should still fail the session's verdict, Req 3.4); transcript:"$'\n'"$out" + elif ! grep -qF "$RCFILE_BOGUS_CMD" <<<"$out"; then + failcase "$name" "rc-file command failure was not reported (expected '$RCFILE_BOGUS_CMD: No such command...', Req 3.1); transcript:"$'\n'"$out" + elif [ "$mval" != "$OVERRIDE_MASTERS" ]; then + failcase "$name" "expected masters=='$OVERRIDE_MASTERS' -- the rc-file line AFTER the failing one should still have run (Req 3.2), got '$mval'; transcript:"$'\n'"$out" + elif ! grep -qF 'xce>' <<<"$out"; then + failcase "$name" "no 'xce>' interactive prompt seen after the rc-file failure -- session did not reach the normal prompt (Req 3.2); transcript:"$'\n'"$out" + else + pass "$name" + fi + cleanup_rc_home "$homedir" +} + # ── Main ────────────────────────────────────────────────────────────────────── main() { @@ -267,6 +356,8 @@ main() { test_rcfile_takes_effect_piped test_rcfile_takes_effect_interactive test_norc_skips_rcfile + test_rcfile_failure_aborts_piped + test_rcfile_failure_continues_interactive log "---- summary ----" local r