open-standards/.github/workflows/wai-sink-android.yml
Transaction Science 4708f7a65c ci: wai-sink-android, the JVM tests on linux-arm64 and the AAR and emulator tests on macos
linux-arm64 builds libwai and the JNI layer for the runner and runs the unit
tests on the host JVM, requiring every test class to have run and none to fail
or skip; no Android SDK is used there. macos, where the platform SDK, its NDK
and its emulator are installed, builds every ABI and the AAR, checks the AAR
holds both libraries for each ABI, and runs the instrumented tests in a
headless emulator, failing unless at least one ran and all passed.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-26 08:59:13 -04:00

164 lines
7.1 KiB
YAML

name: wai-sink-android native sink library
# The native sink's Kotlin library (wai/wai-sink-android, extensions/native-sink.md).
#
# On the linux-arm64 runner, the platform-free half's unit tests on the host JVM: libwai and the
# JNI layer built for the runner, then the corpus live-conformance/native-sink through the JNI
# layer, every case and every refused configuration; the page-parity bundle byte for byte; the
# C interface's statuses as the JNI layer carries them; the loopback relay over the reference
# package's floor (presented byte for byte, a changed fragment answered 502 on every request,
# ranges, content codings, an unsigned playlist refused, nothing answered outside the session's
# token); and the observation loop's hidden and idle endings with its clock advanced by the test.
# No Android SDK is used there.
#
# On the macOS runner, the host where the platform SDK, its NDK and its emulator are installed:
# libwai and the JNI layer for each ABI with the pinned NDK, the AAR, and the instrumented tests in
# a headless emulator (the host's ABI, arm64-v8a): the corpus and the relay on the device, the
# platform's media player library playing the floor through the sink to its end, a changed
# fragment refused on every retry, a session nobody can see ended as hidden, and the attention
# signals read from a real activity. Nothing listens beyond loopback.
#
# Push filters match the push's delta, so every path the jobs read is listed.
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
on:
push:
branches: [main]
paths:
- 'wai/wai-sink-android/**'
- 'wai/wai-rs/**'
- 'wai/wai-quantum/**'
- 'wai/wai-ops/**'
- 'wai/live-conformance/native-sink/**'
- 'wai/extensions/native-sink.md'
- 'rust-toolchain.toml'
- '.github/workflows/wai-sink-android.yml'
pull_request:
paths:
- 'wai/wai-sink-android/**'
- 'wai/wai-rs/**'
- 'wai/wai-quantum/**'
- 'wai/wai-ops/**'
- 'wai/live-conformance/native-sink/**'
- 'wai/extensions/native-sink.md'
- 'rust-toolchain.toml'
- '.github/workflows/wai-sink-android.yml'
jobs:
relevant:
name: does this change touch it
runs-on: linux-arm64
outputs:
run: ${{ steps.gate.outputs.run }}
steps:
- uses: actions/checkout@v5
with:
fetch-depth: 0
- id: gate
run: |
set -euo pipefail
r=$(python3 scripts/pr-touches-paths.py .github/workflows/wai-sink-android.yml \
"${{ github.event.pull_request.base.sha }}" \
"${{ github.event.pull_request.head.sha }}")
echo "this change touches wai-sink-android: $r"
echo "run=$r" >> "$GITHUB_OUTPUT"
jvm-linux-arm64:
name: the JNI layer and the platform-free half on the host JVM · linux-arm64
needs: relevant
if: needs.relevant.outputs.run == 'true'
runs-on: linux-arm64
timeout-minutes: 60
steps:
- uses: actions/checkout@v5
- name: Install Rust 1.98.0
shell: bash
run: |
export PATH="$HOME/.cargo/bin:$PATH"
command -v rustup >/dev/null 2>&1 || \
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --profile minimal --default-toolchain 1.98.0
rustup toolchain install 1.98.0 --profile minimal
rustup default 1.98.0
echo "$HOME/.cargo/bin" >> "$GITHUB_PATH"
# A JDK 17, from the image's packages when the image has none.
- name: JDK 17
shell: bash
run: |
set -euo pipefail
j="/usr/lib/jvm/java-17-openjdk-$(dpkg --print-architecture)"
if [ ! -x "$j/bin/javac" ]; then
sudo=$(command -v sudo || true)
$sudo apt-get update -qq
$sudo apt-get install -y -qq --no-install-recommends openjdk-17-jdk-headless >/dev/null
fi
test -f "$j/include/jni.h" || { echo "::error::no JDK 17 with jni.h at $j"; exit 1; }
echo "JAVA_HOME=$j" >> "$GITHUB_ENV"
- name: libwai and the JNI layer for this runner
shell: bash
working-directory: wai/wai-sink-android
run: bash scripts/build-native.sh host
# The pinned Gradle and the build's dependencies are kept on the runner's cache volume when
# it has one.
- name: The unit tests
shell: bash
working-directory: wai/wai-sink-android
run: |
set -euo pipefail
cache=/ci-cache/wai-sink-android
mkdir -p "$cache" 2>/dev/null || cache="$RUNNER_TEMP/wai-sink-android"
export WAI_SINK_GRADLE_DISTS="$cache/dists" GRADLE_USER_HOME="$cache/gradle-home"
bash scripts/gradle.sh -Pwai.sink.jvmOnly=true :core:test
python3 -I - core/build/test-results/test <<'PY'
import sys, pathlib, xml.etree.ElementTree as ET
runs = {}
tests = fails = skips = 0
for f in pathlib.Path(sys.argv[1]).glob("*.xml"):
s = ET.parse(f).getroot()
tests += int(s.get("tests", 0)); fails += int(s.get("failures", 0)) + int(s.get("errors", 0)); skips += int(s.get("skipped", 0))
runs[s.get("name").split(".")[-1]] = int(s.get("tests", 0))
print(f"unit tests: {tests} run, {fails} failed, {skips} skipped: {runs}")
need = {"CorpusTest", "RelayTest", "SessionDriverTest", "JsonTest"}
missing = sorted(c for c in need if runs.get(c, 0) == 0)
if missing: print(f"::error::these classes ran no test: {missing}")
sys.exit(0 if tests > 0 and fails == 0 and skips == 0 and not missing else 1)
PY
android-macos:
name: every ABI, the AAR, and the instrumented tests in a headless emulator · macos
needs: relevant
if: needs.relevant.outputs.run == 'true'
runs-on: macos
timeout-minutes: 90
steps:
- uses: actions/checkout@v5
# launchd's PATH omits cargo on the macOS host runner.
- name: Put cargo on PATH
shell: bash
run: echo "$HOME/.cargo/bin" >> "$GITHUB_PATH"
- name: The targets the ABIs need
shell: bash
run: rustup target add aarch64-linux-android armv7-linux-androideabi x86_64-linux-android
- name: libwai and the JNI layer for each ABI
shell: bash
working-directory: wai/wai-sink-android
run: nice -n 10 bash scripts/build-native.sh android
- name: The AAR
shell: bash
working-directory: wai/wai-sink-android
run: |
set -euo pipefail
nice -n 10 bash scripts/gradle.sh :sink:assembleRelease
aar=sink/build/outputs/aar/sink-release.aar
for abi in arm64-v8a armeabi-v7a x86_64; do
for so in libwai.so libwai_sink_jni.so; do
unzip -l "$aar" | grep -q " jni/$abi/$so\$" || { echo "::error::the AAR has no jni/$abi/$so"; exit 1; }
done
done
unzip -l "$aar" | grep -q ' classes.jar$' || { echo "::error::the AAR has no classes"; exit 1; }
- name: The instrumented tests in a headless emulator
shell: bash
working-directory: wai/wai-sink-android
run: nice -n 10 bash scripts/test-emulator.sh