Skip to content

Run the test suites on pull requests - #73

Merged
ivan-digital merged 2 commits into
mainfrom
ci/run-tests-on-pull-requests
Aug 2, 2026
Merged

Run the test suites on pull requests#73
ivan-digital merged 2 commits into
mainfrom
ci/run-tests-on-pull-requests

Conversation

@ivan-digital

Copy link
Copy Markdown
Member

Why

Nothing ran on a pull request except CodeQL's default scan. Unit tests, the JNI build and the submodule pointer were exercised only by the tag build — so a regression was reviewable but not detectable, and the first time anything checked it was during a release.

Two concrete cases from #68, both real:

  • It added 13 TextInsertionTest cases that would not have run anywhere before merge.
  • It carried a speech-core pointer older than main, which would have silently reverted the worker-loop exception-safety fix from #126. In a 2,100-line diff that is one line reading -Subproject commit … / +Subproject commit …, and nothing distinguishes a legitimate bump from a reversion by looking at it.

What runs

unit-tests:sdk:test and :app:testDebugUnitTest, then :sdk:assembleRelease and :app:assembleDebug. The assemble step matters independently: it compiles the JNI bridge against the pinned speech-core, so a submodule that no longer builds against the current bridge fails here rather than at release time. Test reports upload on failure.

submodule-ancestry (PRs only) — compares HEAD:speech-core against base:speech-core and fails if the pointer is an ancestor of the base, i.e. moves backwards. It also verifies the commit exists in soniqo/speech-core, since a pointer reachable only from a fork or a local branch breaks every later checkout of this repository. Advancing the pointer stays allowed and unchanged pointers pass immediately.

Notes

  • push: [main] as well as pull_request, so a direct commit to main is covered by the same gate.
  • Gradle caching keyed on the build files, and concurrency cancels superseded runs.
  • Device tests (connectedAndroidTest) are deliberately not here — they need an emulator and download ~1.2 GB of models, which is a separate decision about CI cost.

Nothing ran on a pull request except CodeQL's default scan. Unit tests, the
JNI build and the submodule pointer were exercised only by the tag build, so
a regression stayed reviewable but undetectable — #68 arrived carrying 13 new
unit tests that would not have run anywhere before merge, and a speech-core
pointer older than main, which reads as one unremarkable line in a 2,100-line
diff and would have quietly reverted the worker-loop fix.

The submodule check encodes that second case: the pointer may advance, but it
may not retreat, and it has to exist upstream. A pointer reachable only from
a fork or a local branch breaks every later checkout of this repository, and
nothing was catching it.
Comment thread .github/workflows/ci.yml
Comment on lines +18 to +61
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
submodules: recursive

- uses: actions/setup-java@v4
with:
distribution: temurin
java-version: '17'

- uses: android-actions/setup-android@v3

- name: Cache Gradle
uses: actions/cache@v4
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: gradle-${{ runner.os }}-${{ hashFiles('**/*.gradle.kts', 'gradle/libs.versions.toml', 'gradle/wrapper/gradle-wrapper.properties') }}
restore-keys: gradle-${{ runner.os }}-

# The SDK suite covers the download manifest; the app suite covers the
# demo's own logic, including text insertion. Neither ran on a PR before.
- name: Unit tests
run: ./gradlew :sdk:test :app:testDebugUnitTest

# Compiles the JNI bridge against the pinned speech-core, so a submodule
# that does not build with the current bridge fails here rather than at
# release time.
- name: Assemble
run: ./gradlew :sdk:assembleRelease :app:assembleDebug

- name: Upload test reports
if: always()
uses: actions/upload-artifact@v4
with:
name: test-reports
path: |
sdk/build/reports/tests/
app/build/reports/tests/
retention-days: 7

submodule-ancestry:
Comment thread .github/workflows/ci.yml
Comment on lines +66 to +104
if: github.event_name == 'pull_request'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0

- name: speech-core must not move backwards
run: |
set -euo pipefail
git fetch --quiet origin "${{ github.base_ref }}"
base=$(git rev-parse "origin/${{ github.base_ref }}:speech-core")
head=$(git rev-parse "HEAD:speech-core")
echo "base ${{ github.base_ref }}: $base"
echo "head: $head"

if [ "$base" = "$head" ]; then
echo "unchanged"
exit 0
fi

# The pointer may legitimately advance. It may not retreat, and the
# commit has to exist upstream — a pointer only reachable from a fork
# or a local branch breaks every later checkout of this repository.
git clone --quiet --filter=blob:none --no-checkout \
https://github.com/soniqo/speech-core.git /tmp/speech-core
cd /tmp/speech-core

if ! git cat-file -e "$head^{commit}" 2>/dev/null; then
echo "::error::speech-core $head does not exist upstream"
exit 1
fi

if git merge-base --is-ancestor "$head" "$base"; then
echo "::error::speech-core moves backwards: $head is an ancestor of $base on ${{ github.base_ref }}"
exit 1
fi

echo "speech-core advances or diverges forward"
The Assemble step failed on its own pull request: CMake is handed ORT_DIR
and LITERT_DIR, and nothing had created them. setup.sh fetches both AARs and
the release workflow runs it before any Gradle task; CI has to do the same.

Cached on the versions pinned in setup.sh, which the script already compares
against the installed version.txt, so a hit makes the step a no-op instead of
a repeated download. control-demo joins the test list to match what the
release build covers.
@ivan-digital
ivan-digital merged commit 42718a6 into main Aug 2, 2026
5 checks passed
@ivan-digital
ivan-digital deleted the ci/run-tests-on-pull-requests branch August 2, 2026 09:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants