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
57 lines
6.9 KiB
Markdown
57 lines
6.9 KiB
Markdown
## Summary
|
|
- **Feature**: `xce-test-rc-file`
|
|
- **Discovery Scope**: Extension (existing `xce_test` REPL client)
|
|
- **Key Findings**:
|
|
- `xce_test`'s own `testsuite/` directory already has an established pattern for a small, dependency-free capability implemented as its own `.c`/`.h` module and linked into both `xce_test` and `controller_unit_test` (e.g. `configcompare.c` at `meson.build:57` and `meson.build:166-167`). The new rc-file logic should follow this exact pattern rather than growing `test.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_line` dispatch, and a return-code convention — `0` = keep going, nonzero = abort-on-failure — that already differs by session mode (`g_continue_on_error`, set from `isatty(STDIN_FILENO)` at `test.c:4476-4477`). Feeding rc-file lines through this exact function, unmodified, satisfies Requirement 3 with no new failure-handling code.
|
|
- `main()` computes `g_interactive`/`g_continue_on_error` at `test.c:4476-4477`, immediately before the `while (!done)` read loop at `test.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/rc` when `$XDG_CONFIG_HOME` is unset or empty.
|
|
- **Sources Consulted**: XDG Base Directory Specification (freedesktop.org) — defines `$XDG_CONFIG_HOME` with a required fallback of `$HOME/.config` when unset.
|
|
- **Findings**: The spec explicitly names `$HOME/.config` as 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 `$HOME` is 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**:
|
|
1. Write rc-file-specific error handling (its own abort/continue logic, its own error formatting).
|
|
2. Pass `preprocess_and_run` itself as a callback into a generic line-loading loop.
|
|
- **Selected Approach**: (2). `rcfile_load(path, runner)` takes a `int (*runner)(char*)` function pointer; `main()` passes `preprocess_and_run` directly.
|
|
- **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 updates `g_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.c` cannot be linked/tested with a real `preprocess_and_run` in isolation (it lives in `test.c`, coupled to the whole command-table/client-context state) — unit tests use a fake runner instead, which is sufficient since `rcfile_load`'s own contract (call runner per line, stop on nonzero, handle ENOENT/other errors) is what needs proving, not `preprocess_and_run` itself (already covered by `xce-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**:
|
|
1. Treat every `fopen` failure identically (silent skip).
|
|
2. Distinguish `ENOENT` (silent) from other errors (non-fatal stderr warning, then proceed).
|
|
- **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_test` to 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 `--help` output.
|
|
- **Selected Approach**: Add `{ "norc", OPT_NORC, 0, 0, "..." }` to `test.c`'s existing `options[]` (`test.c:674-677`), using a symbolic `key` above the printable-ASCII range (the standard `argp` idiom for a long-only flag with no short letter), and a new `int norc` field on `struct arguments`.
|
|
- **Rationale**: Matches this project's existing single-flag `argp` setup (`-v`/`--verbose`) with minimal surface; `argp` auto-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_test` command is already non-interactive/non-prompting; this feature does not add any new blocking primitive.
|
|
- A missing/unset `$HOME` in 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](https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html) — defines `$XDG_CONFIG_HOME` and its `$HOME/.config` fallback.
|