#80 added SafPickerRoundTripTest's rotation case to @FailsOnEmulatorApi37, because a real rotation aborts the framework on android-37.0. Three tests carry the marker now -- two in Media3EngineTest, one in SafPickerRoundTripTest -- and five statements still described two. Four were counts, and wrong: status_check.yml "notAnnotation removes the two tests that do not pass" status_check.yml "The two API 37 tests the gating row above excludes" CLAUDE.md "the gating leg runs the other 55" (59 - 3 = 56) CLAUDE.md "do not read a green run as evidence those two tests pass" The fifth was worse, because it was not a count. The advisory job's header justified its name with an invariant: "It is named for WHAT IT RUNS, deliberately. Both tests drive a full H.264 -> H.265 hardware transcode through Media3Engine" The rotation case drives no transcode. So the comment did not merely miscount -- it asserted a property of the job's contents that had stopped being true, and that property was the entire argument for the name. The name is unchanged, deliberately, and the header now says so instead of implying the question never arose. This is not a required context, it is red on every PR by design, and it is one people have learned to look for; renaming a check costs more than the imprecision does. What replaced the invariant is the honest rule: THE MARKER IS THE DEFINITION, NOT THE NAME -- this job holds the tests that cannot pass on the API 37 emulator image, whatever their subject. Two things stay as they were because they are still true. "the two Media3EngineTest cases that pass here" is correct: that class has four tests and two carry the marker. And the decoder theory is still a claim about the Media3 pair alone, so it now says so rather than being read as covering a rotation failure it has nothing to do with. Nothing about the job's behaviour changes: same name, same continue-on-error, same marker, same selection on both rows. Verified: yaml parses, five jobs, matrix still 33/34/35/36/37. The check to re-run when a test next joins or leaves the marker, which is the event that broke this twice: grep -rn "@FailsOnEmulatorApi37" app/src/androidTest --include='*.kt' | grep -v import | grep -c FailsOn It must equal the number every corrected comment states. It is 3. Closes #81.
488 lines
24 KiB
YAML
488 lines
24 KiB
YAML
name: Status check
|
|
|
|
on:
|
|
pull_request:
|
|
branches: [main]
|
|
|
|
# A newer push to the same PR makes the in-flight run obsolete. An emulator matrix is
|
|
# expensive, so cancel rather than let runs pile up.
|
|
concurrency:
|
|
group: status-check-${{ github.ref }}
|
|
cancel-in-progress: true
|
|
|
|
# Third-party actions are pinned to a commit rather than a tag. A tag is mutable: the
|
|
# owner can repoint v4 at new code, so a tag reference is an open invitation to run
|
|
# whatever that repository contains tomorrow. The trailing comment records which
|
|
# release each hash corresponds to, since a bare hash is unreadable.
|
|
# Nothing here writes: these jobs read the code, build it and attach reports. Declared
|
|
# explicitly rather than inherited from the repository default, for the same reason the
|
|
# action SHAs above are pinned -- the token's reach should be readable here, and a default
|
|
# that widens later should not silently widen these jobs with it. build.yml's release job
|
|
# makes the opposite declaration for the same reason.
|
|
permissions:
|
|
contents: read
|
|
|
|
env:
|
|
GRADLE_CACHE_PATHS: |
|
|
~/.gradle/caches
|
|
~/.gradle/wrapper
|
|
|
|
jobs:
|
|
# ---------------------------------------------------------------------------
|
|
# Validates the committed FFmpeg archive. It does not build anything: the whole
|
|
# point of checking the binary in is that a red run means broken code rather than
|
|
# a cross-compile that hiccuped.
|
|
#
|
|
# Its own job so a bad archive reports once, clearly, instead of surfacing as five
|
|
# confusing emulator failures. It takes seconds, so gating the matrix on it costs
|
|
# almost nothing.
|
|
# ---------------------------------------------------------------------------
|
|
ffmpeg:
|
|
name: FFmpeg binary
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 10
|
|
steps:
|
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
|
|
- name: Verify the committed archive
|
|
run: |
|
|
AAR=bin/ffmpeg-kit-next-8.1.1.aar
|
|
test -f "$AAR" || { echo "::error::$AAR is missing"; exit 1; }
|
|
|
|
# A truncated file, or a Git LFS pointer checked out without LFS, would pass
|
|
# a file-exists check and then surface much later as a confusing linker
|
|
# error. Assert the archive actually carries native libraries for both ABIs.
|
|
for abi in arm64-v8a x86_64; do
|
|
n=$(unzip -l "$AAR" | grep -c "jni/$abi/.*\.so$" || true)
|
|
echo " $abi: $n shared libraries"
|
|
test "$n" -gt 0 || { echo "::error::AAR has no $abi libraries"; exit 1; }
|
|
done
|
|
|
|
# 16 KB alignment is a Play requirement and is easy to lose in a rebuild,
|
|
# so it is checked here rather than discovered at submission.
|
|
unzip -q -o "$AAR" 'jni/*' -d /tmp/aarcheck
|
|
bad=0
|
|
for f in /tmp/aarcheck/jni/*/*.so; do
|
|
align=$(readelf -lW "$f" | awk '$1=="LOAD"{print $NF}' | sort -u)
|
|
if [ "$align" != "0x4000" ]; then
|
|
echo "::error::$(basename "$f") is $align, not 16 KB aligned"; bad=1
|
|
fi
|
|
done
|
|
test "$bad" -eq 0 || exit 1
|
|
echo " all libraries are 16 KB aligned"
|
|
|
|
# Record what shipped, so a failing run elsewhere can be tied to a version.
|
|
echo " sha256: $(sha256sum "$AAR" | cut -d' ' -f1)"
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# JVM tests: the routing matrix, the FFmpeg argument builder, the concat planner
|
|
# and the retry rule. No device needed, so this is the fastest signal on a PR.
|
|
# ---------------------------------------------------------------------------
|
|
unit:
|
|
name: Unit tests
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 30
|
|
steps:
|
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
|
|
- uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5.7.0
|
|
with:
|
|
distribution: temurin
|
|
java-version: '25' # Matches the daemon JVM pinned in gradle/gradle-daemon-jvm.properties
|
|
|
|
# Gradle is invoked through the committed wrapper rather than a setup action.
|
|
# The wrapper verifies its own distribution against distributionSha256Sum, and
|
|
# caching is a handful of lines, so the action earned little here.
|
|
- uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
|
|
with:
|
|
path: ${{ env.GRADLE_CACHE_PATHS }}
|
|
key: gradle-${{ runner.os }}-${{ hashFiles('**/*.gradle.kts', 'gradle/libs.versions.toml', 'gradle/wrapper/gradle-wrapper.properties') }}
|
|
restore-keys: gradle-${{ runner.os }}-
|
|
|
|
- name: Unit tests
|
|
run: ./gradlew :app:testDebugUnitTest
|
|
|
|
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
|
if: always()
|
|
with:
|
|
name: unit-test-report
|
|
path: app/build/reports/tests/
|
|
|
|
# Reported, not gated. A coverage floor is only meaningful against a measured
|
|
# baseline, and this is the thing that measures it -- currently 31% of lines. Once
|
|
# that number has settled, a jacocoTestCoverageVerification task can hold it.
|
|
- name: Coverage report
|
|
run: ./gradlew :app:jacocoTestReport
|
|
|
|
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
|
if: always()
|
|
with:
|
|
name: coverage-report
|
|
path: app/build/reports/jacoco/jacocoTestReport/
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Three tools, one job, because they answer three different questions and a
|
|
# developer wants all three answers at once rather than one per push.
|
|
#
|
|
# ktlint -- formatting. Owns it outright; detekt's formatting ruleset is off,
|
|
# so the two can never disagree about the same line.
|
|
# detekt -- static analysis. Its config lives in config/detekt/detekt.yml and
|
|
# overrides only the rules this codebase legitimately breaks.
|
|
# lint -- the Android-specific things neither of the others can see: opt-in
|
|
# markers, API-level misuse, manifest and resource problems.
|
|
#
|
|
# --continue is what makes it one round trip: a ktlint failure still lets detekt
|
|
# and lint report, so a red run hands over the whole list rather than the first
|
|
# item on it.
|
|
#
|
|
# No emulator and no FFmpeg archive needed, so this is the cheapest gate here and
|
|
# deliberately does not depend on the ffmpeg job.
|
|
# ---------------------------------------------------------------------------
|
|
static-analysis:
|
|
name: Static analysis
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 20
|
|
steps:
|
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
|
|
- uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5.7.0
|
|
with:
|
|
distribution: temurin
|
|
java-version: '25'
|
|
|
|
- uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
|
|
with:
|
|
path: ${{ env.GRADLE_CACHE_PATHS }}
|
|
key: gradle-${{ runner.os }}-${{ hashFiles('**/*.gradle.kts', 'gradle/libs.versions.toml', 'gradle/wrapper/gradle-wrapper.properties') }}
|
|
restore-keys: gradle-${{ runner.os }}-
|
|
|
|
# Shell is the other language in this repo -- four scripts, one of them the CI
|
|
# entry point itself -- and nothing was checking it. `git ls-files` rather than a
|
|
# fixed list, so a script added later is covered without editing this workflow.
|
|
#
|
|
# Full severity, `info` included. The findings it raises today are answered with
|
|
# targeted `disable` directives carrying their reason, the same way
|
|
# config/detekt/detekt.yml carries only the rules this codebase legitimately
|
|
# breaks. A blanket --severity=warning would have hidden them and the next real
|
|
# one alike.
|
|
#
|
|
# PINNED BY DIGEST, for the reason CLAUDE.md already gives for pinning ktlint,
|
|
# detekt and JaCoCo: a new rule in a linter makes files nobody touched stop
|
|
# passing, so CI goes red on a PR whose diff cannot explain it. That is not
|
|
# hypothetical here. The first cut of this step used the runner's ambient
|
|
# shellcheck, which is 0.9.0, and 0.9.0 reports a trap handler as seven
|
|
# unreachable commands (SC2317) where 0.11.0 reports it once on the declaration
|
|
# (SC2329) -- same script, same directive, different answer, and a red build on
|
|
# the PR that introduced the step. The version is printed so a finding that
|
|
# appears out of nowhere can be tied to a bump of this line.
|
|
- name: shellcheck
|
|
env:
|
|
SHELLCHECK: koalaman/shellcheck@sha256:61862eba1fcf09a484ebcc6feea46f1782532571a34ed51fedf90dd25f925a8d
|
|
run: |
|
|
docker run --rm "$SHELLCHECK" --version
|
|
git ls-files -z '*.sh' | xargs -0 -r docker run --rm -v "$PWD:/mnt" "$SHELLCHECK"
|
|
|
|
# `!cancelled()` rather than a plain sequence: a shellcheck failure above must not
|
|
# cost the ktlint/detekt/lint lists. Same reason this step passes --continue -- one
|
|
# round trip should produce every list, not stop at the first.
|
|
- name: ktlint, detekt and Android lint
|
|
if: '!cancelled()'
|
|
run: ./gradlew :app:ktlintCheck :app:detekt :app:lintDebug --continue --stacktrace
|
|
|
|
# The XML matters as much as the HTML: it is the one that can be diffed between
|
|
# runs to see what a change actually moved.
|
|
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
|
if: always()
|
|
with:
|
|
name: static-analysis-reports
|
|
path: |
|
|
app/build/reports/ktlint/
|
|
app/build/reports/detekt/
|
|
app/build/reports/lint-results-debug.html
|
|
app/build/reports/lint-results-debug.xml
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# One runner per API level, across the whole supported range.
|
|
#
|
|
# The range is the point: minSdk is 33, and the foreground service type differs
|
|
# across it -- none below 34, dataSync at 34, mediaProcessing from 35. Testing a
|
|
# single level would leave two thirds of that branch unexercised.
|
|
#
|
|
# It reaches targetSdk 37, but the API 37 row is not like the other four and the
|
|
# comment on it says how. Two tests are excluded there and run in their own
|
|
# advisory job below. See docs/api-37-emulator-crash.md.
|
|
#
|
|
# FFmpeg is not built here. The AAR is committed under bin/, so a red run means the
|
|
# code is broken rather than that a cross-compile hiccuped.
|
|
# ---------------------------------------------------------------------------
|
|
e2e:
|
|
name: E2E API ${{ matrix.label }}
|
|
runs-on: ubuntu-latest
|
|
needs: ffmpeg
|
|
timeout-minutes: 60
|
|
strategy:
|
|
# Report every API level rather than stopping at the first red one. Knowing
|
|
# whether a failure is universal or specific to one level is most of the
|
|
# diagnosis.
|
|
fail-fast: false
|
|
matrix:
|
|
include:
|
|
- label: "33"
|
|
api-level: "33"
|
|
- label: "34"
|
|
api-level: "34"
|
|
- label: "35"
|
|
api-level: "35"
|
|
- label: "36"
|
|
api-level: "36"
|
|
# API 37, and it is NOT the same device as the four rows above it.
|
|
#
|
|
# CAVEAT, read this before trusting a green here: this leg runs with
|
|
# SystemUI disabled and the framework restarted under it. No other leg
|
|
# and no Pixel run uses that configuration. It is defensible only because
|
|
# nothing THIS LEG RUNS touches system UI -- Media3, FFmpeg and
|
|
# WorkManager tests -- and because the alternative is no CI coverage of
|
|
# the level this app targets. **Anything that ever does depend on system
|
|
# UI must not trust this row.** E2E_DISABLE_SYSTEM_UI is what does it;
|
|
# .github/scripts/e2e-run.sh explains the mechanism and why every step of
|
|
# it is verified rather than assumed.
|
|
#
|
|
# "this leg" and not "this suite", since 2026-08-24, and the difference is
|
|
# now load-bearing: SafPickerRoundTripTest DOES touch system UI. It drives
|
|
# DocumentsUI and rotates the display, and both reach the gralloc mapper
|
|
# this image aborts in -- disabling SystemUI removes the IDLE trigger, not
|
|
# those. Measured per method on android-37.0: the ROTATION test takes the
|
|
# framework down (INSTRUMENTATION_ABORTED) and carries
|
|
# @FailsOnEmulatorApi37, so notAnnotation below keeps it off this row; the
|
|
# PICKER test passes and runs here like anything else. A rotation rebuilds
|
|
# every surface at once, and starting another app's activity does not.
|
|
#
|
|
# So this row does now run one test that depends on system UI, and the
|
|
# caveat above still applies to it: a green here is not evidence the picker
|
|
# works on a device with SystemUI running -- the Pixel release check is.
|
|
# docs/api-37-emulator-crash.md has the per-method measurements, and the
|
|
# correction that produced them.
|
|
#
|
|
# api-level must be "37.0". A bare 37 is not an SDK package and fails
|
|
# during setup, which cost a run to discover.
|
|
#
|
|
# notAnnotation removes the three tests that do not pass on this image; they
|
|
# run in the advisory job below, off the same marker so they cannot end up
|
|
# in both or neither. docs/api-37-emulator-crash.md has the measurements.
|
|
- label: "37"
|
|
api-level: "37.0"
|
|
disable-system-ui: "1"
|
|
gradle-args: "-Pandroid.testInstrumentationRunnerArguments.notAnnotation=org.libremediaconverter.FailsOnEmulatorApi37"
|
|
steps:
|
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
|
|
- uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5.7.0
|
|
with:
|
|
distribution: temurin
|
|
java-version: '25'
|
|
|
|
- uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
|
|
with:
|
|
path: ${{ env.GRADLE_CACHE_PATHS }}
|
|
key: gradle-${{ runner.os }}-${{ hashFiles('**/*.gradle.kts', 'gradle/libs.versions.toml', 'gradle/wrapper/gradle-wrapper.properties') }}
|
|
restore-keys: gradle-${{ runner.os }}-
|
|
|
|
# Without this the emulator falls back to software rendering and takes minutes
|
|
# longer to boot, when it boots at all.
|
|
- name: Enable KVM
|
|
run: |
|
|
echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' \
|
|
| sudo tee /etc/udev/rules.d/99-kvm4all.rules
|
|
sudo udevadm control --reload-rules
|
|
sudo udevadm trigger --name-match=kvm
|
|
|
|
- name: Instrumented tests
|
|
uses: reactivecircus/android-emulator-runner@a421e43855164a8197daf9d8d40fe71c6996bb0d # v2.38.0
|
|
# Both of these are empty on every row but 37, and both are read with a
|
|
# `:-` default in e2e-run.sh, so the four legs below 37 run the identical
|
|
# gradle command they always have.
|
|
env:
|
|
E2E_DISABLE_SYSTEM_UI: ${{ matrix.disable-system-ui }}
|
|
E2E_EXTRA_GRADLE_ARGS: ${{ matrix.gradle-args }}
|
|
with:
|
|
api-level: ${{ matrix.api-level }}
|
|
target: google_apis
|
|
arch: x86_64
|
|
profile: pixel_6
|
|
# swiftshader_indirect is correct here only because runners have no GPU to
|
|
# pass through. On a workstation the same setting routes through
|
|
# SwiftShader's JIT, which is a known crash source.
|
|
emulator-options: -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none
|
|
disable-animations: true
|
|
# The default userdata partition is not big enough for this APK once the
|
|
# FFmpeg libraries are in it. One level failed outright with "Requested
|
|
# internal only, but not enough space", and the margin was thin everywhere
|
|
# else, so give them all room.
|
|
disk-size: 8G
|
|
# Pinned because the emulator's own default is not uniform: it raises an
|
|
# undersized guest to a floor that varies by API level -- 2048M at 33, 2560M
|
|
# at 34 through 36 -- and skips levels it does not recognise entirely. 2560M
|
|
# is the highest of those floors, so no level gets less memory than it
|
|
# already had, and none of them depend on that heuristic any more.
|
|
ram-size: 2560M
|
|
# Build only the ABI the emulator can execute. FFmpeg's native libraries
|
|
# dominate the APK, so shipping arm64 to an x86_64 emulator doubles the
|
|
# install for code that can never run: 114 MB against 80 MB.
|
|
#
|
|
# Diagnostics live in .github/scripts/e2e-run.sh, not here. This action splits
|
|
# `script` on newlines and runs each line as its own `sh -c`, so a handler written
|
|
# inline has to fit on ONE line -- which is how the previous version ended up as an
|
|
# unreadable chain of semicolons. One line invokes the script; the script can use
|
|
# functions, and captures a hang as well as a failure. See its header.
|
|
script: bash .github/scripts/e2e-run.sh ${{ matrix.label }}
|
|
|
|
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
|
if: always()
|
|
with:
|
|
name: e2e-report-api${{ matrix.label }}
|
|
path: |
|
|
app/build/reports/androidTests/
|
|
app/build/outputs/androidTest-results/
|
|
if-no-files-found: warn
|
|
|
|
# The streamed logcat and the failure dump. Uploaded always, because a leg that goes red
|
|
# once and green on re-run is exactly the one worth reading afterwards, and by then the
|
|
# emulator is long gone.
|
|
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
|
if: always()
|
|
with:
|
|
name: e2e-diagnostics-api${{ matrix.label }}
|
|
path: |
|
|
${{ runner.temp }}/logcat-api${{ matrix.label }}.txt
|
|
${{ runner.temp }}/diagnostics-api${{ matrix.label }}.txt
|
|
if-no-files-found: warn
|
|
|
|
# Only exists when the wrapper timeout tripped, so `ignore` keeps healthy runs quiet
|
|
# instead of warning on every green leg.
|
|
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
|
if: always()
|
|
with:
|
|
name: e2e-wedge-api${{ matrix.label }}
|
|
path: ${{ runner.temp }}/wedge-diagnostics-api${{ matrix.label }}.txt
|
|
if-no-files-found: ignore
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# The three API 37 tests the gating row above excludes, run on their own so they
|
|
# stay visible instead of disappearing behind a notAnnotation.
|
|
#
|
|
# continue-on-error: it reports, it never blocks. That is the whole reason it is
|
|
# a separate job rather than a sixth matrix row: a row would share the gating
|
|
# job's `E2E API <label>` name, and a check cannot be both required and advisory
|
|
# under one name.
|
|
#
|
|
# It was named for WHAT IT RUNS, and that is now APPROXIMATE rather than exact.
|
|
# When this job was created it held two tests, both driving a full H.264 -> H.265
|
|
# hardware transcode through Media3Engine -- which is exactly what separated them
|
|
# from the two Media3EngineTest cases that pass here, since those two never decode
|
|
# video. Since 2026-08-25 it also holds SafPickerRoundTripTest's rotation case,
|
|
# which drives no transcode at all: a real rotation rebuilds every surface at once
|
|
# and takes the framework down on this image (INSTRUMENTATION_ABORTED), which is a
|
|
# different failure from the decoder one below.
|
|
#
|
|
# The name is kept anyway, and that is a decision rather than an oversight. This is
|
|
# not a required context, it is red on every PR by design, and it is one people
|
|
# have learned to look for -- renaming a check costs more than the imprecision
|
|
# does. **The marker is the definition, not the name:** what this job holds is the
|
|
# tests that cannot pass on the API 37 emulator image, whatever their subject. The
|
|
# theory about the Media3 pair is in the next paragraph, where it can be corrected
|
|
# without touching the name.
|
|
#
|
|
# THEORY, NOT SETTLED: the exception surfaces at `dequeueOutputBuffer` on
|
|
# `c2.goldfish.h264.decoder`, the emulator's own codec, which gets its frames out
|
|
# of a host-side colour buffer -- the same readback machinery that aborts
|
|
# surfaceflinger on this image. It is about the MEDIA3 PAIR only; the rotation
|
|
# case above fails for its own reason. What is MEASURED is narrower: those two
|
|
# fail on
|
|
# the API 37 emulator image; pass at API 36 on this runner under the same renderer
|
|
# AND the same SystemUI-disable path; pass at API 33-36 without that path at all,
|
|
# since nothing below 37 needs it; and pass on a physical Pixel 10 Pro XL at 37. That the decoder is the culprit rather than something else
|
|
# the decode path touches is inference. docs/api-37-emulator-crash.md separates
|
|
# the two, and the images also differ on the encoder side, which is why "broken
|
|
# h264 decoder" is not written into this job's name.
|
|
#
|
|
# WHEN THIS GOES GREEN, delete the annotation rather than this job: the gating
|
|
# row picks the tests back up automatically, and this job goes empty and can go
|
|
# with it.
|
|
# ---------------------------------------------------------------------------
|
|
e2e-api37-advisory:
|
|
name: E2E API 37 Media3 hardware transcode (advisory)
|
|
runs-on: ubuntu-latest
|
|
needs: ffmpeg
|
|
timeout-minutes: 60
|
|
continue-on-error: true
|
|
env:
|
|
E2E_LABEL: "37-media3-transcode"
|
|
steps:
|
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
|
|
- uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5.7.0
|
|
with:
|
|
distribution: temurin
|
|
java-version: '25'
|
|
|
|
- uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
|
|
with:
|
|
path: ${{ env.GRADLE_CACHE_PATHS }}
|
|
key: gradle-${{ runner.os }}-${{ hashFiles('**/*.gradle.kts', 'gradle/libs.versions.toml', 'gradle/wrapper/gradle-wrapper.properties') }}
|
|
restore-keys: gradle-${{ runner.os }}-
|
|
|
|
- name: Enable KVM
|
|
run: |
|
|
echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' \
|
|
| sudo tee /etc/udev/rules.d/99-kvm4all.rules
|
|
sudo udevadm control --reload-rules
|
|
sudo udevadm trigger --name-match=kvm
|
|
|
|
- name: Instrumented tests
|
|
uses: reactivecircus/android-emulator-runner@a421e43855164a8197daf9d8d40fe71c6996bb0d # v2.38.0
|
|
env:
|
|
E2E_DISABLE_SYSTEM_UI: "1"
|
|
# The complement of the gating row's notAnnotation, off the same marker,
|
|
# so a test can never be excluded from both jobs or run in both.
|
|
E2E_EXTRA_GRADLE_ARGS: "-Pandroid.testInstrumentationRunnerArguments.annotation=org.libremediaconverter.FailsOnEmulatorApi37"
|
|
with:
|
|
# Every device pin below matches the gating row exactly, so a difference
|
|
# between the two jobs is the test selection and nothing else.
|
|
api-level: "37.0"
|
|
target: google_apis
|
|
arch: x86_64
|
|
profile: pixel_6
|
|
emulator-options: -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none
|
|
disable-animations: true
|
|
disk-size: 8G
|
|
ram-size: 2560M
|
|
script: bash .github/scripts/e2e-run.sh ${{ env.E2E_LABEL }}
|
|
|
|
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
|
if: always()
|
|
with:
|
|
name: e2e-report-api${{ env.E2E_LABEL }}
|
|
path: |
|
|
app/build/reports/androidTests/
|
|
app/build/outputs/androidTest-results/
|
|
if-no-files-found: warn
|
|
|
|
# Uploaded always, and here it matters more than anywhere else in this file:
|
|
# this job is EXPECTED to be red, so the logcat is the only thing that says
|
|
# whether it is red for the known reason or for a new one.
|
|
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
|
if: always()
|
|
with:
|
|
name: e2e-diagnostics-api${{ env.E2E_LABEL }}
|
|
path: |
|
|
${{ runner.temp }}/logcat-api${{ env.E2E_LABEL }}.txt
|
|
${{ runner.temp }}/diagnostics-api${{ env.E2E_LABEL }}.txt
|
|
if-no-files-found: warn
|
|
|
|
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
|
if: always()
|
|
with:
|
|
name: e2e-wedge-api${{ env.E2E_LABEL }}
|
|
path: ${{ runner.temp }}/wedge-diagnostics-api${{ env.E2E_LABEL }}.txt
|
|
if-no-files-found: ignore
|