From 0584a6229a8dd916ac9e9defb936ee1081bc470f Mon Sep 17 00:00:00 2001 From: Rongsong Shen Date: Fri, 19 Jun 2026 15:45:09 +0000 Subject: [PATCH] feat(xce-test-e2e-primitives): !cmd shell-escape via system(), feeds $? (task 2.1) --- .kiro/specs/xce-test-e2e-primitives/tasks.md | 2 +- src/controller/testsuite/test.c | 35 ++++++++++++++++++++ 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/.kiro/specs/xce-test-e2e-primitives/tasks.md b/.kiro/specs/xce-test-e2e-primitives/tasks.md index 3e48e48e..bff2d956 100644 --- a/.kiro/specs/xce-test-e2e-primitives/tasks.md +++ b/.kiro/specs/xce-test-e2e-primitives/tasks.md @@ -17,7 +17,7 @@ - _Depends: 1.1_ - [ ] 2. Shell-escape -- [ ] 2.1 `!` runs a shell command and feeds `$?` +- [x] 2.1 `!` runs a shell command and feeds `$?` - In `preprocess_and_run`, when the first non-whitespace char is `!`, run the remainder via `system()`; set `g_last_status = WEXITSTATUS(rc)` (non-zero on `system` failure); a non-zero status is a command failure (subject to 1.1's abort/continue + exit code). - Observable: `!true` succeeds and sets `$?`=0; `!false` fails the session and sets `$?`≠0; a script can `!kill ...` / `!bash start.sh ...` to drive node lifecycle. - _Requirements: 2.1, 2.2, 2.3, 2.4_ diff --git a/src/controller/testsuite/test.c b/src/controller/testsuite/test.c index dcd76178..7bfc5839 100644 --- a/src/controller/testsuite/test.c +++ b/src/controller/testsuite/test.c @@ -433,6 +433,41 @@ int preprocess_and_run(char* line) return 0; } + /* Shell-escape: a line whose first non-whitespace char is `!` runs the + * remainder via system(). The shell command's exit status becomes + * g_last_status (the `$?` auto-variable); a non-zero status is a command + * failure subject to the same abort-vs-continue rules as execute_line. The + * remainder is passed raw to the shell (no parse_argv / auto-var + * substitution — that is task 3.1). (xce-test-e2e-primitives task 2.1) */ + if (*p == '!') { + const char* rest = p + 1; + int sysrc = system(rest); + int status; + + if (sysrc == -1) { + /* system() itself failed to run the shell — treat as failure. */ + status = 127; + } else if (WIFEXITED(sysrc)) { + status = WEXITSTATUS(sysrc); + } else { + /* Signaled / abnormal termination — non-zero failure status. */ + status = 1; + } + + g_last_status = status; + + if (status != 0) { + g_session_failed = 1; + if (!g_continue_on_error) { + return 1; /* abort: stop the read loop */ + } + } else { + add_history(line); + } + + return 0; + } + rc = execute_line(line); g_last_status = (rc < 0) ? (rc) : 0;