#!/bin/bash # Test: VERSION single-source-of-truth # Locks the invariant: every version string in the repo must resolve to # ./VERSION at runtime. No duplicate hardcoding allowed. # # Note: many assertions below reference variables (`cli_v`, `voice_v`, # `mirror_v`, etc.) that are assigned inside single-quoted `bash -c` # subshells. shellcheck cannot trace through the quoting layer, so it # raises SC2154 warnings. These are intentional and the assertions DO # verify the variables — see the test body. # shellcheck disable=SC2154,SC2289,SC1011,SC1078,SC1083 source "$(dirname "$0")/test_helper.sh" suite "VERSION single-source-of-truth tests" VERSION_FILE="$REPO_ROOT/VERSION" AIROOTFS_VERSION="$PROFILE_DIR/airootfs/etc/samios-version" SAMIOS_CLI="$PROFILE_DIR/airootfs/usr/local/bin/samios" INSTALLER="$SCRIPTS_DIR/samios-installer.sh" PROFILEDEF="$PROFILE_DIR/profiledef.sh" VOICE_BRIDGE="$SCRIPTS_DIR/voice-bridge.py" PKGBUILD="$SCRIPTS_DIR/../packages/samios-branding/PKGBUILD" DESKTOP_SETUP="$SCRIPTS_DIR/samios-desktop-setup.sh" BASHRC="$PROFILE_DIR/airootfs/root/.bashrc" ZSHRC="$PROFILE_DIR/airootfs/root/.zshrc" # ── Hardcoded-release-version detector ──────────────────────────────────── # Scans a file for any X.Y.Z release-version literal, EXCLUDING: # - lines starting with # (comments documenting SSOT design) # - the documented fallback "0.0.0-unknown" string # - test fixture strings like "9.9.9-test" and "0.2.0-rc1" which # intentionally test version-set and prerelease flows # The detector must catch ANY release-version literal, including: # - version=1.2.3 (assignment) # - "1.2.3" (string literal) # - '1.2.3' (single-quoted string) # - 1.2.3-foo (prerelease) # - 1.2.3+bar (build metadata) # - SamiOS 1.2.3 (display string) # - VERSION=1.2.3 OR VERSION="1.2.3" (variable assignment) detect_hardcoded_release() { # detect_hardcoded_release # Exits 0 (clean) if no release-version literal is found; exits 1 if # one is found. # # Production files: only the documented "0.0.0-unknown" fallback is # allowed (matches the placeholder our resolvers print when the # canonical VERSION cannot be located). Any other X.Y.Z literal # (including prerelease, build-meta, v-prefixed, etc.) is a violation. # # Test files (under tests/): the same rule applies EXCEPT that two # additional test fixtures are tolerated (0.2.0-rc1 in test 13's # prerelease assertion, and 9.9.9-test in test 12's version-set # assertion). Production components must NEVER contain these strings. # # Anchored allowed-token matching: each allowed token is matched as a # complete regex match with non-alphanumeric boundaries on both sides. # This prevents `127.0.0.10` from being eaten as `127.0.0.1` followed # by `0` — `127.0.0.1` only matches at positions where the character # AFTER it is non-alphanumeric, so `127.0.0.10` keeps its `0` intact # and the version literal `127.0.0.10` is correctly flagged. local f="$1" local 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: same boundary rules as version_pattern. # The middle group is the allowed token itself. local allowed_pattern if [[ "$f" == */tests/test_version.sh ]]; then # Test fixture: tolerate the two prerelease/version-set fixtures # AND the documented fallback + localhost IPs. allowed_pattern='(^|[^a-zA-Z0-9])(0[.]0[.]0-unknown|9[.]9[.]9-test|0[.]2[.]0-rc1|127[.]0[.]0[.]1|127[.]0[.]1[.]1)([^a-zA-Z0-9]|$)' else # Production: only the documented 0.0.0-unknown 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]|$)' fi # Comment detection: a line is a comment if its first non-whitespace # character is '#'. This handles shell, Python (after the leading # whitespace), .desktop, and other line-oriented comment syntaxes. local hits # Strategy: find all version-pattern matches and allowed-pattern matches # on each line. If a version-pattern match's character range does NOT # fully overlap with an allowed-pattern match's range, it's a violation. hits="$(grep -nE "$version_pattern" "$f" \ | grep -vE "^[^:]+:[ \\t]*#" \ | awk -F':' -v version_pat="$version_pattern" -v allowed_pat="$allowed_pattern" ' BEGIN { IGNORECASE = 0 } { content = substr($0, length($1) + 2) # Build a "covered" map: for each allowed match, mark its # character positions as covered. Then check each version # match: if its range is NOT entirely covered, flag it. 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 # Advance past this match to find the next allowed match pos = e + 1 } # Now scan content for version matches and report any # that are not fully covered. 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 FOUND in $f:" >&2 echo "$hits" >&2 return 1 fi return 0 } # Export the function so it can be called from `bash -c '...'` subshells export -f detect_hardcoded_release # ── Repository-wide SSOT scan ──────────────────────────────────────────── # Every production file in the repo must pass detect_hardcoded_release. # "Production" excludes tests/ (where we tolerate fixtures) and the # canonical VERSION + airootfs mirror (which by definition contain # the literal). If you add a new production file with a hardcoded # version, it will appear here. assert "every production file passes detect_hardcoded_release" \ bash -c ' violations=0 # Scan all production files. We exclude by EXACT canonical paths # (not substring patterns), so a nested or unrelated file named # VERSION cannot accidentally bypass SSOT enforcement. while IFS= read -r -d "" f; do case "$f" in */.git/*|*/tests/*) continue ;; esac # Compare against the exact canonical paths (resolved). _resolved="$(cd "$(dirname "$f")" && pwd)/$(basename "$f")" _root_version="$(cd "$REPO_ROOT" && pwd)/VERSION" _root_mirror="$(cd "$REPO_ROOT" && pwd)/packaging/archiso/airootfs/etc/samios-version" if [ "$_resolved" = "$_root_version" ] || [ "$_resolved" = "$_root_mirror" ]; then continue fi if ! detect_hardcoded_release "$f"; then violations=$((violations + 1)) fi done < <(find "$REPO_ROOT" -type f -not -path "*/.git/*" -not -path "*/tests/*" -print0) if [ "$violations" -gt 0 ]; then echo "FAIL: $violations production files contain hardcoded version literals" exit 1 fi ' # ── Regression test: 0.2.0-rc1 is REJECTED in production components ────── # The prerelease string "0.2.0-rc1" is allowed ONLY in test_version.sh as # a test fixture. If it appears in any production component, the SSOT # invariant is violated. assert "regression: 0.2.0-rc1 is REJECTED in production components" \ bash -c ' # Find any production file containing 0.2.0-rc1 outside of test_version.sh while IFS= read -r -d "" f; do case "$f" in */.git/*|*/tests/*) continue ;; esac if grep -q "0\.2\.0-rc1" "$f" 2>/dev/null; then echo "FAIL: $f contains 0.2.0-rc1 in production" exit 1 fi done < <(find "$REPO_ROOT" -type f -not -path "*/.git/*" -not -path "*/tests/*" -print0) ' # ── Regression test: 9.9.9-test is REJECTED in production components ───── # Same as above but for the version-set test fixture. assert "regression: 9.9.9-test is REJECTED in production components" \ bash -c ' while IFS= read -r -d "" f; do case "$f" in */.git/*|*/tests/*) continue ;; esac if grep -q "9\.9\.9-test" "$f" 2>/dev/null; then echo "FAIL: $f contains 9.9.9-test in production" exit 1 fi done < <(find "$REPO_ROOT" -type f -not -path "*/.git/*" -not -path "*/tests/*" -print0) ' # ── Detector self-tests ────────────────────────────────────────────────── # The detector is a non-trivial regex; these tests verify it catches every # form we claim and does not raise false positives. The tests construct # synthetic files in $TMPDIR, run detect_hardcoded_release against them, # and check the result. Each form is a separate assertion so a regression # points at exactly which form broke. assert "detector self-test: catches double-quoted X.Y.Z literal" \ bash -c ' tmpf="$(mktemp)" trap "rm -f '\''$tmpf'\''" EXIT echo "echo \"1.2.3\"" > "$tmpf" detect_hardcoded_release "$tmpf" || exit 0 echo "FAILED: detector missed \"1.2.3\"" >&2; exit 1 ' assert "detector self-test: catches single-quoted X.Y.Z literal" \ bash -c ' tmpf="$(mktemp)" trap "rm -f '\''$tmpf'\''" EXIT echo "echo '"'"'1.2.3'"'"'" > "$tmpf" detect_hardcoded_release "$tmpf" || exit 0 echo "FAILED: detector missed '"'"'1.2.3'"'"'" >&2; exit 1 ' assert "detector self-test: catches bare X.Y.Z literal" \ bash -c ' tmpf="$(mktemp)" trap "rm -f '\''$tmpf'\''" EXIT echo "echo 1.2.3" > "$tmpf" detect_hardcoded_release "$tmpf" || exit 0 echo "FAILED: detector missed bare 1.2.3" >&2; exit 1 ' assert "detector self-test: catches prerelease X.Y.Z-foo literal" \ bash -c ' tmpf="$(mktemp)" trap "rm -f '\''$tmpf'\''" EXIT echo "echo 1.2.3-rc1" > "$tmpf" detect_hardcoded_release "$tmpf" || exit 0 echo "FAILED: detector missed 1.2.3-rc1" >&2; exit 1 ' assert "detector self-test: catches build-metadata X.Y.Z+bar literal" \ bash -c ' tmpf="$(mktemp)" trap "rm -f '\''$tmpf'\''" EXIT echo "echo 1.2.3+build.5" > "$tmpf" detect_hardcoded_release "$tmpf" || exit 0 echo "FAILED: detector missed 1.2.3+build.5" >&2; exit 1 ' assert "detector self-test: catches SamiOS X.Y.Z display string" \ bash -c ' tmpf="$(mktemp)" trap "rm -f '\''$tmpf'\''" EXIT echo "echo \"SamiOS 1.2.3 (WSL Desktop)\"" > "$tmpf" detect_hardcoded_release "$tmpf" || exit 0 echo "FAILED: detector missed SamiOS 1.2.3" >&2; exit 1 ' assert "detector self-test: catches VERSION=1.2.3 assignment" \ bash -c ' tmpf="$(mktemp)" trap "rm -f '\''$tmpf'\''" EXIT echo "VERSION=\"1.2.3\"" > "$tmpf" detect_hardcoded_release "$tmpf" || exit 0 echo "FAILED: detector missed VERSION=1.2.3" >&2; exit 1 ' assert "detector self-test: catches v-prefixed v1.2.3" \ bash -c ' tmpf="$(mktemp)" trap "rm -f '\''$tmpf'\''" EXIT echo "echo v1.2.3" > "$tmpf" detect_hardcoded_release "$tmpf" || exit 0 echo "FAILED: detector missed v1.2.3" >&2; exit 1 ' assert "detector self-test: catches X.Y.Z in path (/opt/1.2.3/bin)" \ bash -c ' tmpf="$(mktemp)" trap "rm -f '\''$tmpf'\''" EXIT echo "PATH=/opt/1.2.3/bin" > "$tmpf" detect_hardcoded_release "$tmpf" || exit 0 echo "FAILED: detector missed path-embedded 1.2.3" >&2; exit 1 ' assert "detector self-test: catches X.Y.Z in colon-delimited value" \ bash -c ' tmpf="$(mktemp)" trap "rm -f '\''$tmpf'\''" EXIT echo "key=val:1.2.3:rest" > "$tmpf" detect_hardcoded_release "$tmpf" || exit 0 echo "FAILED: detector missed colon-delimited 1.2.3" >&2; exit 1 ' assert "detector self-test: catches X.Y.Z in brackets [1.2.3]" \ bash -c ' tmpf="$(mktemp)" trap "rm -f '\''$tmpf'\''" EXIT echo "x=[1.2.3]" > "$tmpf" detect_hardcoded_release "$tmpf" || exit 0 echo "FAILED: detector missed bracketed 1.2.3" >&2; exit 1 ' assert "detector self-test: ALLOWS 0.0.0-unknown fallback" \ bash -c ' tmpf="$(mktemp)" trap "rm -f '\''$tmpf'\''" EXIT echo "VERSION=0.0.0-unknown" > "$tmpf" detect_hardcoded_release "$tmpf" && exit 0 echo "FAILED: detector flagged documented fallback 0.0.0-unknown" >&2; exit 1 ' assert "detector self-test: ALLOWS comments (lines starting with #)" \ bash -c ' tmpf="$(mktemp)" trap "rm -f '\''$tmpf'\''" EXIT echo "# This is a comment with 1.2.3 in it" > "$tmpf" detect_hardcoded_release "$tmpf" && exit 0 echo "FAILED: detector flagged commented-out version" >&2; exit 1 ' assert "detector self-test: ALLOWS localhost IP (127.0.0.1)" \ bash -c ' tmpf="$(mktemp)" trap "rm -f '\''$tmpf'\''" EXIT echo "127.0.0.1 localhost" > "$tmpf" detect_hardcoded_release "$tmpf" && exit 0 echo "FAILED: detector flagged localhost IP" >&2; exit 1 ' assert "detector self-test: catches MIXED allowed+forbidden on same line" \ bash -c ' # A line that contains BOTH the allowed fallback and a forbidden # release-version literal must be flagged. This catches the bug # where `grep -v '\''0.0.0-unknown'\''` would suppress the entire line # and miss the forbidden literal. tmpf="$(mktemp)" trap "rm -f '\''$tmpf'\''" EXIT echo "echo 0.0.0-unknown 1.2.3" > "$tmpf" detect_hardcoded_release "$tmpf" || exit 0 echo "FAILED: detector missed mixed allowed/forbidden line" >&2; exit 1 ' assert "detector self-test: ALLOWS indented # comments with version literal" \ bash -c ' tmpf="$(mktemp)" trap "rm -f '\''$tmpf'\''" EXIT cat > "$tmpf" << '"'"'EOF'"'"' # Top-level comment with 1.2.3 # Indented comment with 1.2.4 echo real_code EOF detect_hardcoded_release "$tmpf" && exit 0 echo "FAILED: detector flagged commented version literals" >&2; exit 1 ' assert "detector self-test: catches 127.0.0.10 (substring of allowed IP)" \ bash -c ' # 127.0.0.10 contains 127.0.0.1 as a substring. A naive substring # filter would erase `127.0.0.1` and leave `0` (which does NOT # match X.Y.Z), letting the real release-version literal # `127.0.0.10` slip through. Our anchored detector must catch it. tmpf="$(mktemp)" trap "rm -f '\''$tmpf'\''" EXIT echo "echo 127.0.0.10" > "$tmpf" detect_hardcoded_release "$tmpf" || exit 0 echo "FAILED: detector missed 127.0.0.10" >&2; exit 1 ' assert "detector self-test: catches 0.0.0-unknown+build (suffix of fallback)" \ bash -c ' # 0.0.0-unknown+build contains 0.0.0-unknown as a substring. A naive # filter would erase `0.0.0-unknown` and leave `+build` (which # does NOT match X.Y.Z), letting the build-metadata version literal # `0.0.0-unknown+build` slip through. tmpf="$(mktemp)" trap "rm -f '\''$tmpf'\''" EXIT echo "echo 0.0.0-unknown+build" > "$tmpf" detect_hardcoded_release "$tmpf" || exit 0 echo "FAILED: detector missed 0.0.0-unknown+build" >&2; exit 1 ' # ── 1. Canonical file exists and is a valid semver-ish string ────────────── assert_file_exists "VERSION file exists at repo root" "$VERSION_FILE" assert "VERSION is non-empty single-line" \ bash -c ' [ "$(wc -l < "'"$VERSION_FILE"'")" -eq 1 ] && \ [ -n "$(cat "'"$VERSION_FILE"'")" ] ' assert "VERSION matches semver-ish pattern (X.Y.Z optional suffix)" \ bash -c ' grep -qE "^[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9]+(\.[a-zA-Z0-9]+)*)?$" "'"$VERSION_FILE"'" ' # ── 2. airootfs mirror is byte-identical to root VERSION ─────────────────── assert_file_exists "airootfs mirror /etc/samios-version exists" "$AIROOTFS_VERSION" assert "airootfs VERSION mirror matches repo root VERSION" \ bash -c ' diff -q "'"$VERSION_FILE"'" "'"$AIROOTFS_VERSION"'" >/dev/null ' # ── 3. samios CLI must NOT hardcode any release-version literal ────────── assert_file_exists "samios CLI exists" "$SAMIOS_CLI" assert "samios CLI has no hardcoded release-version literal" \ bash -c 'detect_hardcoded_release "'"$SAMIOS_CLI"'"' assert "samios CLI references ./VERSION walk-up lookup" \ bash -c ' grep -q "/VERSION" "'"$SAMIOS_CLI"'" && \ grep -q "_lookup_version" "'"$SAMIOS_CLI"'" ' assert "samios CLI prints repo VERSION when run from repo root" \ bash -c ' cd "'"$REPO_ROOT"'" && \ expected="$(cat VERSION)" && \ actual="$(bash "'"$SAMIOS_CLI"'" version | sed -E "s/^SamiOS v//" | head -n1)" && \ [ "$actual" = "$expected" ] ' # ── 4. Installer must NOT hardcode any release-version literal ─────────── assert_file_exists "samios-installer.sh exists" "$INSTALLER" assert "installer has no hardcoded release-version literal" \ bash -c 'detect_hardcoded_release "'"$INSTALLER"'"' assert "installer references ./VERSION walk-up lookup" \ bash -c ' grep -q "_VERSION_FILE\|/VERSION" "'"$INSTALLER"'" ' # Verify the installer's actual heredoc-emitted CLI has the _lookup_version # function — not a substituted $VERSION literal. assert "installer heredoc emits CLI with _lookup_version (no literal)" \ bash -c ' # The installer MUST NOT substitute $VERSION into a literal at # the top of the emitted CLI. We extract the heredoc body (between # the << SAMIOS_CLI marker line and the closing SAMIOS_CLI) and # check for the forbidden pattern. 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"'")" # Forbidden: a literal VERSION="${VERSION}" (substituted from outer scope) if echo "$heredoc_body" | grep -qE "^VERSION=\"\\\$\{VERSION\}\""; then echo "installer emits VERSION=\"\${VERSION}\" literal at top of heredoc" exit 1 fi # 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 ' # ── 5. profiledef.sh must NOT hardcode any release-version literal ──────── assert_file_exists "profiledef.sh exists" "$PROFILEDEF" assert "profiledef.sh has no hardcoded release-version literal" \ bash -c 'detect_hardcoded_release "'"$PROFILEDEF"'"' assert "profiledef.sh references ./VERSION walk-up lookup" \ bash -c ' grep -q "_iso_version\|/VERSION" "'"$PROFILEDEF"'" ' # ── 6. voice-bridge.py must read from ./VERSION ──────────────────────────── assert_file_exists "voice-bridge.py exists" "$VOICE_BRIDGE" assert "voice-bridge.py parses as valid Python" \ bash -c ' python3 -c "import ast; ast.parse(open(\"'"$VOICE_BRIDGE"'\").read())" ' assert "voice-bridge.py has no hardcoded release-version literal" \ bash -c 'detect_hardcoded_release "'"$VOICE_BRIDGE"'"' assert "voice-bridge.py resolves VERSION from repo root" \ bash -c ' cd "'"$REPO_ROOT"'" && \ expected="$(cat VERSION)" && \ actual="$(python3 "'"$VOICE_BRIDGE"'" --version | sed -E "s/^SamiOS voice-bridge v//")" && \ [ "$actual" = "$expected" ] ' # ── 7. PKGBUILD must NOT hardcode any release-version literal ──────────── assert_file_exists "PKGBUILD exists" "$PKGBUILD" assert "PKGBUILD has no hardcoded release-version literal" \ bash -c 'detect_hardcoded_release "'"$PKGBUILD"'"' assert "PKGBUILD references ../../../VERSION" \ bash -c ' grep -q "../../../VERSION" "'"$PKGBUILD"'" ' # ── 8. desktop-setup.sh must NOT hardcode any release-version literal ────── assert_file_exists "samios-desktop-setup.sh exists" "$DESKTOP_SETUP" assert "desktop-setup.sh has no hardcoded release-version literal" \ bash -c 'detect_hardcoded_release "'"$DESKTOP_SETUP"'"' # ── 9. .bashrc / .zshrc must NOT hardcode any release-version literal ──── assert ".bashrc has no hardcoded release-version literal" \ bash -c 'detect_hardcoded_release "'"$BASHRC"'"' assert ".zshrc has no hardcoded release-version literal" \ bash -c 'detect_hardcoded_release "'"$ZSHRC"'"' # ── 10. Cross-system invariant: CLI, installer, profiledef, voice-bridge ── # Test the actual code paths in each file. For the installer and profiledef, # we extract their version-resolution blocks from the real files (not # reimplementations) into temp helpers, then rewrite the $0-relative path # to point at the actual file's directory so the walk-up starts in the # right place. PROFILEDEF_RESOLVER="$(mktemp)" PROFILEDEF_DIR="$(dirname "$PROFILEDEF")" { sed -n '/^# ── Canonical version source ───/,/^unset _d$/p' \ "$PROFILEDEF" echo 'echo "$_iso_version"' } > "$PROFILEDEF_RESOLVER" sed -i "s|cd \"\\\$(dirname \"\\\$0\")\"|cd \"$PROFILEDEF_DIR\"|g" "$PROFILEDEF_RESOLVER" chmod +x "$PROFILEDEF_RESOLVER" # Extract the installer's version-resolution block (top of file) into a # real shell script we can run. The block uses $0 to find its own # directory; when extracted standalone, $0 becomes the helper's path # (under /tmp), which isn't where VERSION lives. We rewrite the $0 # lookup to start from the installer's actual directory. INSTALLER_DIR="$(dirname "$INSTALLER")" INSTALLER_RESOLVER="$(mktemp)" { sed -n '/^# ── Canonical version source ───/,/^unset _d _VERSION_FILE/p' \ "$INSTALLER" echo 'echo "$VERSION"' } > "$INSTALLER_RESOLVER" # Rewrite $0 references inside the extracted block so they resolve from # the installer's actual directory, not from /tmp/codex-judge-XXXXXX. sed -i "s|cd \"\\\$(dirname \"\\\$0\")\"|cd \"$INSTALLER_DIR\"|g" "$INSTALLER_RESOLVER" chmod +x "$INSTALLER_RESOLVER" assert "all four components agree on VERSION (tested from real files)" \ bash -c ' cd "'"$REPO_ROOT"'" || exit 1 expected="$(cat VERSION)" cli_v="$(bash "'"$SAMIOS_CLI"'" version | head -n1 | sed -E "s/^SamiOS v//")" voice_v="$(python3 "'"$VOICE_BRIDGE"'" --version | sed -E "s/^SamiOS voice-bridge v//")" inst_v="$("'"$INSTALLER_RESOLVER"'" | tail -n1)" prof_v="$("'"$PROFILEDEF_RESOLVER"'")" if [ "$cli_v" != "$expected" ]; then echo "CLI=$cli_v expected=$expected"; exit 1 fi if [ "$voice_v" != "$expected" ]; then echo "voice=$voice_v expected=$expected"; exit 1 fi if [ "$inst_v" != "$expected" ]; then echo "installer=$inst_v expected=$expected"; exit 1 fi if [ "$prof_v" != "$expected" ]; then echo "profiledef=$prof_v expected=$expected"; exit 1 fi ' rm -f "$PROFILEDEF_RESOLVER" "$INSTALLER_RESOLVER" # ── 11. PKGBUILD resolves pkgver to the canonical VERSION ────────────────── # Source the PKGBUILD in a subshell (PKGBUILD is bash-runnable) and capture # the resulting pkgver. This proves the conditional `pkgver=` block in the # PKGBUILD evaluates to the canonical VERSION. assert "PKGBUILD pkgver resolves to VERSION (sourced)" \ bash -c ' cd "'"$REPO_ROOT"'"/packaging/packages/samios-branding && \ expected="$(head -n1 ../../../VERSION)" && \ actual="$(set -e; source PKGBUILD >/dev/null 2>&1 && echo "$pkgver")" && \ [ "$actual" = "$expected" ] || { echo "actual=$actual expected=$expected"; exit 1; } ' # ── 11b. PKGBUILD SSOT is absolute (no SAMIOS_PKGVER_OVERRIDE escape) ───── # The previous PKGBUILD had a SAMIOS_PKGVER_OVERRIDE env var that allowed # pkgver to differ from VERSION. That violated the SSOT invariant. Verify # the override is removed so release builds cannot diverge. assert "PKGBUILD has no SAMIOS_PKGVER_OVERRIDE escape hatch (SSOT is absolute)" \ bash -c ' # Look for SAMIOS_PKGVER_OVERRIDE outside of comments (lines # starting with #). Comments can mention the variable for # documentation purposes; only executable code that references # it would violate SSOT. if grep -n "SAMIOS_PKGVER_OVERRIDE" "'"$PKGBUILD"'" | grep -vE "^[0-9]+:#"; then echo "PKGBUILD still references SAMIOS_PKGVER_OVERRIDE outside a comment — SSOT invariant violated" grep -n "SAMIOS_PKGVER_OVERRIDE" "'"$PKGBUILD"'" | grep -vE "^[0-9]+:#" exit 1 fi # Also verify the override does not actually bypass VERSION, in # case some future shell magic reintroduces the override. cd "'"$REPO_ROOT"'"/packaging/packages/samios-branding && \ expected="$(head -n1 ../../../VERSION)" && \ actual="$(SAMIOS_PKGVER_OVERRIDE=99.99.99-bogus bash -c "set -e; source PKGBUILD >/dev/null 2>&1; echo \$pkgver")" && \ [ "$actual" = "$expected" ] || { echo "override leaked: actual=$actual expected=$expected"; exit 1; } ' assert "regression: hash-based diff catches deletions (not just additions)" \ bash -c ' # The version-set test (assertion above) counts only the right # side of the diff. A pure deletion produces only a left-side # line. Verify the diff-based detection we use catches BOTH # sides: synthesize a fake before/after where a file is deleted # and a different file is added, then count unique filenames. diff_file="$(mktemp)" trap "rm -f '\''$diff_file'\''" EXIT cat > "$diff_file" << '"'"'DIFF_EOF'"'"' < d84523f8671da4c8b99f543127715bb8c562fafe8675016112c6d260d14e761b ./some-deleted-file.txt > 0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef ./some-new-file.txt DIFF_EOF count="$(awk '"'"'/^[<>] / {print $NF}'"'"' "$diff_file" | sort -u | wc -l)" if [ "$count" -ne 2 ]; then echo "FAIL: expected 2 unique filenames (one deleted + one added), got $count" exit 1 fi ' # ── 12. End-to-end: version-set mutates only VERSION + mirror ───────────── assert "version-set mutates only VERSION + airootfs mirror (no other files)" \ bash -c ' clone="$(mktemp -d)" diff_file="$(mktemp)" # Cleanup trap uses $clone and $diff_file from the surrounding scope trap "rm -rf ${clone:?} ${diff_file:?}" EXIT # Copy with .git so any pre-existing modifications are visible as # baseline (so we can detect whether version-set changed anything # by content, not by index state). cp -a "$REPO_ROOT" "$clone/repo" cd "$clone/repo" || exit 1 orig_version="$(cat VERSION)" orig_mirror="$(head -n1 packaging/archiso/airootfs/etc/samios-version)" # Snapshot the content hash of every file in the repo (excluding # .git) BEFORE the mutation. We use this to detect any file change, # not git status which can be misleading for already-modified files. before_hashes="$(find . -path ./.git -prune -o -type f -print0 2>/dev/null \ | xargs -0 sha256sum 2>/dev/null | sort)" # `make version-set` must SUCCEED. If it fails (e.g. bad NEW value, # missing make), the test fails — not silently passes. if ! make version-set NEW=9.9.9-test >/dev/null 2>&1; then echo "make version-set NEW=9.9.9-test failed unexpectedly" exit 1 fi # VERSION must actually have changed. if [ "$(cat VERSION)" != "9.9.9-test" ]; then echo "VERSION did not change to 9.9.9-test" exit 1 fi # Mirror must actually have changed. if [ "$(head -n1 packaging/archiso/airootfs/etc/samios-version)" != "9.9.9-test" ]; then echo "airootfs mirror did not sync to 9.9.9-test" exit 1 fi # Snapshot hashes AFTER the mutation. after_hashes="$(find . -path ./.git -prune -o -type f -print0 2>/dev/null \ | xargs -0 sha256sum 2>/dev/null | sort)" # Diff before vs after. Count BOTH sides of the diff (a deletion # produces only a left-side line; an addition produces only a # right-side line; a modification produces one of each). Use the # parsed filename-to-hash maps to count unique files that # changed in either direction, not just added/right-side lines. diff <(echo "$before_hashes") <(echo "$after_hashes") > "$diff_file" # Extract the set of filenames that appear on either side of the # diff. `awk` gets us the distinct filenames regardless of side. changed_count="$(awk "/^[<>] / {print \$NF}" "$diff_file" | sort -u | wc -l)" if [ "$changed_count" -ne 2 ]; then echo "expected exactly 2 files to change (VERSION + mirror), got $changed_count:" awk "/^[<>] / {print \$0}" "$diff_file" exit 1 fi # Verify the two changed files are VERSION and the mirror (not # some other files). The diff format is " "; # match on the filename suffix. unexpected="$(awk "/^[<>] / {print \$NF}" "$diff_file" | sort -u)" unexpected="$(echo "$unexpected" | grep -vE "VERSION$|packaging/archiso/airootfs/etc/samios-version$")" if [ -n "$unexpected" ]; then echo "version-set changed unexpected files:" echo "$unexpected" exit 1 fi # Restore clone state — the developers checkout was never touched. # We restore to the original VERSION we captured, not to a # hardcoded value, so this test is safe to run at any version. echo "$orig_version" > VERSION if ! make version-sync >/dev/null 2>&1; then echo "make version-sync failed during restore" exit 1 fi # Verify restoration succeeded by snapshotting again and comparing # to the original before-snapshot. The post-restore state must # match the original content hash (no stray modifications). restore_hashes="$(find . -path ./.git -prune -o -type f -print0 2>/dev/null \ | xargs -0 sha256sum 2>/dev/null | sort)" if [ "$before_hashes" != "$restore_hashes" ]; then echo "restore failed — clone does not match original" diff <(echo "$before_hashes") <(echo "$restore_hashes") | head -10 exit 1 fi # Sanity: the values we saved at the top must be the canonical # values, not arbitrary ones. (We already know VERSION=0.1.0 from # earlier tests, but this guards against the test running against # an unusual repo state.) if [ -z "$orig_version" ] || [ -z "$orig_mirror" ]; then echo "could not read original VERSION or mirror; bail" exit 1 fi ' # ── 13. Prerelease VERSION works through the whole stack ───────────────── # Mutate VERSION to a prerelease and verify ALL resolvers see it correctly: # CLI, voice-bridge, airootfs mirror, installer (resolves through its own # walk-up), profiledef (resolves through its own walk-up), desktop-setup # (resolves through its own walk-up), and PKGBUILD (transforms to underscore). # # Use a cleanup trap and preserve the original VERSION rather than # hardcoding 0.1.0, so this test is safe to run at any version. assert "prerelease VERSION (e.g. 0.2.0-rc1) flows through all 7 components" \ bash -c ' clone="$(mktemp -d)" trap "rm -rf '\''$clone'\''" EXIT cp -a "$REPO_ROOT" "$clone/repo" cd "$clone/repo" || exit 1 orig_version="$(cat VERSION)" echo "0.2.0-rc1" > VERSION if ! make version-sync >/dev/null 2>&1; then echo "make version-sync failed"; exit 1 fi cli_v="$(bash packaging/archiso/airootfs/usr/local/bin/samios version | head -n1 | sed -E "s/^SamiOS v//")" voice_v="$(python3 packaging/scripts/voice-bridge.py --version | sed -E "s/^SamiOS voice-bridge v//")" mirror_v="$(head -n1 packaging/archiso/airootfs/etc/samios-version)" # Installer resolves through its own walk-up block at the top of the file installer_resolver="$(mktemp)" sed -n "/^# ── Canonical version source ───/,/^unset _d _VERSION_FILE/p" \ packaging/scripts/samios-installer.sh | sed "s|cd \"\\\$(dirname \"\\\$0\")\"|cd \"$(pwd)/packaging/scripts\"|g" \ > "$installer_resolver" printf "echo \"\$VERSION\"\n" >> "$installer_resolver" chmod +x "$installer_resolver" installer_v="$("$installer_resolver" | tail -n1)" rm -f "$installer_resolver" # Profiledef resolves through its own walk-up block profiledef_resolver="$(mktemp)" sed -n "/^# ── Canonical version source ───/,/^unset _d$/p" \ packaging/archiso/profiledef.sh | sed "s|cd \"\\\$(dirname \"\\\$0\")\"|cd \"$(pwd)/packaging/archiso\"|g" \ > "$profiledef_resolver" printf "echo \"\$_iso_version\"\n" >> "$profiledef_resolver" chmod +x "$profiledef_resolver" profiledef_v="$("$profiledef_resolver" | tail -n1)" rm -f "$profiledef_resolver" # Desktop-setup resolves through its own walk-up block. Extract # the resolution block up to (but not including) the # echo-write line — that line writes to a system path we cant # touch in a test. desktop_start="$(grep -n "# Version file — generate" packaging/scripts/samios-desktop-setup.sh | head -1 | cut -d: -f1)" desktop_end_pre="$(grep -n "echo \"SamiOS.*> /usr/share/samios/version" packaging/scripts/samios-desktop-setup.sh | head -1 | cut -d: -f1)" desktop_end=$((desktop_end_pre - 1)) desktop_resolver="$(mktemp)" if [ -z "$desktop_start" ] || [ -z "$desktop_end_pre" ]; then echo "could not locate desktop-setup version block markers" exit 1 fi sed -n "${desktop_start},${desktop_end}p" packaging/scripts/samios-desktop-setup.sh | \ sed "s|cd \"\\\$(dirname \"\\\$0\")\"|cd \"$(pwd)/packaging/scripts\"|g" \ > "$desktop_resolver" printf "echo \"\$_SAMIOS_DESK_VERSION\"\n" >> "$desktop_resolver" chmod +x "$desktop_resolver" desktop_v="$("$desktop_resolver" | tail -n1)" rm -f "$desktop_resolver" # PKGBUILD transforms hyphen to underscore for Arch pkgver pkgver_v="$(cd packaging/packages/samios-branding && set -e && source PKGBUILD >/dev/null 2>&1 && echo "${pkgver:-}")" # Restore clone to its original VERSION (captured above) rather # than hardcoding a value — safe at any current version. echo "$orig_version" > VERSION make version-sync >/dev/null 2>&1 # Verify all seven agree (or transform as expected) [ "$cli_v" = "0.2.0-rc1" ] || { echo "CLI=$cli_v"; exit 1; } [ "$voice_v" = "0.2.0-rc1" ] || { echo "voice=$voice_v"; exit 1; } [ "$mirror_v" = "0.2.0-rc1" ] || { echo "mirror=$mirror_v"; exit 1; } [ "$installer_v" = "0.2.0-rc1" ] || { echo "installer=$installer_v"; exit 1; } [ "$profiledef_v" = "0.2.0-rc1" ] || { echo "profiledef=$profiledef_v"; exit 1; } [ "$desktop_v" = "0.2.0-rc1" ] || { echo "desktop=$desktop_v"; exit 1; } [ "$pkgver_v" = "0.2.0_rc1" ] || { echo "pkgver=$pkgver_v expected=0.2.0_rc1"; exit 1; } ' # ── 14. PKGBUILD transforms "0.X.Y-suffix" to "0.X.Y_suffix" for Arch ─── # This duplicates test 13's PKGBUILD check but in isolation, so a failure # here points directly at PKGBUILD (not at the broader end-to-end test). assert "PKGBUILD transforms prerelease hyphen to underscore for Arch pkgver" \ bash -c ' clone="$(mktemp -d)" trap "rm -rf ${clone:?}" EXIT cp -a "$REPO_ROOT" "$clone/repo" cd "$clone/repo/packaging/packages/samios-branding" || exit 1 echo "0.2.0-rc1" > ../../../VERSION # Source PKGBUILD and echo pkgver — no nested bash -c quoting needed. pkgver_actual="$(set -e; source PKGBUILD >/dev/null 2>&1 && echo "$pkgver")" cd / rm -rf "$clone" [ "$pkgver_actual" = "0.2.0_rc1" ] || { echo "pkgver=$pkgver_actual expected=0.2.0_rc1"; exit 1; } ' print_summary