Files
flamenet-messenger-android/.gitea/workflows/ci.yml
T
Paul HittandClaude Opus 5 d1ddbba3de
ci / secret-sweep (pull_request) Successful in 13s
ci / image (pull_request) Successful in 14s
ci / vectors-head (pull_request) Failing after 4m35s
ci / unit-tests (pull_request) Failing after 4m45s
ci: name bash as the shell, dash has no pipefail
The container job picked `sh -e {0}`, and sh on eclipse-temurin:jammy is
dash. Every `set -euo pipefail` step failed on line 2 with "Illegal option
-o pipefail" before running a command, so unit-tests and vectors-head went
red without compiling anything.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-10-14 10:11:36 -04:00

204 lines
9.4 KiB
YAML

# CI for the Android app on Gitea Actions. Translated from .gitlab-ci.yml on
# 2026-10-13; every gate it had is kept. Instrumented tests stay manual: they
# need an emulator and a local relay.
#
# Runs on mac1/mac2 (`ubuntu-latest`) inside the pinned toolchain image built
# from ci/android-ci.Dockerfile. Never on pauls-imac: Android unit tests hang
# there (2026-10-09).
name: ci
on:
push:
branches: [main]
pull_request:
env:
# Bump when ci/android-ci.Dockerfile changes.
IMAGE: git.extant2000.com/extant2000/flamenet-android-ci:jdk21.0.5-api35-r1
GITEA_BASE: https://git.extant2000.com/extant2000
# Inside a container job the runner picks `sh -e {0}`, which is dash on this
# image, and dash has no `pipefail`. Every `set -euo pipefail` step died on
# line 2 before it ran anything. Named here once rather than per step.
defaults:
run:
shell: bash
jobs:
# The repository is public, so this sweep is the last thing between a pasted
# key and the internet. It was red for nine days in 2026-09 because nobody
# ran it.
secret-sweep:
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- uses: actions/checkout@v4
- run: bash scripts/secret-sweep.sh
# Builds and pushes the toolchain image when its tag is not in the registry
# yet. A no-op on every other run.
image:
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@v4
- name: Build and push the toolchain image if missing
env:
REGISTRY_TOKEN: ${{ secrets.REGISTRY_TOKEN }}
JOB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
ACTOR: ${{ gitea.actor }}
run: |
set -euo pipefail
curl -fsSL https://download.docker.com/linux/static/stable/x86_64/docker-27.3.1.tgz \
| tar -xz -C /usr/local/bin --strip-components=1 docker/docker
if [ -n "${REGISTRY_TOKEN:-}" ]; then
echo "$REGISTRY_TOKEN" | docker login git.extant2000.com -u extant2000git --password-stdin
else
echo "$JOB_TOKEN" | docker login git.extant2000.com -u "$ACTOR" --password-stdin
fi
if docker manifest inspect "$IMAGE" >/dev/null 2>&1; then
echo "image $IMAGE exists"; exit 0
fi
DOCKER_BUILDKIT=0 docker build -t "$IMAGE" -f ci/android-ci.Dockerfile ci
docker push "$IMAGE"
unit-tests:
needs: image
runs-on: ubuntu-latest
timeout-minutes: 45
container:
image: git.extant2000.com/extant2000/flamenet-android-ci:jdk21.0.5-api35-r1
env:
# The interop tests FAIL rather than skip when unconfigured, on purpose.
# They need a real lyrebird from the flamenet-elligator2 checkout, so they
# are opted out HERE and nowhere else. Run scripts/obfs4-interop.sh
# locally before merging any obfs4 change.
OBFS4_INTEROP_OPTOUT: "1"
steps:
- uses: actions/checkout@v4
- name: Fetch the pinned protocol vectors
run: |
set -euo pipefail
# Pinned to .e2e-ref, not tip (flamenet-relay#56 is what tip cost).
# `vectors-head` below is the advisory half that watches HEAD.
FLAMENET_E2E_PIN=$(cat .e2e-ref)
rm -rf .ci-vectors
git clone -q "$GITEA_BASE/flamenet-e2e.git" .ci-vectors
git -C .ci-vectors checkout -q "$FLAMENET_E2E_PIN"
- name: Unit tests
run: |
set -euo pipefail
V="$GITHUB_WORKSPACE/.ci-vectors/test/vectors"
export FLAMENET_VECTORS="$V/protocol-v1.json"
export FLAMENET_PQ_VECTORS="$V/pqx3dh-v2.json"
export FLAMENET_ACCOUNT_VECTORS="$V/account-kdf-v1.json"
export FLAMENET_BACKUP_VECTORS="$V/backup-v1.json"
export FLAMENET_BACKUP_PAYLOAD_VECTORS="$V/backup-payload-v1.json"
export FLAMENET_CONTENT_VECTORS="$V/content-v1.json"
export FLAMENET_POLL_VECTORS="$V/poll-tally-v1.json"
export FLAMENET_PADDING_VECTORS="$V/padding-v1.json"
export FLAMENET_SYNC_VECTORS="$V/sync-merge-v1.json"
export FLAMENET_HANDLE_CERT_VECTORS="$V/handle-cert-v1.json"
export FLAMENET_LINK_VECTORS="$V/links-v1.json"
export FLAMENET_BRIDGE_VECTORS="$V/bridge-offer-v1.json"
export FLAMENET_CONTENT_KINDS_VECTORS="$V/content-kinds-v1.json"
export FLAMENET_TODAY_VECTORS="$V/today-v1.json"
export FLAMENET_HELLO_LINK_VECTORS="$V/hello-link-v1.json"
# A path that silently does not exist would read as a protocol bug.
for f in "$FLAMENET_VECTORS" "$FLAMENET_PQ_VECTORS" "$FLAMENET_ACCOUNT_VECTORS" \
"$FLAMENET_BACKUP_VECTORS" "$FLAMENET_BACKUP_PAYLOAD_VECTORS" \
"$FLAMENET_CONTENT_VECTORS" "$FLAMENET_POLL_VECTORS" \
"$FLAMENET_PADDING_VECTORS" "$FLAMENET_SYNC_VECTORS" \
"$FLAMENET_HANDLE_CERT_VECTORS" "$FLAMENET_LINK_VECTORS" \
"$FLAMENET_BRIDGE_VECTORS" "$FLAMENET_CONTENT_KINDS_VECTORS" \
"$FLAMENET_TODAY_VECTORS" "$FLAMENET_HELLO_LINK_VECTORS"; do
[ -f "$f" ] || { echo "GATE: vector file missing: $f"; exit 1; }
done
# Clear old results so the gate never counts ghosts.
rm -rf ./*/build/test-results
# --rerun-tasks: a FROM-CACHE pass prints the same BUILD SUCCESSFUL
# as a real one. Both app variants: release is the one that ships.
./gradlew --no-daemon --rerun-tasks :engine:test :client:test :obfs4:test \
:games:test :app:testDebugUnitTest :app:testReleaseUnitTest
- name: Gate on the test results
run: |
# Assert on the XML, never on Gradle's exit status: `:app` prints no
# test names, and an up-to-date task is also "BUILD SUCCESSFUL".
python3 - <<'PY'
import glob, re, sys
# Floors near 90% of the suite (663 / 453 / 75 / 572, games 15).
# `app` counts both variants.
MINIMUMS = {"engine": 595, "client": 405, "obfs4": 68, "games": 13, "app": 515}
totals = {}
for module in MINIMUMS:
tests = failures = skipped = 0
for path in glob.glob(f"{module}/build/test-results/*/TEST-*.xml"):
text = open(path).read()
m = re.search(r'tests="(\d+)" skipped="(\d+)" failures="(\d+)" errors="(\d+)"', text)
if not m:
continue
tests += int(m.group(1)); skipped += int(m.group(2))
failures += int(m.group(3)) + int(m.group(4))
totals[module] = (tests, failures, skipped)
bad = False
for module, (tests, failures, skipped) in totals.items():
print(f"GATE: {module}: {tests} tests, {failures} failures, {skipped} skipped")
if tests < MINIMUMS[module]:
print(f"GATE: {module} reported {tests} tests, the suite did not run")
bad = True
if failures:
bad = True
grand = sum(t for t, _, _ in totals.values())
print(f"GATE: {grand} tests in total")
if grand < 1580:
print(f"GATE: only {grand} tests in the whole build; something is not running")
bad = True
sys.exit(1 if bad else 0)
PY
# The pin's conscience: the vector suites against flamenet-e2e HEAD.
# Allowed to fail. A red run means an update is waiting; see .e2e-ref.md.
vectors-head:
needs: image
runs-on: ubuntu-latest
timeout-minutes: 20
continue-on-error: true
container:
image: git.extant2000.com/extant2000/flamenet-android-ci:jdk21.0.5-api35-r1
env:
OBFS4_INTEROP_OPTOUT: "1"
steps:
- uses: actions/checkout@v4
- name: Vector suites at flamenet-e2e HEAD
run: |
set -euo pipefail
rm -rf .ci-vectors-head
git clone --depth 1 "$GITEA_BASE/flamenet-e2e.git" .ci-vectors-head
echo "pinned: $(cat .e2e-ref)"
echo "at HEAD: $(git -C .ci-vectors-head rev-parse HEAD)"
V="$GITHUB_WORKSPACE/.ci-vectors-head/test/vectors"
export FLAMENET_VECTORS="$V/protocol-v1.json"
export FLAMENET_PQ_VECTORS="$V/pqx3dh-v2.json"
export FLAMENET_ACCOUNT_VECTORS="$V/account-kdf-v1.json"
export FLAMENET_BACKUP_VECTORS="$V/backup-v1.json"
export FLAMENET_BACKUP_PAYLOAD_VECTORS="$V/backup-payload-v1.json"
export FLAMENET_CONTENT_VECTORS="$V/content-v1.json"
export FLAMENET_POLL_VECTORS="$V/poll-tally-v1.json"
export FLAMENET_PADDING_VECTORS="$V/padding-v1.json"
export FLAMENET_SYNC_VECTORS="$V/sync-merge-v1.json"
export FLAMENET_HANDLE_CERT_VECTORS="$V/handle-cert-v1.json"
export FLAMENET_LINK_VECTORS="$V/links-v1.json"
export FLAMENET_BRIDGE_VECTORS="$V/bridge-offer-v1.json"
export FLAMENET_CONTENT_KINDS_VECTORS="$V/content-kinds-v1.json"
export FLAMENET_TODAY_VECTORS="$V/today-v1.json"
export FLAMENET_HELLO_LINK_VECTORS="$V/hello-link-v1.json"
# Only the tests that read vectors: the engine, :obfs4 (the
# bridge-offer vectors, SPEC section 24), and the three 0.32 loaders
# in :client and :app.
./gradlew --no-daemon --rerun-tasks :engine:test :obfs4:test \
:client:test --tests io.flamenet.client.SharedVectorsTodayTest \
:app:testDebugUnitTest --tests io.flamenet.messenger.app.SharedVectorsContentKindsTest \
--tests io.flamenet.messenger.app.SharedVectorsHelloLinkTest