versioning: anchor at the repo root — a pathspec is relative to the caller's cwd
CI & Build / Build now, or wait for Android? (push) Successful in 3s
CI & Build / Python lint (push) Successful in 4s
CI & Build / TypeScript typecheck (push) Successful in 6s
CI & Build / Python tests (push) Successful in 10s
CI & Build / integration (push) Successful in 14s
CI & Build / Build & push image (push) Successful in 15s
CI & Build / Build now, or wait for Android? (push) Successful in 3s
CI & Build / Python lint (push) Successful in 4s
CI & Build / TypeScript typecheck (push) Successful in 6s
CI & Build / Python tests (push) Successful in 10s
CI & Build / integration (push) Successful in 14s
CI & Build / Build & push image (push) Successful in 15s
Three failures on c504433, two root causes, and the interesting one is that
`git log -- <paths>` resolves pathspecs against the CURRENT DIRECTORY.
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 one push produced THREE versions:
desktop build 1.0.3494522 <- six days stale
pacman packager 1.0.3502131
manifest job 1.0.3502131
The build's pathspec had matched `desktop/src-tauri/Cargo.toml` — a real file
— so git answered with the newest commit touching THAT. Non-empty, so the
shallow-clone guard could not fire; the manifest then found no bundle matching
its own answer and the lane went red two steps from the cause. The Android job
failed loudly in the same run only because ITS pathspec happened to match
nothing from `android/`. Same bug, luckier symptom.
The script `cd`s to `git rev-parse --show-toplevel` before doing anything now,
and the test asserts every artifact answers identically from four directories.
## And a third instance of the trap that bit yesterday
The unit test caught it: `version.sh display nope` printed "unknown artifact"
to stderr and then answered `2026.08.28.0900` with exit 0. `paths_for` is
reached through `$(paths_for "$1")`, so its `exit 2` ended the subshell,
returned an EMPTY pathspec — and an empty pathspec matches everything.
That is now three occurrences of one mistake in one file: the shallow-clone
guard on `key` (emitted `1.0.-26297280`, exit 0), the same guard on `display`
(which failed only because `date` then choked on the empty string), and this.
Each was found by a different mechanism and none by reading the code. The
artifact is validated in the parent shell now, and the file says so where the
next guard would be written.
Both tests assert on STDOUT as well as the exit code. The exit code alone
passed for `display nope` while stdout carried a lie.
#3144
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -39,6 +39,25 @@
|
|||||||
# field, never in the version — note 3127 §7, and rule 149.
|
# field, never in the version — note 3127 §7, and rule 149.
|
||||||
set -eu
|
set -eu
|
||||||
|
|
||||||
|
# ANCHOR AT THE REPO ROOT BEFORE ANYTHING ELSE.
|
||||||
|
#
|
||||||
|
# `git log -- <paths>` 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
|
# 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
|
# renumbers every artifact downwards, which is the one direction you cannot recover
|
||||||
# from (note 3127 §6.4).
|
# from (note 3127 §6.4).
|
||||||
@@ -115,6 +134,28 @@ minutes_since_epoch() { echo $(( ($1 - EPOCH) / 60 )); }
|
|||||||
what="${1:?usage: version.sh <display|key|paths> <desktop|android|server>}"
|
what="${1:?usage: version.sh <display|key|paths> <desktop|android|server>}"
|
||||||
artifact="${2:?usage: version.sh <display|key|paths> <desktop|android|server>}"
|
artifact="${2:?usage: version.sh <display|key|paths> <desktop|android|server>}"
|
||||||
|
|
||||||
|
# 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
|
case "$what" in
|
||||||
paths)
|
paths)
|
||||||
paths_for "$artifact"
|
paths_for "$artifact"
|
||||||
|
|||||||
@@ -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(
|
return subprocess.run(
|
||||||
["sh", str(SCRIPT), what, artifact],
|
["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()
|
).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"
|
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 -- <paths>` 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 -------------------------------------------------------
|
# --- the ordering keys -------------------------------------------------------
|
||||||
|
|
||||||
def test_the_desktop_key_is_valid_semver(repo: Path) -> None:
|
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 ----------------------------------------------------------
|
# --- failing loudly ----------------------------------------------------------
|
||||||
|
|
||||||
def test_an_unknown_artifact_is_rejected(repo: Path) -> None:
|
@pytest.mark.parametrize("what", ["display", "key", "paths"])
|
||||||
r = subprocess.run(["sh", str(SCRIPT), "display", "nope"],
|
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)
|
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"])
|
@pytest.mark.parametrize("what", ["display", "key"])
|
||||||
|
|||||||
Reference in New Issue
Block a user