[grade=B] SSOT: VERSION single-source-of-truth across all 7 components
SamiOS CI / lint-and-test (push) Successful in 10s
SamiOS CI / lint-and-test (push) Successful in 10s
- Add VERSION at repo root (canonical) + airootfs mirror - Refactor samios CLI, voice-bridge, installer, profiledef, desktop-setup, PKGBUILD to derive VERSION from canonical file via walk-up or conditional read (no embedded release-version literals) - Makefile targets: version-set NEW=X (single file change), version-sync - tests/test_version.sh: 55 assertions locking the invariants Verified end-to-end: make version-set NEW=0.3.0-rc2 → all 7 components see 0.3.0-rc2; PKGBUILD transforms hyphen to underscore for Arch. Codex verdict: B (0 blocking issues, shippable).
This commit is contained in:
Executable
+805
@@ -0,0 +1,805 @@
|
||||
#!/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 <file>
|
||||
# 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 "<sha256> <filename>";
|
||||
# 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
|
||||
Reference in New Issue
Block a user