feat(xce-harness-component): relocate xce_test into standalone harness component
Move xce_test's 15 production sources + headers from src/controller/testsuite/ into a new src/harness/ component with its own meson.build, and strip every now-dangling controller-side reference (11 controller_unit_test_src entries, 4 standalone test-binary blocks, 11 controller_test.c extern/RUN_TEST lines) so the controller build is never left broken. Fixes a broken relative-symlink bridge (xce_test_controller_link) added to keep two controller-embedded tests (test_test_cli.c, test_xcetest_process_entry.c) finding the relocated xce_test binary until task 2.1 moves them too. Discovered and documented in design.md: four controller-domain regression tests call functions directly from four of xce_test's own files, requiring controller_unit_test to permanently recompile them from src/harness/ — a real dependency neither the original discovery nor design audit caught. controller_unit_test: 1228 Tests 0 Failures 0 Ignored (verified via the Unity binary's own printed summary line, not just meson's aggregate count). Task 1.1 of xce-harness-component. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RLSNJhNoULvVcUhXEFafQC
This commit is contained in:
parent
b8ef82c1b2
commit
cf55d691de
36 changed files with 550 additions and 180 deletions
101
src/harness/include/confirm.h
Normal file
101
src/harness/include/confirm.h
Normal file
|
|
@ -0,0 +1,101 @@
|
|||
#pragma once
|
||||
/*
|
||||
* confirm.h -- destructive-command confirmation gate: prompts the operator
|
||||
* on stderr before a destructive command runs, offers a session-scoped
|
||||
* toggle to skip that prompt, and treats a Ctrl-C during the prompt the
|
||||
* same as declining it (xce-test-cli-ux spec, task 2.1; Requirements
|
||||
* 9.1-9.4, 9.6-9.9. Requirement 9.5 -- the five-command enumeration and
|
||||
* each cmd_*'s own `--yes` bypass flag -- is task 3.3, NOT this module;
|
||||
* this module is the generic, command-agnostic gate mechanism only).
|
||||
*
|
||||
* THE MECHANISM (see design.md's "Confirmation Gate" component and its
|
||||
* "Destructive Command Confirmation Gate" flowchart):
|
||||
* - confirm_destructive() is the gate itself. It internally checks, in
|
||||
* order: the session-scoped skip toggle (confirm_is_skip_set()), then
|
||||
* whether the top-level session is even interactive
|
||||
* (xcetest_is_interactive(), see below). If either says "don't
|
||||
* prompt", it returns 1 (proceed) immediately WITHOUT touching stdin
|
||||
* or printing anything. Otherwise it prints a yes/no prompt to
|
||||
* stderr and performs one blocking read of the answer from stdin.
|
||||
* - confirm_destructive() deliberately has NO parameter for a
|
||||
* per-command `--yes` bypass flag -- that flag is per-INVOCATION, not
|
||||
* session state, so it cannot be folded into confirm_is_skip_set()'s
|
||||
* persistent toggle without leaking into the next command. Task 3.3's
|
||||
* five call sites (cmd_jobkill, cmd_shutdown, cmd_qctrl, cmd_hostctrl,
|
||||
* cmd_upgradeconfig) are responsible for short-circuiting around
|
||||
* confirm_destructive() entirely when their OWN --yes flag was given,
|
||||
* e.g.:
|
||||
* if (!opts.yes && !confirm_destructive("kill job 1234"))
|
||||
* return NOT_RUN;
|
||||
* (design.md's Implementation Notes describes this same three-way
|
||||
* bypass -- toggle, TTY, --yes -- as one boolean expression each call
|
||||
* site evaluates; confirm_destructive() itself owns the first two,
|
||||
* the call site owns the third because only it knows its own flag.)
|
||||
*
|
||||
* RETURN CONTRACT (confirm_destructive): exactly two outcomes, matching
|
||||
* design.md's Service Interface postcondition -- no third "error" case is
|
||||
* defined for this function.
|
||||
* 1 -- proceed: skip-toggle set, OR non-interactive session, OR the
|
||||
* operator answered affirmatively ("y"/"yes", case-insensitive,
|
||||
* surrounding whitespace ignored).
|
||||
* 0 -- not run: the operator declined (anything else, including a bare
|
||||
* Enter or unrecognized text), OR stdin hit EOF, OR the prompt's
|
||||
* blocking read was interrupted by a real Ctrl-C/SIGINT (EINTR;
|
||||
* Requirement 9.9 -- treated identically to a decline, never
|
||||
* retried). The caller must report this as "not run", distinct
|
||||
* from "failed" (Requirement 9.2/9.9).
|
||||
*
|
||||
* WHY THE PROMPT READ IS A PLAIN fgets(), NOT libedit's readline(): a
|
||||
* y/n answer routed through readline() would pollute the session's
|
||||
* command history and behave inconsistently when stdin isn't a TTY
|
||||
* (design.md's Implementation Notes / research.md). It also would not
|
||||
* reliably surface EINTR the way a bare blocking libc read does.
|
||||
*
|
||||
* WHY EINTR IS RELIABLE HERE: task 1.3 (xcetest_interrupt.h,
|
||||
* xcetest_install_sigint()) replaced test.c's old `signal(SIGINT,
|
||||
* sigbreak)` installation with an explicit sigaction() call with
|
||||
* SA_RESTART cleared, once, at process startup. That is a PRECONDITION
|
||||
* of this module, not something confirm_destructive() re-checks on every
|
||||
* call -- see design.md's Service Interface preconditions.
|
||||
*/
|
||||
|
||||
/* Prompts the operator (on stderr) to confirm a destructive action
|
||||
* described by `action_description` (a short phrase completing a
|
||||
* sentence like "About to <action_description>. Continue? [y/N] " --
|
||||
* e.g. "kill job 1234"), unless the skip toggle is set or the session
|
||||
* isn't interactive, in which case it returns 1 without printing
|
||||
* anything or reading stdin. See the return contract above. */
|
||||
int confirm_destructive(const char* action_description);
|
||||
|
||||
/* Sets/gets the session-scoped confirmation-skip toggle (Requirement
|
||||
* 9.6/9.7), in the exact same style as test.c's existing g_continue_on_error
|
||||
* `-e`/`+e` toggle: a plain `static int`, defaulting to 0 (confirmation
|
||||
* required -- NOT skipped) at process start, flipped by `cmd_set` (task
|
||||
* 3.3 wires the `set` command's new branch; this module only owns the
|
||||
* flag itself). While set, confirm_destructive() proceeds without
|
||||
* prompting regardless of TTY state, so a `source`d script (Requirement
|
||||
* 13) can opt in to unattended destructive commands explicitly. */
|
||||
void confirm_set_skip(int skip);
|
||||
int confirm_is_skip_set(void);
|
||||
|
||||
/* Read-only accessor over test.c's private `g_interactive` flag (set once
|
||||
* in main() from isatty(STDIN_FILENO), never changed after that) --
|
||||
* mirrors xcetest_interrupted()'s accessor pattern over test.c's private
|
||||
* `break_out` flag (xcetest_interrupt.h, task 1.3). Declared here, not in
|
||||
* xcetest_interrupt.h, because confirm.c is its only consumer; g_interactive
|
||||
* itself is unrelated to that header's SIGINT/EINTR concern.
|
||||
*
|
||||
* Per design.md's Confirmation Gate invariant, this is deliberately the
|
||||
* TOP-LEVEL session's own stdin, not "is this line currently being read
|
||||
* from a sourced file" -- Requirement 13.7 (a later task) depends on that
|
||||
* distinction: a destructive command inside a `source`d file in an
|
||||
* otherwise-interactive session must still prompt unless the skip toggle
|
||||
* is set.
|
||||
*
|
||||
* Implemented in test.c. test.c itself is never linked into
|
||||
* controller_unit_test (it pulls in libedit/cJSON/libxce/Cap'n Proto/
|
||||
* OpenSSL and defines its own main() -- see xcetest_interrupt.h's header
|
||||
* comment for the same reasoning applied to xcetest_interrupted()), so
|
||||
* test_confirm.c supplies its own local fake definition, exactly as
|
||||
* test_interruptcall.c already does for xcetest_interrupted(). */
|
||||
int xcetest_is_interactive(void);
|
||||
Loading…
Add table
Add a link
Reference in a new issue