fix(ci): wire six orphaned modules into test shards, stabilize cache keys (#6407)

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
James RichandClaude Fable 5 authored and GitHub committed 2026-07-24 11:28:08 -05:00
1 parent 480334a0ad
commit 84aae4b589
73 files changed
+1840 -9

No files matched your search

+85
View File
@@ -22,6 +22,7 @@ jobs:
timeout-minutes: 10
outputs:
android: ${{ steps.filter.outputs.android }}
screenshots: ${{ steps.filter.outputs.screenshots }}
steps:
- uses: actions/checkout@v7.0.1
- uses: dorny/paths-filter@v4
@@ -29,6 +30,24 @@ jobs:
with:
token: ''
filters: |
# Anything in screenshot-tests' dependency closure (feature/* and
# core/* transitively) or the build machinery that shapes rendering.
# androidApp-, desktopApp- and docs-only changes skip screenshots on
# PRs; the merge queue always runs them as the final gate.
screenshots:
- 'screenshot-tests/**'
- 'core/**'
- 'feature/**'
- 'build-logic/**'
- 'config/**'
- 'gradle/**'
- 'build.gradle.kts'
- 'settings.gradle.kts'
- 'gradle.properties'
- 'config.properties'
- 'compose_compiler_config.conf'
- '.github/workflows/**'
- '.github/actions/**'
android:
# CI/workflow implementation
- '.github/workflows/**'
@@ -93,6 +112,71 @@ jobs:
print('check-changes filter is aligned with settings.gradle module roots.')
PY
# Second drift guard: the shard task lists in reusable-check.yml are
# hand-maintained and have silently dropped modules before (discovery,
# docs, wifi-provision, car, datastore, konsist had tests that never ran
# in CI). Every module in settings.gradle.kts must appear in the shard
# matrix or be explicitly exempted — and an exempt module that gains test
# sources fails the guard until it is wired into a shard.
- name: Verify every module with tests is wired into a CI test shard
run: |
python3 - <<'PY'
import re
from pathlib import Path
settings = Path('settings.gradle.kts').read_text()
check = Path('.github/workflows/reusable-check.yml').read_text()
modules = set(re.findall(r'"(:[^"]+)"', settings))
# Modules whose tests run in a dedicated job or only on-device --
# exempt unconditionally.
covered_elsewhere = {
':screenshot-tests', # dedicated screenshot-check job
':docs-screenshots', # doc-screenshot generation (screenshot tooling)
':baselineprofile', # benchmark module, instrumented-only
}
# Modules with no unit-test sources yet. One of these gaining test
# sources fails the guard: move it into a shard in reusable-check.yml
# and remove it from this list.
no_tests_yet = {
':core:di',
':core:nfc',
':core:resources',
':feature:widget',
}
shards = check.split('# ── Sharded Unit Tests')[1].split('# ── Android Build')[0]
def has_test_sources(module):
root = Path(module.lstrip(':').replace(':', '/'))
return any(
f.suffix == '.kt'
for d in root.glob('src/*')
if 'test' in d.name.lower()
for f in d.rglob('*.kt')
)
problems = []
for m in sorted(modules):
if m in covered_elsewhere:
continue
if m in no_tests_yet:
if has_test_sources(m):
problems.append(f'{m} is exempt as test-less but has test sources -- wire it into a shard')
# Require an actual test task (allTests / test / test<Variant>UnitTest),
# not just any reference -- a lone kover entry must not satisfy this.
elif not re.search(rf'{re.escape(m)}:(allTests|test)', shards):
problems.append(f'{m} has no test task in any reusable-check.yml test shard')
if problems:
print('CI shard coverage drift detected:')
for p in problems:
print(' -', p)
raise SystemExit(1)
exempt = covered_elsewhere | no_tests_yet
print(f'{len(modules) - len(modules & exempt)} modules verified against the shard matrix.')
PY
# 1c. STORE METADATA: Enforce store-listing length limits (e.g. the F-Droid /
# Play 80-char short_description). These files are mirrored from Crowdin, so
@@ -139,6 +223,7 @@ jobs:
uses: ./.github/workflows/reusable-check.yml
with:
run_lint: true
run_screenshot_tests: ${{ needs.check-changes.outputs.screenshots == 'true' }}
run_unit_tests: true
run_coverage: false
upload_artifacts: true
+42 -9
View File
@@ -6,6 +6,9 @@ on:
run_lint:
type: boolean
default: true
run_screenshot_tests:
type: boolean
default: true
run_unit_tests:
type: boolean
default: true
@@ -59,10 +62,18 @@ jobs:
# `needs:` one, which serialized TWO runner-queue waits per run — under pool
# congestion the setup job alone sat queued for ~10 minutes before the real
# jobs could even enter the queue. Its two outputs are now sourced without a
# job: cache writability is the pure expression above, and the versionCode is
# derived by the build itself (GitVersionValueSource runs
# `git rev-list --count HEAD` when the VERSION_CODE env var is unset), which
# is why every Gradle job below checks out full — blob-less — history.
# job: cache writability is the pure expression above, and the versionCode
# comes from one of two places:
# - Jobs whose artifacts ship (android-check, build-desktop,
# build-flatpak-src) check out full — blob-less — history so the build's
# GitVersionValueSource derives the real commit-count versionCode.
# - Validation-only jobs (lint, screenshot, test shards) pin VERSION_CODE
# to a constant instead: their outputs are never shipped, and the real
# value changes on every commit, which poisons the versionCode-dependent
# task chain (BuildConfig -> compile -> test/lint) in the build cache.
# A stable value keeps those cache keys identical across commits so
# unchanged modules resolve FROM-CACHE on every run. Keep the pinned
# value (30000000) identical across the three jobs for the same reason.
# ── Lint & Static Analysis ──────────────────────────────────────────
lint-check:
@@ -71,11 +82,14 @@ jobs:
contents: read
timeout-minutes: 30
if: inputs.run_lint == true
env:
VERSION_CODE: 30000000 # pinned for cache stability -- see comment above
steps:
- name: Checkout code
uses: actions/checkout@v7.0.1
with:
# Full history: spotless ratchetFrom("origin/main") diffs against main.
fetch-depth: 0
filter: 'blob:none'
submodules: true
@@ -96,14 +110,15 @@ jobs:
permissions:
contents: read
timeout-minutes: 20
if: inputs.run_lint == true
if: inputs.run_lint == true && inputs.run_screenshot_tests == true
env:
VERSION_CODE: 30000000 # pinned for cache stability -- see comment above
steps:
- name: Checkout code
uses: actions/checkout@v7.0.1
with:
fetch-depth: 0
filter: 'blob:none'
fetch-depth: 1
submodules: true
- name: Gradle Setup
@@ -311,6 +326,9 @@ jobs:
# in roughly the same wall time. shard-app already compiles the full
# dependency graph (androidApp depends on everything), so hosting extra
# core-module tests there costs only their execution time.
# These lists are hand-maintained; pull-request.yml's check-changes job
# guards them against drift (every module in settings.gradle.kts with test
# sources must appear here or be explicitly exempted).
# shard-core: remaining core:* KMP module tests (allTests)
# shard-feature: feature:* KMP module tests + :core:service
# shard-app: Pure-Android/JVM tests (androidApp, desktopApp,
@@ -321,6 +339,8 @@ jobs:
contents: read
timeout-minutes: 45
if: inputs.run_unit_tests == true
env:
VERSION_CODE: 30000000 # pinned for cache stability -- see comment above
strategy:
fail-fast: false
matrix:
@@ -330,7 +350,9 @@ jobs:
:core:ble:allTests
:core:common:allTests
:core:data:allTests
:core:datastore:allTests
:core:domain:allTests
:core:konsist:allTests
:core:model:allTests
:core:navigation:allTests
:core:prefs:allTests
@@ -342,7 +364,9 @@ jobs:
:core:ble:koverXmlReport
:core:common:koverXmlReport
:core:data:koverXmlReport
:core:datastore:koverXmlReport
:core:domain:koverXmlReport
:core:konsist:koverXmlReport
:core:model:koverXmlReport
:core:navigation:koverXmlReport
:core:prefs:koverXmlReport
@@ -354,21 +378,27 @@ jobs:
tasks: >-
:core:service:allTests
:feature:connections:allTests
:feature:discovery:allTests
:feature:docs:allTests
:feature:firmware:allTests
:feature:intro:allTests
:feature:map:allTests
:feature:messaging:allTests
:feature:node:allTests
:feature:settings:allTests
:feature:wifi-provision:allTests
kover: >-
:core:service:koverXmlReport
:feature:connections:koverXmlReport
:feature:discovery:koverXmlReport
:feature:docs:koverXmlReport
:feature:firmware:koverXmlReport
:feature:intro:koverXmlReport
:feature:map:koverXmlReport
:feature:messaging:koverXmlReport
:feature:node:koverXmlReport
:feature:settings:koverXmlReport
:feature:wifi-provision:koverXmlReport
- name: shard-app
tasks: >-
:androidApp:testFdroidDebugUnitTest
@@ -378,6 +408,8 @@ jobs:
:core:barcode:testGoogleDebugUnitTest
:core:database:allTests
:core:network:allTests
:feature:car:testFdroidDebugUnitTest
:feature:car:testGoogleDebugUnitTest
kover: >-
:androidApp:koverXmlReportFdroidDebug
:androidApp:koverXmlReportGoogleDebug
@@ -386,13 +418,14 @@ jobs:
:desktopApp:koverXmlReport
:core:database:koverXmlReport
:core:network:koverXmlReport
:feature:car:koverXmlReportFdroidDebug
:feature:car:koverXmlReportGoogleDebug
steps:
- name: Checkout code
uses: actions/checkout@v7.0.1
with:
fetch-depth: 0
filter: 'blob:none'
fetch-depth: 1
submodules: true
- name: Gradle Setup