Cover rpi_nc_ca.sh (98.63%) and bind the two paths its suite needed

A 26-assertion suite for rpi_nc_ca.sh, third in the hardest-first order. All
five OS branches are exercised by seeding the marker file each one tests for
inside the jailed /etc, plus a uname stub for Darwin; both NSS stores and all
three Firefox outcomes are covered.

SHARED-FILE BUG: the Debian branch does `mkdir -p
/usr/local/share/ca-certificates` and jail_args bound only /usr/local/bin, so
the suite aborted there under set -e. /usr/local/share and /etc/pki are now
bound. jail_args is shared by every suite in the directory, so all five libs
were re-measured: dot_resolver_install.sh still 39/39 = 100.00%, and
rpi_nc_install.sh, dwm_config.sh and aw_autostart.sh are unchanged. An
over-aggressive mount looks exactly like unreachable code, so this check is
not optional.

That bind also unblocked the runner for the whole directory: with the old
args the run exited 1 before nc_php.sh could be measured at all.

Measured 72/73 = 98.63% through run_all.sh, which is the exact subject
is_covered() builds -- the gate's own view -- so the lib STAYS on the
allowlist. The single test file alone reports 73/73 = 100.00%, both figures
reproduced twice. The one-line delta is line 141, the first line of
show_help's heredoc BODY: not a statement, so the denominator is wrong rather
than a test missing. Excluding heredoc bodies from the denominator is the
next piece of work; it is what stands between this suite and the bar.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016sxxsDHWgdRhmdmzNMA6XM
This commit is contained in:
Krzysztof Rudnicki 2026-08-22 12:59:51 +02:00
parent 26afe4390c
commit 92678c9506
3 changed files with 262 additions and 0 deletions

View File

@ -0,0 +1,50 @@
{
"intent": "Add a test suite for rpi_nc_ca.sh (score 51), the third target in the hardest-first order, and add the two bind paths its suite exposed as missing from the shared jail_args. It stays on the allowlist at 98.63% as measured through run_all.sh, which is the invocation the gate itself uses.",
"scope": [
"linux_configuration/scripts/single_use/features/lib/tests/test_rpi_nc_ca.sh: new, 26 assertions covering all 5 OS branches, both NSS stores and every Firefox outcome",
"linux_configuration/scripts/single_use/features/lib/tests/jail_args: add --bind /usr/local/share and --bind /etc/pki",
"Non-goal: no change to rpi_nc_ca.sh -- no latent bug surfaced",
"Non-goal: excluding heredoc bodies from the coverage denominator; that is the next piece of work and is what stands between this suite and 100%"
],
"changes": [
"The five OS branches (Arch/Debian/RHEL/macOS/unknown) are selected by SEEDING the marker file each one tests for inside the jailed /etc, plus a `uname` stub for Darwin. Safe only because /etc is bind-mounted -- on a real host this would make the machine claim to be a different distribution.",
"The sshpass stub EMITS a certificate rather than recording the call. A record-only stub leaves an empty file and the subject dies on its own emptiness check, per the documented trap that a stub must materialise what the real tool produces.",
"SHARED-FILE BUG FIXED: the Debian branch does `mkdir -p /usr/local/share/ca-certificates`, and jail_args bound only /usr/local/bin. The suite aborted there under set -e. /usr/local/share and /etc/pki are now bound. Because jail_args is shared by every suite in the directory, all five libs were re-measured after the change (see verification) -- an over-aggressive mount looks exactly like unreachable code."
],
"verification": [
{
"command": "shell_coverage_jail.sh --subject .../run_all.sh <jail_args> --measure rpi_nc_ca.sh --min 100 --fail-on-case-error",
"result": "pass",
"evidence": "rpi_nc_ca.sh: 72/73 = 98.63%, uncovered line 141, and no 'warn: case exited' -- every assertion passes. Measured through run_all.sh because that is the exact subject is_covered() builds; it is the gate's own view."
},
{
"command": "same, but with the single test file as the subject",
"result": "pass",
"evidence": "73/73 = 100.00%, reproduced twice. The runner reports 98.63% twice. Stable in both modes, so this is a kcov per-process merge artifact, not flakiness. Line 141 is the first line of show_help's heredoc BODY -- not a statement, so the denominator is wrong rather than the test missing."
},
{
"command": "re-measure all five libs through run_all.sh after the jail_args change",
"result": "pass",
"evidence": "dot_resolver_install.sh 39/39 = 100.00% (unchanged, and it is already off the allowlist so a regression here would make the gate unsatisfiable); rpi_nc_install.sh 11.36%, dwm_config.sh 47.95%, aw_autostart.sh 82.93% -- all unchanged. No mount masked anything."
},
{
"command": "measure nc_php.sh with the OLD jail_args vs the NEW, single-file",
"result": "pass",
"evidence": "84/85 = 98.82% under both -- no regression. Under the OLD args the RUNNER could not measure it at all (the suite exited 1 on the /usr/local/share abort this commit fixes); it now reports 'instrumented no lines' via the runner, a pre-existing merge artifact of the same family as line 141."
},
{
"command": "shellcheck on the new suite",
"result": "pass",
"evidence": "clean, no suppressions added."
}
],
"risks": [
"jail_args is shared by every suite in the directory, so these two binds affect all of them. Mitigated by re-measuring all five libs; if a future bind does mask something, scope it to the leaf directory (--bind /usr/local/share/ca-certificates) rather than reverting the suite that needed it.",
"The suite seeds /etc/arch-release, /etc/debian_version and /etc/redhat-release. Run outside the jail this would make the host claim a false distribution, which is why the suite refuses to start when /etc is not writable.",
"98.63% vs 100% hinges on one heredoc-body line. Until shell_coverage_report.py excludes heredoc bodies from the denominator, any lib with a show_help-style heredoc is permanently capped below the bar."
],
"rollback": [
"git revert the commit; the suite disappears and jail_args loses the two binds, which returns the whole directory's runner to exiting 1 on the /usr/local/share abort.",
"After rollback, confirm the runner fails again for every suite in the directory -- that shared failure is the symptom this commit removes."
]
}

