Files
LibreMediaConverter/.github/workflows/status_check.yml
T
JMR-dev 0702916229 Say what the advisory API 37 job actually found, so a new failure is not invisible
That job is continue-on-error and red on every PR by design, which CLAUDE.md
states plainly -- and that instruction is exactly why nobody reads it. Nothing in
a red X separates "the known three" from "the known three plus yours".

A bare failure count would not have fixed it, and this is measured rather than
assumed. The run is usually truncated: seven of eight advisory runs read on
2026-08-25 ended in `Test run failed to complete. Expected 3 tests, received 2.`
with INSTRUMENTATION_ABORTED, and one did not. A count taken from a truncated run
misleads in both directions -- a fourth marked test can still yield the same
number if the abort lands earlier, and the known set getting worse can lower it.

The test XML does not rescue it either, which was the thing worth checking before
building on it: it IS written for an aborted run, and it reports a tidy
tests="3" failures="3" for a run the runner had just described as truncated. So
the XML is the authority on how many results landed, the runner's own output is
the only authority on whether the run finished, and the report reads both and
says which number came from where.

The baseline is one number beside the marker, because the marker means "cannot
pass on this image": the count is both how many tests the advisory leg runs and
how many should fail. A smaller failure count is the interesting direction -- it
means one now passes, which is the documented trigger for deleting the
annotation.

Nothing about the job's status changes. It stays continue-on-error, stays red,
stays out of the required contexts; a deviation is a ::notice::, never an
::error::. The report is a separate script so it can be run against a real log
saved from a real CI run, which is how the comparison was shown to fire.

The gating legs get the shape without the comparison: they run the whole suite,
so comparing there would announce a deviation five times a run -- but a truncated
run reporting fewer results than it ran is what #108 looks like, and "completed
cleanly" is the field that would show it.

Closes #83
2026-08-25 16:04:13 -05:00

518 lines
26 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"
# actionlint closes the half shellcheck cannot see. The step above reads .sh files;
# a good deal of this repo's bash lives in inline `run:` blocks instead -- the release
# verification here, the emulator setup and teardown in this file and in
# api37-debug.yml. actionlint parses each workflow and runs shellcheck over every
# `run:`, on top of its own checks for expression syntax, `needs:` references, matrix
# keys and action input names.
#
# Pinned by digest for the same reason shellcheck is, and with a second reason of its
# own: actionlint's documented install is `bash <(curl -s .../download-actionlint.bash)`
# off a moving branch, which would sit badly in a repo that pins every action by SHA.
- name: actionlint
env:
ACTIONLINT: rhysd/actionlint@sha256:9d36088643581e728c969f35141f88139fec77280b2be23c1f66f8e40e1025e7
run: |
docker run --rm "$ACTIONLINT" -version
docker run --rm -v "$PWD:/repo" -w /repo "$ACTIONLINT" -color
# `!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 a POINT release. A bare 37 is not an SDK package and
# fails during setup, which cost a run to discover. `37.0` is the choice
# here rather than the only option: `37.1` and `37.2-beta*` exist and
# abort the same way, and api37-debug.yml's inputs document both, with
# the wrinkle that above 37.0 they ship only as google_apis_ps16k.
# docs/api-37-emulator-crash.md measures 37.0 rev 6 and 37.1 rev 8 side
# by side, so pinning 37.0 is a decision, not a constraint.
#
# 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
${{ runner.temp }}/gradle-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"
# Turns on the baseline comparison in the run-shape report, and only here. Every leg
# prints the shape; this is the one that also says whether it matches
# FAILS_ON_EMULATOR_API37_BASELINE, because this is the one whose test list is the
# marker. A deviation is a `::notice::` -- this job stays continue-on-error and stays
# out of the required contexts, so nothing the report finds can change a conclusion.
E2E_ADVISORY: "1"
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
${{ runner.temp }}/gradle-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