Adds rcfile_resolve_path() (rcfile.c/rcfile.h): resolves $XDG_CONFIG_HOME/xce_test/rc when set and non-empty, else $HOME/.config/xce_test/rc, else NULL. Standalone libc-only module mirroring the configcompare.c pattern; wired into xce_test_src and controller_unit_test_src, registered in controller_test.c's Unity runner. 6 unit tests in test_rcfile.c cover all XDG/HOME combinations plus a dedicated env-var save/restore isolation test, addressing the cross-test leak risk found during design review (controller_unit_test runs 80+ test files in one process). Adds the xce-test-rc-file spec (requirements/design/tasks/research), including design fixes from validation: getline()-based line reading (task 1.2, avoids truncating long rc-file lines) and the env-isolation requirement above. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AgYRU3zDTqNGV5Jk8aiKLg
6.9 KiB
6.9 KiB
Summary
- Feature:
xce-test-rc-file - Discovery Scope: Extension (existing
xce_testREPL client) - Key Findings:
xce_test's owntestsuite/directory already has an established pattern for a small, dependency-free capability implemented as its own.c/.hmodule and linked into bothxce_testandcontroller_unit_test(e.g.configcompare.catmeson.build:57andmeson.build:166-167). The new rc-file logic should follow this exact pattern rather than growingtest.c(already ~4500 lines) with inline logic.preprocess_and_run(char* line)(test.c:955) already implements the exact per-line contract this feature needs: blank/comment handling,!-shell-escape, auto-var substitution,execute_linedispatch, and a return-code convention —0= keep going, nonzero = abort-on-failure — that already differs by session mode (g_continue_on_error, set fromisatty(STDIN_FILENO)attest.c:4476-4477). Feeding rc-file lines through this exact function, unmodified, satisfies Requirement 3 with no new failure-handling code.main()computesg_interactive/g_continue_on_errorattest.c:4476-4477, immediately before thewhile (!done)read loop attest.c:4479. This is the correct insertion point for rc-file loading: after mode detection (so the reused abort/continue behavior is correct) and before the first real input line is read (satisfying Requirement 1.4).
Research Log
XDG Base Directory Specification — default path resolution
- Context: Requirements call for
$XDG_CONFIG_HOME/xce_test/rc, falling back to~/.config/xce_test/rcwhen$XDG_CONFIG_HOMEis unset or empty. - Sources Consulted: XDG Base Directory Specification (freedesktop.org) — defines
$XDG_CONFIG_HOMEwith a required fallback of$HOME/.configwhen unset. - Findings: The spec explicitly names
$HOME/.configas the fallback, so the fallback in Requirement 1.1 is not a project-specific guess — it is the spec's own defined default. The spec is silent on what to do when even$HOMEis unavailable. - Implications: Path resolution is a pure function of two environment variables. When neither is usable, there is no defined path — treat this the same as "rc file not found" (silent skip), not an error, since no plausible location exists to report.
Architecture Pattern Evaluation
| Option | Description | Strengths | Risks / Limitations | Notes |
|---|---|---|---|---|
New rcfile.c/rcfile.h module |
Path resolution + line-loading loop as a standalone, dependency-free pair, wired into test.c's main() |
Matches existing configcompare.c/waldump.c precedent; independently unit-testable via controller_unit_test; keeps test.c unchanged except for the call site and one new argp option |
None significant — smallest addition that satisfies all requirements | Selected |
Inline logic in test.c's main() |
Add the loop and path resolution directly in main() |
Fewer files | Not unit-testable in isolation (would require driving the full main()/argp/readline stack); breaks from this directory's established module pattern |
Rejected |
Design Decisions
Decision: Reuse preprocess_and_run as the rc-file line runner, unmodified
- Context: Requirement 3 needs a failing rc-file command to be reported and handled exactly like any other failing command, without introducing a parallel failure-handling mechanism.
- Alternatives Considered:
- Write rc-file-specific error handling (its own abort/continue logic, its own error formatting).
- Pass
preprocess_and_runitself as a callback into a generic line-loading loop.
- Selected Approach: (2).
rcfile_load(path, runner)takes aint (*runner)(char*)function pointer;main()passespreprocess_and_rundirectly. - Rationale:
preprocess_and_run's existing return contract (0 = continue, nonzero = abort) already encodes the mode-dependent abort-vs-continue decision (Requirement 3.2/3.3) and already updatesg_last_status/g_session_failed(Requirement 3.4). Reusing it verbatim means Requirement 3 requires zero new logic — only a thin loop that stops when the runner says to. - Trade-offs:
rcfile.ccannot be linked/tested with a realpreprocess_and_runin isolation (it lives intest.c, coupled to the whole command-table/client-context state) — unit tests use a fake runner instead, which is sufficient sincercfile_load's own contract (call runner per line, stop on nonzero, handle ENOENT/other errors) is what needs proving, notpreprocess_and_runitself (already covered byxce-test-e2e-primitives's tests). - Follow-up: None.
Decision: Distinguish ENOENT from other fopen failures
- Context: Requirement 1.3 only specifies silence when the rc file does not exist. It does not say what happens on a real failure (e.g. permission denied, or the path is a directory).
- Alternatives Considered:
- Treat every
fopenfailure identically (silent skip). - Distinguish
ENOENT(silent) from other errors (non-fatal stderr warning, then proceed).
- Treat every
- Selected Approach: (2).
- Rationale: A misconfigured rc file (wrong permissions, a directory at the expected path) is a real misconfiguration worth surfacing — silently ignoring it could leave a user wondering why their settings never took effect. It must still be non-fatal (startup must proceed) since this feature must not become a new way for
xce_testto fail to start. - Trade-offs: One extra branch in
rcfile_load; no downside identified. - Follow-up: None.
Decision: --norc as a long-only argp option
- Context: Requirement 2 needs a flag that skips rc-file loading, documented in
--helpoutput. - Selected Approach: Add
{ "norc", OPT_NORC, 0, 0, "..." }totest.c's existingoptions[](test.c:674-677), using a symbolickeyabove the printable-ASCII range (the standardargpidiom for a long-only flag with no short letter), and a newint norcfield onstruct arguments. - Rationale: Matches this project's existing single-flag
argpsetup (-v/--verbose) with minimal surface;argpauto-generates the--help/-?entry from the option's doc string, satisfying Requirement 2.2 with no additional code. - Trade-offs: None identified for this scope.
Risks & Mitigations
- A user's rc file blocks on stdin (e.g. contains a command that itself prompts) — mitigated by the fact that every existing
xce_testcommand is already non-interactive/non-prompting; this feature does not add any new blocking primitive. - A missing/unset
$HOMEin a stripped-down container changes rc-file behavior in a way an operator doesn't expect — mitigated by treating "no resolvable path" identically to "file not found" (silent, non-fatal), consistent with Requirement 1.3's spirit.
References
- XDG Base Directory Specification — defines
$XDG_CONFIG_HOMEand its$HOME/.configfallback.