diff --git a/packaging/version.sh b/packaging/version.sh index d8afe9f..ac63984 100755 --- a/packaging/version.sh +++ b/packaging/version.sh @@ -39,6 +39,25 @@ # field, never in the version — note 3127 §7, and rule 149. set -eu +# ANCHOR AT THE REPO ROOT BEFORE ANYTHING ELSE. +# +# `git log -- ` resolves pathspecs relative to the CURRENT DIRECTORY, not to +# the repo root. Callers run from wherever suits them — the desktop build from +# `desktop/src-tauri`, the Android build from `android`, the manifest job from the +# root — so without this the same request answers differently per caller. +# +# It is not a tidy failure. Measured on run 4796, one push produced THREE versions: +# the desktop build (cwd `desktop/src-tauri`) said 1.0.3494522, while the pacman +# packager and the manifest job both said 1.0.3502131. The build's pathspec had +# matched `desktop/src-tauri/Cargo.toml` — a real file — so git returned the newest +# commit touching THAT, six days stale. Non-empty, so the guard below could not fire; +# the manifest then found no bundle matching its own answer and the lane went red for +# a reason two steps removed from the cause. +# +# The Android job failed loudly in the same run only because its pathspec happened to +# match nothing from `android/`. Same bug, louder symptom, pure luck. +cd "$(git rev-parse --show-toplevel)" + # 2020-01-01T00:00:00Z. The counter epoch, and it must NEVER move: shifting it # renumbers every artifact downwards, which is the one direction you cannot recover # from (note 3127 §6.4). @@ -115,6 +134,28 @@ minutes_since_epoch() { echo $(( ($1 - EPOCH) / 60 )); } what="${1:?usage: version.sh }" artifact="${2:?usage: version.sh }" +# VALIDATED HERE, in the parent shell, and not left to `paths_for`'s default arm. +# +# Third instance of one trap in this script, so it is worth stating plainly: `exit` +# inside a function called as `$(...)` ends the SUBSHELL, not the script. `paths_for` +# is reached through `$(paths_for "$1")`, so its `exit 2` printed the error and +# returned an EMPTY pathspec — and an empty pathspec matches everything, so +# `version.sh display nope` answered `2026.08.28.0900` and exited 0. A confident +# version for an artifact that does not exist. +# +# The other two were the shallow-clone guard on the `key` path (emitted +# `1.0.-26297280`, exit 0) and the same guard on `display` (which failed only because +# `date` then choked on an empty string — luck, not design). Each was found by a +# different mechanism; none by reading the code. If you add a guard to this file, +# make sure it runs where the script does. +case "$artifact" in + desktop|android|server) : ;; + *) + echo "version.sh: unknown artifact '$artifact' (want desktop, android or server)" >&2 + exit 2 + ;; +esac + case "$what" in paths) paths_for "$artifact" diff --git a/tests/test_versioning.py b/tests/test_versioning.py index ead2e2a..6e5307d 100644 --- a/tests/test_versioning.py +++ b/tests/test_versioning.py @@ -59,10 +59,11 @@ def commit(repo: Path, path: str, when: int) -> None: ) -def version(repo: Path, what: str, artifact: str) -> str: +def version(repo: Path, what: str, artifact: str, *, subdir: str = "") -> str: return subprocess.run( ["sh", str(SCRIPT), what, artifact], - cwd=repo, check=True, capture_output=True, text=True, + cwd=repo / subdir if subdir else repo, + check=True, capture_output=True, text=True, ).stdout.strip() @@ -134,6 +135,37 @@ def test_the_build_recipe_is_in_the_set(repo: Path) -> None: assert version(repo, "display", "desktop") == "2026.08.28.1000" +# --- where it is called from ------------------------------------------------- + +@pytest.mark.parametrize("subdir", ["", "desktop/src-tauri", "android", "core"]) +def test_the_answer_does_not_depend_on_the_caller_s_directory(repo: Path, subdir: str) -> None: + """`git log -- ` resolves pathspecs relative to the CURRENT DIRECTORY. + Every caller runs from somewhere different — the desktop build from + `desktop/src-tauri`, the Android build from `android`, the manifest from the root + — so without an anchor the same request answers differently per caller. + + This is not hypothetical and it is not a loud failure. Run 4796 produced THREE + versions from one push: the desktop build said 1.0.3494522 while the manifest and + the pacman packager said 1.0.3502131, because the build's pathspec matched + `desktop/src-tauri/Cargo.toml` — a real file, six days stale. Non-empty, so the + shallow-clone guard could not fire. The Android job failed loudly in the same run + only because ITS pathspec happened to match nothing; same bug, luckier symptom.""" + (repo / "desktop/src-tauri").mkdir(parents=True, exist_ok=True) + (repo / "core").mkdir(parents=True, exist_ok=True) + assert version(repo, "display", "desktop", subdir=subdir) == "2026.08.28.0900" + assert version(repo, "key", "desktop", subdir=subdir) == version(repo, "key", "desktop") + + +def test_every_artifact_agrees_across_directories(repo: Path) -> None: + """The property the manifest job actually depends on: the value the bundle was + built with and the value the manifest looks for must be the same string, and they + are computed by different jobs in different directories.""" + for artifact in ("desktop", "android", "server"): + root = version(repo, "display", artifact) + assert version(repo, "display", artifact, subdir="desktop/src-tauri") == root + assert version(repo, "display", artifact, subdir="android") == root + + # --- the ordering keys ------------------------------------------------------- def test_the_desktop_key_is_valid_semver(repo: Path) -> None: @@ -182,10 +214,19 @@ def test_the_server_has_no_ordering_key(repo: Path) -> None: # --- failing loudly ---------------------------------------------------------- -def test_an_unknown_artifact_is_rejected(repo: Path) -> None: - r = subprocess.run(["sh", str(SCRIPT), "display", "nope"], +@pytest.mark.parametrize("what", ["display", "key", "paths"]) +def test_an_unknown_artifact_is_rejected(repo: Path, what: str) -> None: + """It was not. `paths_for`'s `exit 2` ran inside `$(paths_for ...)`, so it printed + the error, returned an EMPTY pathspec — which matches everything — and answered + `2026.08.28.0900` with exit 0. A confident version for an artifact that does not + exist, which is the worst kind of wrong for a value nothing else can contradict. + + Asserting on stdout as well as the exit code, because the exit code alone passed + for `display` in the first version of this file while stdout carried a lie.""" + r = subprocess.run(["sh", str(SCRIPT), what, "nope"], cwd=repo, capture_output=True, text=True) - assert r.returncode != 0 + assert r.returncode != 0, f"{what} exited 0 with stdout={r.stdout!r}" + assert r.stdout.strip() == "", f"{what} emitted a value for a bogus artifact: {r.stdout!r}" @pytest.mark.parametrize("what", ["display", "key"])