Skip to content

test(config): cover the four-layer config precedence chain - #76

Merged
acamarata merged 1 commit into
mainfrom
test/config-mutation-coverage
Sep 15, 2026
Merged

acamarata merged 1 commit into
mainfrom
test/config-mutation-coverage

Conversation

@acamarata

Copy link
Copy Markdown
Contributor

src/config/mod.rs had zero tests. Adds 12 covering DaemonConfig::new, which layers CLI/env args > process environment > {data_dir}/config.toml > built-in defaults.

The mutants that matter here reorder an .or() chain, swap a default constant, or drop an .is_empty() filter — so every field is exercised at more than one layer. A test that only checks the default cannot tell a correct chain from one that ignores its overrides.

Environment safety

These mutate process-global state, and Rust runs tests in parallel threads. This follows the idiom already established in session::cursor::tests (a module lock held across the whole sequence, tolerating a poisoned mutex) and adds a Drop-based guard that clears all eight CLAWD_* variables on entry and restores them on drop, including on panic.

Without the clear, a "the default applies" test would fail on any machine that happens to export CLAWD_API_URL — an environment-dependent test is its own kind of hollow gate. Verified by running the full 706-test lib suite twice with zero failures.

Verification

Hand-applied six mutants; each fails a test:

Mutant Result
DEFAULT_PORT 4300 → 8080 KILLED
default_bind_address0.0.0.0 (LAN exposure) KILLED
port precedence flipped to toml.port.or(port) KILLED
bind precedence putting env before the CLI arg KILLED
drop the is_empty filter on CLAWD_REGISTRY_URL KILLED
log default infowarn KILLED

clippy --all-targets --all-features -- -D warnings and cargo fmt --check both exit 0.

One finding recorded, not changed

api_base_url and relay_url are the only two env reads without a .filter(|s| !s.is_empty()):

Variable set to "" Result
CLAWD_API_URL wins over the TOML value and blanks the URL
CLAWD_RELAY_URL wins and blanks the URL
CLAWD_REGISTRY_URL, CLAWD_BIND, CLAWD_UPDATE_POLICY, CLAWD_LOG_FORMAT, CLAWD_LICENSE_TOKEN, CLAWD_API_TOKEN falls through to the next layer

An unset shell variable expands to "", so CLAWD_API_URL=$SOME_UNSET_VAR silently points the daemon at an empty API URL. A test pins the current asymmetry so it cannot drift silently; I did not add the filter, since that changes startup behaviour for anyone relying on it. Worth a decision.

src/config/mod.rs had zero tests. Adds 12 covering DaemonConfig::new, which
layers CLI/env args > process environment > {data_dir}/config.toml >
built-in defaults. The mutants that matter here reorder an .or() chain, swap
a default constant, or drop an .is_empty() filter, so every field is
exercised at more than one layer — a test that only checks the default
cannot tell a correct chain from one that ignores its overrides.

Environment safety: these mutate process-global state and Rust runs tests in
parallel threads. This follows the idiom already established in
session::cursor::tests (a module lock held across the whole sequence,
tolerating a poisoned mutex) and adds a Drop-based guard that CLEARS all
eight CLAWD_* variables on entry and restores them on drop, including on
panic. Without the clear, 'the default applies' tests would fail on any
machine that happens to export CLAWD_API_URL. Verified by running the full
706-test lib suite twice with no failures.

Verified by hand-applying six mutants and confirming each fails a test:
DEFAULT_PORT 4300 -> 8080, default_bind_address -> 0.0.0.0, port precedence
flipped to toml.port.or(port), bind precedence putting env before the CLI
arg, dropping the is_empty filter on CLAWD_REGISTRY_URL, and the log default
info -> warn. All six were caught.

One finding recorded rather than changed: api_base_url and relay_url are the
only two env reads WITHOUT a .filter(|s| !s.is_empty()), so CLAWD_API_URL=''
beats the TOML value and blanks the URL, where CLAWD_REGISTRY_URL='' falls
through to the next layer. A test pins the asymmetry so it cannot drift
silently; adding the filter would change startup behaviour and is a decision
rather than a coverage change.
@acamarata
acamarata merged commit d547233 into main Sep 15, 2026
12 checks passed
@acamarata
acamarata deleted the test/config-mutation-coverage branch September 15, 2026 03:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant