Skip to content

Run every variant and the sample on the emulator before publishing - #25

Merged
sbokatuk merged 2 commits into
mainfrom
ci-device-coverage
Oct 30, 2026
Merged

sbokatuk merged 2 commits into
mainfrom
ci-device-coverage

Conversation

@sbokatuk

@sbokatuk sbokatuk commented Oct 30, 2026 •

Copy link
Copy Markdown
Owner

Published packages carried a permanent app freeze because CI ran one variant and never deployed the sample. This closes those gaps. It does not touch release.yml or its guard, and adds no release note, so merging releases nothing.

The numbers below are from real runs: this PR's run and throwaway runs on a branch that has since been deleted. The freeze and RELRO results used packages already on nuget.org.

Total cost, measured against the last run of the previous PR: 68 runner-minutes instead of 18, and 11.8 minutes of wall-clock instead of 9.6.

1. Every variant on the emulator

Legs Why
All 8 variants, net10, API 35 Each is a different .aar, and nothing else loads its native code. net10 assets are grafted in per variant by the merge step, so that is the framework where a variant can go wrong on its own.
Video, net8, API 35 Keeps today's net8 coverage.
Audio, net8, API 31 The variant most likely to differ on x86_64. See below.

Why Audio on API 31. On 8.1.9 no variant differs by ABI: across all 8 .aars there is no TEXTREL, no writable and executable segment, identical DT_NEEDED, and 16 KB alignment everywhere. The one ABI-specific load failure on record is RELRO: FFmpegKit 7.1.6 maps it past the end of a library in five variants on both ABIs, and in Audio on x86_64 only. That failure doesn't show on API 35, so eight API 35 legs would all pass. This leg's job is to catch that class of failure if it comes back.

Why one job per leg rather than one emulator working through a list. Legs run side by side, so they add almost no wall-clock time, and a failed boot re-runs one leg rather than all of them.

Cost: 10 emulator jobs instead of 2, at 4.3 to 5.2 minutes each, so about 40 more runner-minutes per push. They're unbilled because the repository is public, and the slowest leg took 5.2 minutes against 4.9 before. Each extra emulator boot is one more chance of an infrastructure flake, which is what the per-leg re-run offsets.

2. ReportsBundledFFmpegVersion now fails on a mismatch

run-device-tests.sh takes the FFmpeg from the package version, resolves it through build/native-versions.tsv so that an unmapped version stops before the build, and compiles it into the test app. The app compares it with what the library reports at runtime.

Cost: no extra job or minutes. Two behaviour changes: a test app built outside the script now fails this check on purpose, and an upstream build reporting anything other than n<version> will fail until someone looks.

3. The sample runs on an emulator

run-sample-test.sh installs the sample, waits until it has loaded, taps convert, requires the success text, then keeps tapping for 15 seconds to prove the app still takes input. An ANR or a dead process fails the run. Screenshot and full log are uploaded either way.

One thing to review closely. The script builds the sample with MONO_GC_PARAMS=nursery-size=64k, from a scratch file, so the sample's own project is unchanged. Without it, the published 8.1.2.5 passed this script three times out of three: the freeze needs a collection after FFmpeg has run, and with the default 4 MB nursery this small app may never have one. So this is still a Release build of the real package, but with far more frequent collections than an app would see. That's what makes the check meaningful.

Cost: one job of 6 to 8.5 minutes, which becomes the slowest job in the run and accounts for the extra wall-clock time. It's also the most flake-prone check here, because it drives a real UI on a software-rendered emulator. It waits on what's on screen rather than fixed delays, reads tap positions from the UI tree, and hides system dialogs so that a "System UI isn't responding" can't swallow a tap.

Evidence

Control Where Result
Test app told to expect FFmpeg 8.1.2, library is n8.1.3 arm64 The check fails with the mismatch; the other 14 pass
No expected version compiled in arm64 The check fails; the other 14 pass
Unmapped version 9.9.9.1 local Stops before the build
Sample, 8.1.2.5, default nursery arm64 Passes 3 of 3: the gap the nursery closes
Sample, 8.1.2.5, this script arm64 Fails 3 of 3 with an ANR
Sample, 8.1.2.5, this script x86_64 Fails 1 of 1 with an ANR
Sample, 8.1.3.1, this script arm64 Passes 7 of 7
Sample, 8.1.3.1, this script x86_64 Passes 4 of 4, including this PR's run, with no ANR lines
Audio 7.1.5.4, API 31 x86_64 can't enable GNU RELRO protection, 1 of 15 pass
Audio 7.1.5.4, API 35 x86_64 Loads, then hangs in the GC check
Full 7.1.5.4, API 31 x86_64 Loads, then hangs in the GC check
Audio 8.1.3.1, API 31 x86_64 Passes 15 of 15
All 10 legs of this PR's run x86_64 Each passes 15 of 15 and reports n8.1.3

