myxce/src/controller/testsuite/rcfile.h
Rongsong Shen 77aba21fb0
feat(xce-test-rc-file): task 1.2 rc-file line-loading via pluggable runner
Adds rcfile_load(path, runner) to rcfile.c/rcfile.h: reads lines with
getline() (no fixed-size buffer, so a long line like a multi-peer
`set masters ...` can never be silently truncated or split), strips the
trailing newline, invokes the caller-supplied runner once per line, and
stops at the first nonzero runner return, propagating that value. A missing
rc file is a silent no-op; any other open failure prints one stderr warning
and returns 0.

Found during GREEN: fopen() succeeds on a directory path on this platform,
so the EISDIR failure only surfaces on the first getline() call (ferror(f)
set) -- added a post-loop ferror() check to catch it (bug-1716).

5 new unit tests (11 total with task 1.1): ENOENT-silent, per-line-in-order
with newline stripped, stop-on-nonzero with exact propagation, non-ENOENT
open-failure warning, and a >4KB single line proving no truncation/split.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AgYRU3zDTqNGV5Jk8aiKLg
2026-08-26 15:02:03 +00:00

39 lines
1.9 KiB
C

#pragma once
/*
* rcfile.h -- xce_test startup rc-file path resolution and line-loading
* (xce-test-rc-file spec, tasks 1.1/1.2).
*
* A standalone, dependency-light module following the same pattern as
* configcompare.c/h -- no dependency on test.c internals, linked into both
* the xce_test executable and controller_unit_test.
*
* Task 1.1 implemented path resolution (rcfile_resolve_path()). Task 1.2
* adds line-loading (rcfile_load()) with a pluggable runner callback.
* Wiring rcfile_load() into xce_test's own startup/main() is a later task
* in the same spec (2.2) and is out of scope here -- this module has no
* knowledge of xce_test commands, contexts, or client state.
*/
/* Resolves the rc-file path per the XDG Base Directory Specification:
* "$XDG_CONFIG_HOME/xce_test/rc" when XDG_CONFIG_HOME is set and non-empty,
* else "$HOME/.config/xce_test/rc" when HOME is set and non-empty. Returns
* a heap string the caller must free(), or NULL if neither variable yields
* a usable base directory. */
char* rcfile_resolve_path(void);
/* Opens `path` and calls `runner(line)` once per line (trailing newline
* stripped), stopping early if `runner` returns nonzero. Lines are read via
* getline() with no length cap -- a line is never truncated or split
* regardless of length.
*
* Returns:
* 0 - path is NULL, the file does not exist (ENOENT),
* the file could not be opened for another reason
* (a warning is printed to stderr; non-fatal), or
* every line ran and every runner call returned 0.
* nonzero (== runner's) - the runner requested an abort; no further lines
* are read.
*
* Preconditions: `runner` is non-NULL when `path` is non-NULL.
* Postconditions: the file handle is always closed before returning. */
int rcfile_load(const char* path, int (*runner)(char* line));