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 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AgYRU3zDTqNGV5Jk8aiKLg
This commit is contained in:
Rongsong Shen 2026-08-26 16:57:09 +00:00
parent d258aa3d91
commit 19bc819204
No known key found for this signature in database
GPG key ID: 5BCDAAE0123E9FE2
2 changed files with 97 additions and 6 deletions

View file

@ -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

View file

@ -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="<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
# ("<name>: 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 "<homedir>|<xdg_config_home>",
# 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