The ANRs carry the known signature: main thread in sem_wait, .NET TP Worker threads in __rt_sigsuspend.

The two hangs also show that the published 7.1.5.4 freezes on x86_64. That wasn't known before; 6.1.6.4 was not run.

Not covered

  • Older tracks. PR CI packs only the default track, so all of this runs on 8.x. 7.x and 6.x still depend on local gates.
  • arm64. CI emulators are x86_64 only.
  • Android versions other than 31 and 35, and net9 on an emulator.
  • Other sample paths. The run covers one conversion on one framework, and proves the conversion, not playback: the preview players can show black under CI's software renderer.
  • net8 locally. That toolchain is broken on the maintainer's Mac, so net8 was checked on CI only.

🤖 Generated with Claude Code

sbokatuk and others added 2 commits October 30, 2026 16:47
ReportsBundledFFmpegVersion claimed a cross-check but only asserted the
value was non-empty, so a package named for one FFmpeg and carrying another
passed, and every gate had to read the ffmpeg= log line by hand.

run-device-tests.sh now derives the FFmpeg from the package version, resolves
it through build/native-versions.tsv so an unmapped version stops before the
build, and compiles it into the test app, which compares it to what the
library reports. An empty value fails rather than skips, so a build that stops
passing it cannot turn this back into the old check.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
CI ran one variant, and built the sample without ever deploying it, so a
package that froze the sample on its first conversion passed everything.

The e2e matrix now runs all eight variants on net10, whose assets the merge
step grafts in per variant. net8 keeps a Video leg and gains Audio on API 31:
Audio is the one variant whose load behaviour has depended on the ABI, and
that RELRO failure only shows on an older Android, so API 35 alone hides it.

run-sample-test.sh installs the sample, taps convert, requires the success
text and fails on an ANR or a dead process. It builds the sample with a tiny
GC nursery: the freeze needs a collection after FFmpeg has run, and without
this the published 8.1.2.5 passed three runs out of three. With it, that
package fails with the known ANR and 8.1.3.1 passes.

Both are inside build.yml's verify gate, so they stop the beta publish and
release runs skip them as before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Copilot AI balanced review requested due to automatic review settings October 30, 2026 14:08

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

The sample test script can reuse a cached NuGet package after repacking the same version, allowing tests to pass against older binaries unless the package cache is cleared or isolated.

1 open issue
What changed in this PR

This PR strengthens pre-publish validation for the Android bindings by exercising every native variant and running the MAUI sample to catch app freezes missed by smoke tests. Release publishing and its guard remain unchanged.

Changes:

  • Expands emulator coverage to all eight variants on net10, plus Video and Audio on net8.
  • Checks the runtime FFmpeg version against the package’s mapped version.
  • Adds a sample conversion and responsiveness check under increased garbage-collection pressure, with diagnostic uploads.
File Description
tests/​FFmpegKit.Android.DeviceTests/​SmokeTests.cs Fails on missing or mismatched expected FFmpeg versions.
tests/​FFmpegKit.Android.DeviceTests/​FFmpegKit.Android.DeviceTests.csproj Embeds the expected version as assembly metadata.
README.md Documents expanded coverage and local sample checks.
.gitignore Ignores sample logs and screenshots.
.github/​workflows/​build.yml Expands the emulator matrix and adds sample execution.
.github/​scripts/​run-sample-test.sh Installs and drives the sample, checking conversion and responsiveness.
.github/​scripts/​run-device-tests.sh Resolves and passes the expected FFmpeg version.
.github/​instructions/​workflows.instructions.md Documents required emulator and sample gates.
.github/​copilot-instructions.md Updates CI and testing guidance.

Review effort: Balanced

echo "==> installing the sample (version=${VERSION}, tfm=${TARGET_FRAMEWORK}, sdk=${sdk_version})"
# A failed build leaves any earlier APK in place, and that would then report a verdict of its own.
adb uninstall "${PACKAGE_NAME}" > /dev/null 2>&1 || true
( cd "${SDK_DIR}" && dotnet build "${PROJECT}" \
@sbokatuk
sbokatuk merged commit 9e69bc3 into main Oct 30, 2026
17 checks passed
@sbokatuk
sbokatuk deleted the ci-device-coverage branch October 30, 2026 14:32

This branch was successfully deployed

1 active deployment
nuget.org — 656fa4a6 Deployed Oct 30, 2026 by sbokatuk via publish beta to nuget.org #35
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