next-version-code.sh: derive the candidate from the commit, not the build clock
fdroid-repo-ci#6's "Superseded" sections change the rule: the candidate is now the committer date of the commit being built, clamped on both sides, rather than whatever minute the runner happened to pick the job up in. Backwards: rebase, amend and cherry-pick can produce a commit older than one already published, so the clamp keys off the committer date, not the author date -- author date survives a rebase unchanged and would replay a stale, already-superseded number. Forwards: a commit's timestamp is attacker- and typo-controlled and the number only ever rises, so a bad one can never be walked back. A value placed just below the int32 ceiling would still publish and then no later build could ever exceed it -- the app becomes permanently un-updatable. TOLERANCE absorbs honest clock skew and the clamp warns loudly rather than rejecting, since rejecting would block a legitimate build over a few minutes of skew while buying nothing the clamp doesn't already cover. next_version_code.py now takes the committer timestamp as a third argument (Unix seconds, what `git log -1 --format=%ct` prints) instead of reading the system clock, keeping it pure and testable without a git checkout. next-version-code.sh, README.md and action.yml updated to match. Granularity stays minutes: concurrency is handled separately (a workflow-level concurrency group, plus publish.sh's refuse-on-existing), so it is now a pure headroom choice. Tests (test_next_version_code.py, test_next_version_code.sh): a normal commit yields its own minute, a commit older than the published maximum still yields published+1, a future-dated commit within tolerance passes through untouched while one beyond it is clamped and warns, a commit dated near the int32 ceiling does not produce a near-int32 result, and a dedicated property test checks the result always strictly exceeds every versionCode in the fixture across all of the above. Each was verified by injecting the corresponding fault (removing the backwards clamp, removing the forwards clamp, and ignoring the commit's date entirely) and confirming the relevant test failed, then reverting. 98 assertions offline (0 failed, 4 skipped -- pre-existing, live-bucket only), up from 91. Co-authored-by: bit <bit@das-labor.org>
This commit is contained in:
@@ -20,15 +20,30 @@ trap 'rm -rf "$work"' EXIT
|
||||
|
||||
APP_ID="de.example.app"
|
||||
|
||||
# "now" is pinned for the whole script, the same reasoning
|
||||
# test_next_version_code.py uses NEXT_VERSION_CODE_NOW for: every expected
|
||||
# result below is then a fixed number, not one that depends on the real
|
||||
# wall clock (and, unlike the Python suite, this file drives the real CLI
|
||||
# through with-nixpkgs.sh, so it is also slower -- one more reason not to
|
||||
# let it depend on when it happens to run).
|
||||
export NEXT_VERSION_CODE_NOW="2026-06-01T00:00:00+00:00"
|
||||
# The committer timestamp for most cases: the *same* instant as "now" above,
|
||||
# as Unix seconds (`git log -1 --format=%ct`'s own unit) -- comfortably
|
||||
# inside tolerance, so a normal invocation is not accidentally
|
||||
# forward-clamped. Verified equal to NEXT_VERSION_CODE_NOW by construction:
|
||||
# both come from 2026-06-01T00:00:00Z.
|
||||
COMMITTER_TS=1780272000
|
||||
|
||||
echo "--- wrong argument count is refused ---"
|
||||
assert_failure "no arguments" bash "$here/next-version-code.sh"
|
||||
assert_failure "one argument" bash "$here/next-version-code.sh" "$work/index.json"
|
||||
assert_failure "two arguments" bash "$here/next-version-code.sh" "$work/index.json" "$APP_ID"
|
||||
|
||||
echo "--- a local file path works end to end, through the real CLI ---"
|
||||
cat > "$work/index.json" <<JSON
|
||||
{"packages": {"$APP_ID": {"versions": {"h": {"manifest": {"versionCode": 41}}}}}}
|
||||
JSON
|
||||
out=$(bash "$here/next-version-code.sh" "$work/index.json" "$APP_ID")
|
||||
out=$(bash "$here/next-version-code.sh" "$work/index.json" "$APP_ID" "$COMMITTER_TS")
|
||||
if [[ "$out" =~ ^[0-9]+$ ]] && [ "$out" -gt 41 ]; then
|
||||
pass "the CLI prints an integer strictly greater than the published version code (got $out)"
|
||||
else
|
||||
@@ -47,15 +62,31 @@ echo "--- a first-ever publish for this application id yields the bare candidate
|
||||
cat > "$work/other-app.json" <<'JSON'
|
||||
{"packages": {"some.other.app": {"versions": {"h": {"manifest": {"versionCode": 5}}}}}}
|
||||
JSON
|
||||
out2=$(bash "$here/next-version-code.sh" "$work/other-app.json" "$APP_ID")
|
||||
out2=$(bash "$here/next-version-code.sh" "$work/other-app.json" "$APP_ID" "$COMMITTER_TS")
|
||||
if [[ "$out2" =~ ^[0-9]+$ ]]; then
|
||||
pass "an application id absent from the index is not an error"
|
||||
else
|
||||
fail "an application id absent from the index is not an error (got '$out2')"
|
||||
fi
|
||||
|
||||
echo "--- a commit dated far in the future is clamped through the real CLI, and warns ---"
|
||||
# "now" is still pinned at 2026-06-01T00:00:00Z (exported above), so a
|
||||
# committer date one century later (2126-01-01T00:00:00Z, hardcoded rather
|
||||
# than computed with `date`, whose -d/-j flags differ between GNU and BSD)
|
||||
# is unambiguously beyond tolerance.
|
||||
future_ts=4922899200
|
||||
out6=$(bash "$here/next-version-code.sh" "$work/other-app.json" "$APP_ID" "$future_ts" \
|
||||
2>/tmp/nvc_stderr4.$$) || true
|
||||
err6=$(cat /tmp/nvc_stderr4.$$); rm -f /tmp/nvc_stderr4.$$
|
||||
if [[ "$out6" =~ ^[0-9]+$ ]]; then
|
||||
pass "a far-future commit date still exits 0 (clamp, not rejection)"
|
||||
else
|
||||
fail "a far-future commit date still exits 0 (clamp, not rejection) (got '$out6')"
|
||||
fi
|
||||
assert_contains "a far-future commit date warns on stderr" "$err6" "warning"
|
||||
|
||||
echo "--- a missing local file fails loudly, prints nothing on stdout ---"
|
||||
out3=$(bash "$here/next-version-code.sh" "$work/does-not-exist.json" "$APP_ID" 2>/tmp/nvc_stderr.$$) && rc=0 || rc=$?
|
||||
out3=$(bash "$here/next-version-code.sh" "$work/does-not-exist.json" "$APP_ID" "$COMMITTER_TS" 2>/tmp/nvc_stderr.$$) && rc=0 || rc=$?
|
||||
err3=$(cat /tmp/nvc_stderr.$$); rm -f /tmp/nvc_stderr.$$
|
||||
if [ "${rc:-0}" -ne 0 ]; then pass "a missing index file exits non-zero"; else
|
||||
fail "a missing index file exits non-zero (got 0)"
|
||||
@@ -69,7 +100,7 @@ echo "--- an unreachable URL fails loudly too, the same way, through the real ne
|
||||
# port 1 is a reserved, unassigned TCP port -- nothing listens there, so
|
||||
# this never leaves the loopback interface and needs no real network access,
|
||||
# the same trick test_publish.sh uses.
|
||||
out4=$(bash "$here/next-version-code.sh" "http://127.0.0.1:1/index-v2.json" "$APP_ID" 2>/tmp/nvc_stderr2.$$) && rc4=0 || rc4=$?
|
||||
out4=$(bash "$here/next-version-code.sh" "http://127.0.0.1:1/index-v2.json" "$APP_ID" "$COMMITTER_TS" 2>/tmp/nvc_stderr2.$$) && rc4=0 || rc4=$?
|
||||
err4=$(cat /tmp/nvc_stderr2.$$); rm -f /tmp/nvc_stderr2.$$
|
||||
if [ "${rc4:-0}" -ne 0 ]; then pass "an unreachable index URL exits non-zero"; else
|
||||
fail "an unreachable index URL exits non-zero (got 0)"
|
||||
@@ -81,11 +112,15 @@ assert_contains "the error explains the index could not be read (URL case)" "$er
|
||||
|
||||
echo "--- malformed JSON fails loudly ---"
|
||||
echo "{not valid json" > "$work/malformed.json"
|
||||
out5=$(bash "$here/next-version-code.sh" "$work/malformed.json" "$APP_ID" 2>/tmp/nvc_stderr3.$$) && rc5=0 || rc5=$?
|
||||
out5=$(bash "$here/next-version-code.sh" "$work/malformed.json" "$APP_ID" "$COMMITTER_TS" 2>/tmp/nvc_stderr3.$$) && rc5=0 || rc5=$?
|
||||
err5=$(cat /tmp/nvc_stderr3.$$); rm -f /tmp/nvc_stderr3.$$
|
||||
if [ "${rc5:-0}" -ne 0 ]; then pass "malformed JSON exits non-zero"; else
|
||||
fail "malformed JSON exits non-zero (got 0)"
|
||||
fi
|
||||
assert_contains "the error explains the index could not be read (malformed JSON)" "$err5" "could not read the index"
|
||||
|
||||
echo "--- a non-numeric committer timestamp is refused, not silently coerced ---"
|
||||
assert_failure "non-numeric committer timestamp" \
|
||||
bash "$here/next-version-code.sh" "$work/index.json" "$APP_ID" "not-a-timestamp"
|
||||
|
||||
report_and_exit
|
||||
|
||||
Reference in New Issue
Block a user