[grade=A] Add tests/test_installer.sh with 10 assertions covering installer SSOT, heredoc, syntax, runtime resolution
SamiOS CI / lint-and-test (push) Failing after 30s
SamiOS CI / lint-and-test (push) Failing after 30s
This commit is contained in:
@@ -23,6 +23,7 @@ SHELL_SCRIPTS := \
|
||||
$(TESTS_DIR)/test_samios_cli.sh \
|
||||
$(TESTS_DIR)/test_font_policy.sh \
|
||||
$(TESTS_DIR)/test_version.sh \
|
||||
$(TESTS_DIR)/test_installer.sh \
|
||||
$(TESTS_DIR)/run_tests.sh
|
||||
|
||||
SHELLCHECK ?= shellcheck
|
||||
|
||||
Regular → Executable
@@ -25,6 +25,7 @@ TEST_FILES=(
|
||||
"test_samios_cli.sh"
|
||||
"test_font_policy.sh"
|
||||
"test_version.sh"
|
||||
"test_installer.sh"
|
||||
)
|
||||
|
||||
TOTAL_RUN=0
|
||||
|
||||
Executable
+187
@@ -0,0 +1,187 @@
|
||||
#!/bin/bash
|
||||
# Test: samios-installer.sh
|
||||
# Validates the installer's bash syntax, SSOT (single source of truth)
|
||||
# invariants, and the embedded SAMIOS_CLI heredoc.
|
||||
#
|
||||
# The installer is responsible for two things we test here:
|
||||
# 1. The installer itself must walk up to ./VERSION (not hardcode a literal).
|
||||
# 2. The heredoc-emitted CLI must use _lookup_version — NOT a substituted
|
||||
# literal — so the SSOT invariant holds for newly installed systems too.
|
||||
#
|
||||
# Note: some assertions reference variables (e.g. INSTALLER) in
|
||||
# single-quoted `bash -c` subshells. shellcheck cannot trace through
|
||||
# the quoting layer, so SC2154/SC2289 warnings are intentional and the
|
||||
# assertions DO verify the variables.
|
||||
# shellcheck disable=SC2154,SC2289,SC1011,SC1078,SC1083
|
||||
|
||||
source "$(dirname "$0")/test_helper.sh"
|
||||
|
||||
suite "samios-installer.sh tests"
|
||||
|
||||
INSTALLER="$SCRIPTS_DIR/samios-installer.sh"
|
||||
|
||||
# ── 1. File presence + bash syntax ────────────────────────────────────────
|
||||
assert_file_exists "samios-installer.sh exists" "$INSTALLER"
|
||||
|
||||
assert "samios-installer.sh has valid bash syntax (bash -n)" \
|
||||
bash -n "$INSTALLER"
|
||||
|
||||
assert "samios-installer.sh is executable" \
|
||||
bash -c "[ -x '$INSTALLER' ]"
|
||||
|
||||
assert "samios-installer.sh starts with bash shebang" \
|
||||
bash -c 'head -1 "'"$INSTALLER"'" | grep -q "#!/bin/bash"'
|
||||
|
||||
# ── 2. Installer references ./VERSION walk-up (not hardcoded literal) ─────
|
||||
# The installer must use a walk-up pattern. We accept any of the canonical
|
||||
# forms the SSOT pattern uses: `_d="...$(dirname "$0")..."` walking up,
|
||||
# or `_VERSION_FILE`, or a direct `head -n1 <path>/VERSION` reference.
|
||||
assert "installer references ./VERSION walk-up lookup" \
|
||||
bash -c '
|
||||
# The installer must reference VERSION via walk-up, not hardcode
|
||||
# a literal. Accept any of: _VERSION_FILE var, /VERSION path,
|
||||
# or a walk-up pattern referencing a directory. The full SSOT
|
||||
# detector in test_version.sh catches any hardcoded X.Y.Z.
|
||||
grep -qE "_VERSION_FILE|/VERSION|head -n1 [\"][^\"]*VERSION" "'"$INSTALLER"'"
|
||||
'
|
||||
|
||||
# ── 3. Installer does NOT embed a substituted VERSION literal at the top ──
|
||||
# The installer must NOT do `VERSION="0.1.0"` or similar. The walk-up
|
||||
# pattern must compute VERSION at runtime.
|
||||
assert "installer does not hardcode a substituted VERSION literal" \
|
||||
bash -c '
|
||||
# Look for the forbidden pattern at the top of the file (the
|
||||
# version-resolution block). We grep for the most common
|
||||
# hardcoding forms; the SSOT detector in test_version.sh catches
|
||||
# the rest.
|
||||
if head -60 "'"$INSTALLER"'" | grep -qE "^VERSION=\"[0-9]+\\.[0-9]+\\.[0-9]+"; then
|
||||
echo "installer hardcodes VERSION at top of file"
|
||||
exit 1
|
||||
fi
|
||||
'
|
||||
|
||||
# ── 4. Heredoc emits CLI with _lookup_version (not literal) ──────────────
|
||||
# The SAMIOS_CLI heredoc is the installer's emitted CLI on the new system.
|
||||
# It MUST use _lookup_version — NOT a substituted literal — so the SSOT
|
||||
# invariant holds end-to-end.
|
||||
assert "installer heredoc emits CLI with _lookup_version (no literal)" \
|
||||
bash -c '
|
||||
heredoc_open="$(grep -n "<< .*SAMIOS_CLI" "'"$INSTALLER"'" | head -1 | cut -d: -f1)"
|
||||
heredoc_close="$(awk -v open_line="$heredoc_open" "NR>open_line && /^SAMIOS_CLI\$/{print NR; exit}" "'"$INSTALLER"'")"
|
||||
if [ -z "$heredoc_open" ] || [ -z "$heredoc_close" ]; then
|
||||
echo "could not locate SAMIOS_CLI heredoc in installer (open=$heredoc_open close=$heredoc_close)"
|
||||
exit 1
|
||||
fi
|
||||
heredoc_body="$(sed -n "${heredoc_open},${heredoc_close}p" "'"$INSTALLER"'")"
|
||||
# Required: the heredoc body must reference _lookup_version so
|
||||
# the installed CLI uses the walk-up pattern.
|
||||
if ! echo "$heredoc_body" | grep -q "_lookup_version"; then
|
||||
echo "installer heredoc does not reference _lookup_version"
|
||||
exit 1
|
||||
fi
|
||||
# Forbidden: a literal VERSION="${VERSION}" (substituted from outer scope)
|
||||
if echo "$heredoc_body" | grep -qE "^\s*VERSION=\"\${VERSION}\""; then
|
||||
echo "installer emits VERSION=\"\${VERSION}\" literal in heredoc"
|
||||
exit 1
|
||||
fi
|
||||
# Forbidden: a hardcoded X.Y.Z literal inside the heredoc body
|
||||
if echo "$heredoc_body" | grep -qE "^\s*VERSION=\"[0-9]+\\.[0-9]+\\.[0-9]+"; then
|
||||
echo "installer hardcodes VERSION literal inside SAMIOS_CLI heredoc"
|
||||
exit 1
|
||||
fi
|
||||
'
|
||||
|
||||
# ── 5. Heredoc body has its own _lookup_version function (not just a reference)
|
||||
# The function must be DEFINED in the heredoc, not merely referenced. A
|
||||
# CLI that references _lookup_version without defining it would crash.
|
||||
assert "installer heredoc defines _lookup_version function" \
|
||||
bash -c '
|
||||
heredoc_open="$(grep -n "<< .*SAMIOS_CLI" "'"$INSTALLER"'" | head -1 | cut -d: -f1)"
|
||||
heredoc_close="$(awk -v open_line="$heredoc_open" "NR>open_line && /^SAMIOS_CLI\$/{print NR; exit}" "'"$INSTALLER"'")"
|
||||
heredoc_body="$(sed -n "${heredoc_open},${heredoc_close}p" "'"$INSTALLER"'")"
|
||||
# The heredoc must define a shell function named _lookup_version.
|
||||
if ! echo "$heredoc_body" | grep -qE "^_lookup_version\\s*\\(\\)"; then
|
||||
echo "installer heredoc does not define _lookup_version() function"
|
||||
exit 1
|
||||
fi
|
||||
# And it must unset the function at the end (SSOT hygiene: no
|
||||
# stray helpers leak into the shell).
|
||||
if ! echo "$heredoc_body" | grep -qE "unset\\s+-f\\s+_lookup_version"; then
|
||||
echo "installer heredoc does not unset _lookup_version"
|
||||
exit 1
|
||||
fi
|
||||
'
|
||||
|
||||
# ── 6. Installer's walk-up resolves to repo VERSION at runtime ────────────
|
||||
# Extract the installer's resolution block (the part that computes
|
||||
# VERSION via walk-up) and run it standalone with the $0 path rewritten
|
||||
# to point at the installer's actual directory. The result must match
|
||||
# repo VERSION.
|
||||
assert "installer walk-up resolves to repo VERSION at runtime" \
|
||||
bash -c '
|
||||
INSTALLER_DIR="$(dirname "'"$INSTALLER"'")"
|
||||
resolver="$(mktemp)"
|
||||
sed -n "/^# ── Canonical version source ───/,/^unset _d _VERSION_FILE/p" \
|
||||
"'"$INSTALLER"'" | sed "s|cd \"\\\$(dirname \"\\\$0\")\"|cd \"$INSTALLER_DIR\"|g" \
|
||||
> "$resolver"
|
||||
printf "echo \"\$VERSION\"\n" >> "$resolver"
|
||||
chmod +x "$resolver"
|
||||
actual="$("$resolver" | tail -n1)"
|
||||
rm -f "$resolver"
|
||||
expected="$(head -n1 "$REPO_ROOT/VERSION")"
|
||||
if [ "$actual" != "$expected" ]; then
|
||||
echo "installer walk-up: actual=$actual expected=$expected"
|
||||
exit 1
|
||||
fi
|
||||
'
|
||||
|
||||
# ── 7. Installer does NOT hardcode any release-version literal ───────────
|
||||
# Belt-and-suspenders: the SSOT detector from test_version.sh flags any
|
||||
# X.Y.Z literal in production files. Run the same detector against the
|
||||
# installer and verify it passes. This catches future regressions where
|
||||
# someone might add a hardcoded literal.
|
||||
assert "installer passes SSOT detector (no X.Y.Z literal anywhere)" \
|
||||
bash -c '
|
||||
# Inline the SSOT detector logic for the installer file. We
|
||||
# only need to check for the literal; the full detector in
|
||||
# test_version.sh is more thorough.
|
||||
# Anchored regex: X.Y.Z literal with non-alphanumeric boundaries.
|
||||
version_pattern="(^|[^a-zA-Z0-9])(v?[0-9]+[.][0-9]+[.][0-9]+([-+][a-zA-Z0-9.-]+)*)([^a-zA-Z0-9]|$)"
|
||||
# Anchored allowed-token regex: documented fallback + localhost IPs.
|
||||
allowed_pattern="(^|[^a-zA-Z0-9])(0[.]0[.]0-unknown|127[.]0[.]0[.]1|127[.]0[.]1[.]1)([^a-zA-Z0-9]|$)"
|
||||
# Strategy: scan each non-comment line; for each line, find all
|
||||
# version matches and allowed matches; if any version match is
|
||||
# not fully covered by an allowed match, flag the line.
|
||||
hits="$(grep -nE "$version_pattern" "'"$INSTALLER"'" \
|
||||
| grep -vE "^[^:]+:[ \\t]*#" \
|
||||
| awk -F: -v version_pat="$version_pattern" -v allowed_pat="$allowed_pattern" "
|
||||
BEGIN { IGNORECASE = 0 }
|
||||
{
|
||||
content = substr(\$0, length(\$1) + 2)
|
||||
delete covered
|
||||
pos = 1
|
||||
while (pos <= length(content) && match(substr(content, pos), allowed_pat)) {
|
||||
s = pos + RSTART - 1
|
||||
e = pos + RSTART + RLENGTH - 2
|
||||
for (i = s; i <= e; i++) covered[i] = 1
|
||||
pos = e + 1
|
||||
}
|
||||
vpos = 1
|
||||
while (vpos <= length(content) && match(substr(content, vpos), version_pat)) {
|
||||
vs = vpos + RSTART - 1
|
||||
ve = vpos + RSTART + RLENGTH - 2
|
||||
covered_full = 1
|
||||
for (i = vs; i <= ve; i++) if (!covered[i]) { covered_full = 0; break }
|
||||
if (!covered_full) { print \$0; break }
|
||||
vpos = ve + 1
|
||||
}
|
||||
}
|
||||
")"
|
||||
if [ -n "$hits" ]; then
|
||||
echo "HARD-CODED RELEASE VERSIONS in installer:"
|
||||
echo "$hits"
|
||||
exit 1
|
||||
fi
|
||||
'
|
||||
|
||||
print_summary
|
||||
Reference in New Issue
Block a user