View File

@ -19,6 +19,10 @@
/var
--bind
/root
--bind
/usr/local/share
--bind
/etc/pki
--seed-dir
/var/www/nextcloud
--seed-dir

View File

@ -0,0 +1,208 @@
#!/usr/bin/env bash
# Tests for rpi_nc_ca.sh — installing the Pi's CA into the client's trust stores.
#
# The subject sudo-copies into /etc/ca-certificates, /usr/local/share and
# /etc/pki, and appends to /etc/hosts. Those writes are the point, so they run
# for real inside the jail, which bind-mounts every one of those paths to a
# throwaway dir. See jail_args beside this file.
#
# The five OS branches are selected by SEEDING the marker file each one tests
# for (/etc/arch-release, /etc/debian_version, ...) inside the jailed /etc.
# That is only safe because /etc is bind-mounted: on a real host this would
# make the machine claim to be a different distribution.
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# shellcheck source=./features_harness.sh
. "$SCRIPT_DIR/features_harness.sh"
# --- Refuse to run outside the jail -----------------------------------------
# The suite seeds /etc/arch-release and friends and appends to /etc/hosts.
# An unwritable /etc means we are NOT contained.
if [[ ! -w /etc ]]; then
printf 'REFUSING: this suite must run under shell_coverage_jail.sh\n' >&2
exit 1
fi
_t_setup_env
trap _t_teardown EXIT
# Globals the entry script defines above its source line.
PI_PASSWORD="hunter2"
PI_USER="pi"
PI_HOSTNAME="raspberrypi"
log_info() { printf '[INFO] %s\n' "$*"; }
log_success() { printf '[OK] %s\n' "$*"; }
log_warning() { printf '[WARN] %s\n' "$*"; }
die() {
printf '[DIE] %s\n' "$*" >&2
exit 1
}
discover_raspberry_pi() { printf '10.0.0.7\n'; }
# sshpass writes the CA to stdout, which the subject redirects into the file.
# A record-only stub would leave an EMPTY file and the subject would die --
# a stub must materialise what the real tool produces.
_t_ssh_emits_ca() {
cat >"$TEST_TMPDIR/bin/sshpass" <<'SSHPASS'
#!/usr/bin/env bash
printf -- '-----BEGIN CERTIFICATE-----\nFAKECA\n-----END CERTIFICATE-----\n'
SSHPASS
chmod +x "$TEST_TMPDIR/bin/sshpass"
}
# shellcheck source=../rpi_nc_ca.sh
. "$FEATURES_LIB_DIR/rpi_nc_ca.sh"
[[ -n $PI_HOSTNAME && -n $PI_USER ]] ||
die "fixture globals are empty; the suite would assert nothing"
# --- show_help --------------------------------------------------------------
help_out="$(show_help)"
_t_has "$help_out" 'Usage:' "show_help prints a usage line"
_t_has "$help_out" 'raspberry_pi_nextcloud.sh' "show_help names the entry script"
# --- phase_install_ca: refuses without a password ---------------------------
no_pw_out="$(
PI_PASSWORD=""
phase_install_ca 2>&1
)" || true
_t_has "$no_pw_out" 'PI_PASSWORD not set' "phase_install_ca dies when PI_PASSWORD is unset"
# --- phase_install_ca: refuses when the Pi cannot be found ------------------
no_pi_out="$(
discover_raspberry_pi() { printf '\n'; }
phase_install_ca 2>&1
)" || true
_t_has "$no_pi_out" 'Failed to discover' "phase_install_ca dies when the Pi is undiscoverable"
# --- phase_install_ca: refuses on an empty CA download ----------------------
_t_stub sshpass
empty_out="$(phase_install_ca 2>&1)" || true
_t_has "$empty_out" 'Failed to download CA' "phase_install_ca dies when the downloaded CA is empty"
# --- phase_install_ca: the Arch branch --------------------------------------
_t_ssh_emits_ca
_t_stub trust
_t_stub certutil
_t_stub curl
rm -f /etc/debian_version /etc/redhat-release
: >/etc/arch-release
mkdir -p /etc/ca-certificates/trust-source/anchors
arch_out="$(phase_install_ca 2>&1)"
_t_has "$arch_out" 'Detected Arch Linux' "the Arch branch is selected by /etc/arch-release"
_t_file_has /etc/ca-certificates/trust-source/anchors/nextcloud-ca.crt 'FAKECA' \
"the Arch branch copies the CA into the system anchor dir"
_t_called 'trust' "the Arch branch runs trust extract-compat"
_t_file_has /etc/hosts 'raspberrypi' "the hostname is appended to /etc/hosts"
# --- phase_install_ca: /etc/hosts is not duplicated on a second run ---------
second_out="$(phase_install_ca 2>&1)"
_t_eq "1" "$(grep -c 'raspberrypi' /etc/hosts)" "a second run does not duplicate the /etc/hosts entry"
_t_has "$second_out" 'already in /etc/hosts' "the second run reports the entry already exists"
# --- phase_install_ca: the Debian branch ------------------------------------
rm -f /etc/arch-release
: >/etc/debian_version
mkdir -p /usr/local/share/ca-certificates
_t_stub update-ca-certificates
deb_out="$(phase_install_ca 2>&1)"
_t_has "$deb_out" 'Detected Debian/Ubuntu' "the Debian branch is selected by /etc/debian_version"
_t_file_has /usr/local/share/ca-certificates/nextcloud-ca.crt 'FAKECA' \
"the Debian branch copies the CA into /usr/local/share"
_t_called 'update-ca-certificates' "the Debian branch runs update-ca-certificates"
# --- phase_install_ca: the RHEL branch --------------------------------------
rm -f /etc/debian_version
: >/etc/redhat-release
mkdir -p /etc/pki/ca-trust/source/anchors
_t_stub update-ca-trust
rhel_out="$(phase_install_ca 2>&1)"
_t_has "$rhel_out" 'Detected RHEL/Fedora' "the RHEL branch is selected by /etc/redhat-release"
_t_file_has /etc/pki/ca-trust/source/anchors/nextcloud-ca.crt 'FAKECA' \
"the RHEL branch copies the CA into the pki anchor dir"
_t_called 'update-ca-trust' "the RHEL branch runs update-ca-trust"
# --- phase_install_ca: the unknown-OS branch --------------------------------
rm -f /etc/redhat-release
unknown_out="$(phase_install_ca 2>&1)"
_t_has "$unknown_out" 'Unknown OS' "an unrecognised OS warns instead of failing"
# --- phase_install_ca: the macOS branch -------------------------------------
# Selected by `uname` rather than a marker file, so the stub is what picks it.
_t_stub security
cat >"$TEST_TMPDIR/bin/uname" <<'UNAME'
#!/usr/bin/env bash
printf 'Darwin\n'
UNAME
chmod +x "$TEST_TMPDIR/bin/uname"
mac_out="$(phase_install_ca 2>&1)"
_t_has "$mac_out" 'Detected macOS' "the macOS branch is selected when uname reports Darwin"
_t_has "$mac_out" 'system keychain' "the macOS branch reports the keychain install"
rm -f "$TEST_TMPDIR/bin/uname"
# --- phase_install_ca: certutil reports the CA is ALREADY installed ---------
# `certutil -L` emitting a line containing "Nextcloud" takes the else branch.
cat >"$TEST_TMPDIR/bin/certutil" <<'CERTUTIL'
#!/usr/bin/env bash
printf 'Nextcloud Home CA CT,C,C\n'
exit 0
CERTUTIL
chmod +x "$TEST_TMPDIR/bin/certutil"
already_out="$(phase_install_ca 2>&1)"
_t_has "$already_out" 'already installed in Chrome/Chromium' \
"an existing NSS entry is reported rather than re-added"
# --- phase_install_ca: certutil FAILS to add the CA -------------------------
# -L must succeed-but-not-match (so the add is attempted) while -A fails.
cat >"$TEST_TMPDIR/bin/certutil" <<'CERTUTIL'
#!/usr/bin/env bash
for arg in "$@"; do
if [[ $arg == -A ]]; then
exit 1
fi
done
exit 0
CERTUTIL
chmod +x "$TEST_TMPDIR/bin/certutil"
cannot_out="$(phase_install_ca 2>&1)"
_t_has "$cannot_out" 'Could not install in Chrome/Chromium' \
"a failing certutil -A warns instead of aborting"
# --- phase_install_ca: the Firefox loop, all three outcomes -----------------
# A profile dir must exist for the loop body to run at all; the glob otherwise
# yields a literal unmatched pattern that fails the -d test.
mkdir -p ~/.mozilla/firefox/abcd.default
# certutil -A still fails here (stubbed above), so nothing installs.
ff_fail_out="$(phase_install_ca 2>&1)"
_t_has "$ff_fail_out" 'Could not install in Firefox' \
"a profile whose certutil -A fails reports the manual-import warning"
# -L succeeds but does not match, and -A now succeeds: the CA is added.
_t_stub certutil
ff_add_out="$(phase_install_ca 2>&1)"
_t_has "$ff_add_out" 'CA installed in Firefox' "a successful certutil -A reports the Firefox install"
# -L matches "Nextcloud": already present, so the add is skipped.
cat >"$TEST_TMPDIR/bin/certutil" <<'CERTUTIL'
#!/usr/bin/env bash
printf 'Nextcloud Home CA CT,C,C\n'
exit 0
CERTUTIL
chmod +x "$TEST_TMPDIR/bin/certutil"
ff_have_out="$(phase_install_ca 2>&1)"
_t_has "$ff_have_out" 'CA installed in Firefox' "an already-present Firefox CA still reports success"
# --- phase_install_ca: HTTPS verification succeeds --------------------------
cat >"$TEST_TMPDIR/bin/curl" <<'CURL'
#!/usr/bin/env bash
printf 'installed\n'
CURL
chmod +x "$TEST_TMPDIR/bin/curl"
verified_out="$(phase_install_ca 2>&1)"
_t_has "$verified_out" 'HTTPS connection verified' "a status.php reporting 'installed' verifies the connection"
_t_report "test_rpi_nc_ca.sh"