fix(harness): reset getopt_long via optind=0 to stop cross-command corruption

Every cmd_* REPL handler in test.c/join_worker_args.c reset flag parsing
with `optind = 1;` only. glibc's getopt_long only fully reinitializes its
internal scan state (nextchar/first_nonopt/last_nonopt) when optind==0, or
on the process's very first getopt_long() call ever -- optind=1 alone
leaves that state stale for every call after the first. Since xce_test's
REPL dispatches many independent cmd_*() calls per process, a command
whose parse ends on a short no-argument flag (`qctrl -a`, `hostctrl -o`)
left state that corrupted the NEXT unrelated command's parse badly enough
to misread its own name as an unrecognized flag. This was the actual
cause of harness_unit_test's 4 deterministic (not flaky -- 20/20 identical
repeat runs) test_test_cli.c failures, each burning its full 5s
confirmation-prompt timeout. Fixed by using optind=0 at all 21 reset
sites; confirmed via 3 isolated pty repros plus 10 consecutive clean
harness_unit_test runs (132/132, was 4 failures/run every time).

Also fixes a resource leak found while investigating: test_test_cli.c's
spawned xce_test sessions were only torn down by inline
clitest_kill_and_reap()/clitest_close() calls at the bottom of each test
function, which Unity's longjmp-based TEST_ASSERT abort skips entirely on
failure, leaking the child process and its pty/pipe fds. Added a static
session mirror (safe across the longjmp, unlike a stack pointer) reaped
from harness_test.c's tearDown(), which Unity guarantees runs after every
test regardless of outcome.

Logged as bug-1807/bug-1808 in .wolf/buglog.json, with the underlying
getopt/longjmp gotchas recorded in .wolf/cerebrum.md. Full ASAN
/xce-build harness also passes 5/5 with no sanitizer findings.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RLSNJhNoULvVcUhXEFafQC
This commit is contained in:
Rongsong Shen 2026-09-14 23:16:11 +00:00
parent cbf822fc53
commit 868c6427c8
No known key found for this signature in database
GPG key ID: 5BCDAAE0123E9FE2
9 changed files with 672 additions and 74 deletions

View file

@ -19,10 +19,19 @@ extern void test_listfmt_json(void);
extern void test_xcetest_process_entry(void);
extern void test_test_cli(void);
/* test_test_cli.c's leak-safety net (see its own comment): reaps and
* closes any clitest session left open by a test function that aborted
* via a TEST_ASSERT_* failure partway through, before it reached its own
* clitest_kill_and_reap()+clitest_close() cleanup. A no-op whenever no
* session is currently open (i.e. after every test outside test_test_cli,
* and after every test_test_cli sub-test that ran its own cleanup). */
extern void clitest_reap_leaked_session(void);
void setUp(void) { return; }
void tearDown(void)
{
clitest_reap_leaked_session();
printf("\n-----------------------------------------------------\n");
return;
}