Skip to content

Commit d1bbf65

Browse files
authored
Merge pull request #394 from superwall/ir/fix/split-tests-ci
Split CI tests & caching improvements
2 parents a9017a6 + c647bab commit d1bbf65

9 files changed

Lines changed: 553 additions & 70 deletions

File tree

.github/badges/branches.svg

Lines changed: 1 addition & 1 deletion
Loading

.github/badges/jacoco.svg

Lines changed: 1 addition & 1 deletion
Loading

.github/workflows/build+test.yml

Lines changed: 275 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -18,64 +18,317 @@ concurrency:
1818

1919
jobs:
2020
build:
21-
runs-on: macos-15-intel
21+
runs-on: ubuntu-latest
2222

2323
steps:
24-
- uses: actions/checkout@v3
24+
- uses: actions/checkout@v4
2525
with:
26-
fetch-depth: 0
2726
ref: ${{ github.head_ref }}
2827
token: ${{ secrets.GITHUB_TOKEN }}
2928

30-
- name: Set Up JDK
31-
uses: actions/setup-java@v3
29+
- name: Set up JDK 17
30+
uses: actions/setup-java@v4
3231
with:
33-
distribution: 'zulu' # See 'Supported distributions' for available options
3432
java-version: '17'
35-
cache: 'gradle'
33+
distribution: 'temurin'
34+
35+
- name: Setup Gradle
36+
uses: gradle/actions/setup-gradle@v3
3637

3738
# Allow us to run the command
3839
- name: Change wrapper permissions
3940
run: chmod +x ./gradlew
4041

41-
# Run Build & Test the Project
42-
- name: Build gradle project
43-
run: ./gradlew build
42+
# Assemble (no tests / no lint — those run in dedicated jobs)
43+
- name: Assemble gradle project
44+
run: ./gradlew assemble -x lint -x lintDebug -x lintVitalRelease
45+
46+
- name: Build test project
47+
run: ./gradlew :app:assembleAndroidTest -DtestBuildType=debug -x lint -x lintDebug -x lintVitalRelease
48+
49+
- name: Run tests on Firebase Test Lab
50+
uses: asadmansr/Firebase-Test-Lab-Action@v1.0
51+
with:
52+
arg-spec: '.github/firebase-tests.yml:android-pixel-7'
53+
env:
54+
SERVICE_ACCOUNT: ${{ secrets.SERVICE_ACCOUNT }}
55+
56+
unit-tests:
57+
runs-on: ubuntu-latest
58+
59+
steps:
60+
- uses: actions/checkout@v4
61+
with:
62+
ref: ${{ github.head_ref }}
63+
token: ${{ secrets.GITHUB_TOKEN }}
64+
65+
- name: Set up JDK 17
66+
uses: actions/setup-java@v4
67+
with:
68+
java-version: '17'
69+
distribution: 'temurin'
70+
71+
- name: Setup Gradle
72+
uses: gradle/actions/setup-gradle@v3
73+
74+
- name: Change wrapper permissions
75+
run: chmod +x ./gradlew
76+
77+
- name: Run unit tests with coverage
78+
run: ./gradlew :superwall:testDebugUnitTest -x lint -x lintDebug -x lintVitalRelease
79+
80+
- name: Upload unit test coverage data
81+
uses: actions/upload-artifact@v4
82+
if: always()
83+
with:
84+
name: unit-test-coverage
85+
path: superwall/build/jacoco/
86+
87+
- name: Upload unit test reports
88+
uses: actions/upload-artifact@v4
89+
if: always()
90+
with:
91+
name: unit-test-reports
92+
path: |
93+
superwall/build/reports/tests/
94+
superwall/build/test-results/
95+
96+
# Single-runner pre-job that populates the AVD snapshot cache before the matrix
97+
# fans out, so the 5 shards don't race to create the same snapshot on a cold cache.
98+
# On a warm cache this job is ~30s of overhead (checkout + cache restore + skip).
99+
warm-emulator-cache:
100+
runs-on: ubuntu-latest
101+
steps:
102+
- uses: actions/checkout@v4
103+
with:
104+
ref: ${{ github.head_ref }}
105+
token: ${{ secrets.GITHUB_TOKEN }}
44106

45-
- name: Run unit and instrumentation tests with coverage
107+
- name: AVD cache
108+
uses: actions/cache@v4
109+
id: avd-cache
110+
with:
111+
path: |
112+
~/.android/avd/*
113+
~/.android/adb*
114+
key: avd-api33-google_apis-x86_64-pixel_7_pro-v1
115+
116+
# Everything below only runs on a cold cache. On a warm cache the job ends here.
117+
- name: Free disk space
118+
if: steps.avd-cache.outputs.cache-hit != 'true'
119+
uses: jlumbroso/free-disk-space@v1.3.1
120+
with:
121+
android: false
122+
tool-cache: true
123+
dotnet: true
124+
haskell: true
125+
swap-storage: true
126+
docker-images: false
127+
large-packages: false
128+
129+
- name: Enable KVM
130+
if: steps.avd-cache.outputs.cache-hit != 'true'
131+
run: |
132+
echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules
133+
sudo udevadm control --reload-rules
134+
sudo udevadm trigger --name-match=kvm
135+
136+
- name: Set up JDK 17
137+
if: steps.avd-cache.outputs.cache-hit != 'true'
138+
uses: actions/setup-java@v4
139+
with:
140+
java-version: '17'
141+
distribution: 'temurin'
142+
143+
- name: Setup Gradle
144+
if: steps.avd-cache.outputs.cache-hit != 'true'
145+
uses: gradle/actions/setup-gradle@v3
146+
147+
- name: Create AVD and generate snapshot for caching
148+
if: steps.avd-cache.outputs.cache-hit != 'true'
46149
uses: reactivecircus/android-emulator-runner@v2
47150
with:
48151
api-level: 33
49152
target: google_apis
50153
arch: x86_64
51154
profile: pixel_7_pro
52-
force-avd-creation: true
155+
force-avd-creation: false
53156
ram-size: 4096M
54157
emulator-boot-timeout: 1800
55-
disable-animations: true
158+
disable-animations: false
159+
emulator-options: >
160+
-no-window
161+
-gpu swiftshader_indirect
162+
-no-boot-anim
163+
-noaudio
164+
-camera-back none
165+
-camera-front none
166+
script: echo "Generated AVD snapshot for caching."
167+
168+
instrumentation-tests:
169+
runs-on: ubuntu-latest
170+
needs: [warm-emulator-cache]
171+
# Run even if warm-emulator-cache failed — the per-shard warm-up step below
172+
# acts as a fallback. Don't run on workflow cancellation.
173+
if: ${{ !cancelled() }}
174+
strategy:
175+
fail-fast: false
176+
matrix:
177+
shard: [0, 1, 2, 3, 4]
178+
179+
steps:
180+
- uses: actions/checkout@v4
181+
with:
182+
ref: ${{ github.head_ref }}
183+
token: ${{ secrets.GITHUB_TOKEN }}
184+
185+
# Reclaim ~10+ GB on the runner before the AVD cache restore + emulator boot.
186+
# Ubuntu runners ship near-full and instrumentation jobs can hit "no space
187+
# left on device" around the emulator boot or coverage write otherwise.
188+
- name: Free disk space
189+
uses: jlumbroso/free-disk-space@v1.3.1
190+
with:
191+
android: false # keep Android SDK / tools
192+
tool-cache: true # rm -rf "$AGENT_TOOLSDIRECTORY"
193+
dotnet: true # rm -rf /usr/share/dotnet
194+
haskell: true # rm -rf /opt/ghc
195+
swap-storage: true # rm -f /mnt/swapfile (4 GiB)
196+
docker-images: false # slow (~16s), skip
197+
large-packages: false # slow (includes google-cloud-sdk), skip
198+
199+
- name: Enable KVM
200+
run: |
201+
echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules
202+
sudo udevadm control --reload-rules
203+
sudo udevadm trigger --name-match=kvm
204+
205+
- name: Set up JDK 17
206+
uses: actions/setup-java@v4
207+
with:
208+
java-version: '17'
209+
distribution: 'temurin'
210+
211+
- name: Setup Gradle
212+
uses: gradle/actions/setup-gradle@v3
213+
214+
- name: Change wrapper permissions
215+
run: chmod +x ./gradlew
216+
217+
# Cache the AVD across runs. Key is shared across all 5 shards because they
218+
# all use the same api-level/target/arch/profile, so they all share one snapshot.
219+
- name: AVD cache
220+
uses: actions/cache@v4
221+
id: avd-cache
222+
with:
223+
path: |
224+
~/.android/avd/*
225+
~/.android/adb*
226+
key: avd-api33-google_apis-x86_64-pixel_7_pro-v1
227+
228+
# Cold-cache only: create the AVD and let the emulator save a snapshot on shutdown.
229+
# Note the deliberate ABSENCE of -no-snapshot-save in emulator-options here, and
230+
# disable-animations is left false so the snapshot is generic and reusable.
231+
- name: Create AVD and generate snapshot for caching
232+
if: steps.avd-cache.outputs.cache-hit != 'true'
233+
uses: reactivecircus/android-emulator-runner@v2
234+
with:
235+
api-level: 33
236+
target: google_apis
237+
arch: x86_64
238+
profile: pixel_7_pro
239+
force-avd-creation: false
240+
ram-size: 4096M
241+
emulator-boot-timeout: 1800
242+
disable-animations: false
56243
emulator-options: >
57244
-no-window
58245
-gpu swiftshader_indirect
59246
-no-boot-anim
60247
-noaudio
61248
-camera-back none
62249
-camera-front none
250+
script: echo "Generated AVD snapshot for caching."
251+
252+
- name: Run instrumentation tests with coverage (shard ${{ matrix.shard }}/5)
253+
uses: reactivecircus/android-emulator-runner@v2
254+
with:
255+
api-level: 33
256+
target: google_apis
257+
arch: x86_64
258+
profile: pixel_7_pro
259+
force-avd-creation: false
260+
ram-size: 4096M
261+
emulator-boot-timeout: 1800
262+
disable-animations: true
263+
emulator-options: >
63264
-no-snapshot-save
64-
script: ./gradlew :superwall:jacocoFullReport
265+
-no-window
266+
-gpu swiftshader_indirect
267+
-no-boot-anim
268+
-noaudio
269+
-camera-back none
270+
-camera-front none
271+
script: >
272+
./gradlew :superwall:connectedDebugAndroidTest
273+
-Pandroid.testInstrumentationRunnerArguments.numShards=5
274+
-Pandroid.testInstrumentationRunnerArguments.shardIndex=${{ matrix.shard }}
275+
-x lint -x lintDebug -x lintVitalRelease
65276
66-
- name: Build test project
67-
run: ./gradlew :app:assembleAndroidTest -DtestBuildType=debug
277+
- name: Upload instrumentation test coverage data
278+
uses: actions/upload-artifact@v4
279+
if: always()
280+
with:
281+
name: instrumentation-test-coverage-shard-${{ matrix.shard }}
282+
path: superwall/build/outputs/code_coverage/
68283

69-
- name: Upload test reports
284+
- name: Upload instrumentation test reports
70285
uses: actions/upload-artifact@v4
71286
if: always()
72287
with:
73-
name: test-reports
288+
name: instrumentation-test-reports-shard-${{ matrix.shard }}
74289
path: |
75-
**/build/reports/tests/
76-
**/build/test-results/
77-
**/build/reports/androidTests/connected/
78-
**/build/outputs/code_coverage/
290+
superwall/build/reports/androidTests/connected/
291+
superwall/build/outputs/androidTest-results/
292+
293+
coverage-report:
294+
runs-on: ubuntu-latest
295+
needs: [unit-tests, instrumentation-tests]
296+
# Run as long as unit-tests succeeded, even if some/all instrumentation
297+
# shards failed — a partial report is better than no report or badge update.
298+
if: ${{ !cancelled() && needs.unit-tests.result == 'success' }}
299+
300+
steps:
301+
- uses: actions/checkout@v4
302+
with:
303+
ref: ${{ github.head_ref }}
304+
token: ${{ secrets.GITHUB_TOKEN }}
305+
306+
- name: Set up JDK 17
307+
uses: actions/setup-java@v4
308+
with:
309+
java-version: '17'
310+
distribution: 'temurin'
311+
312+
- name: Setup Gradle
313+
uses: gradle/actions/setup-gradle@v3
314+
315+
- name: Change wrapper permissions
316+
run: chmod +x ./gradlew
317+
318+
- name: Download unit test coverage data
319+
uses: actions/download-artifact@v4
320+
with:
321+
name: unit-test-coverage
322+
path: superwall/build/jacoco/
323+
324+
- name: Download instrumentation test coverage data (all shards)
325+
uses: actions/download-artifact@v4
326+
with:
327+
pattern: instrumentation-test-coverage-shard-*
328+
path: superwall/build/outputs/code_coverage/
329+
330+
- name: Generate combined coverage report
331+
run: ./gradlew :superwall:compileDebugSources :superwall:jacocoTestReport -x testDebugUnitTest -x lint -x lintDebug -x lintVitalRelease
79332

80333
- name: Upload coverage report
81334
uses: actions/upload-artifact@v4
@@ -107,10 +360,3 @@ jobs:
107360
git commit -m "Update coverage badge [skip ci]"
108361
git push origin HEAD:${{ github.head_ref }}
109362
fi
110-
111-
- name: Run tests on Firebase Test Lab
112-
uses: asadmansr/Firebase-Test-Lab-Action@v1.0
113-
with:
114-
arg-spec: '.github/firebase-tests.yml:android-pixel-7'
115-
env:
116-
SERVICE_ACCOUNT: ${{ secrets.SERVICE_ACCOUNT }}

.github/workflows/on-release.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,4 +13,4 @@ jobs:
1313
with:
1414
token: ${{ secrets.MAIN_REPO_PAT }}
1515
repository: superwall/paywall-next
16-
event-type: android-release
16+
event-type: android-release

0 commit comments

Comments
 (0)