test: add emulator-free JVM unit tests + JaCoCo coverage gate (97.8%)#13
test: add emulator-free JVM unit tests + JaCoCo coverage gate (97.8%)#13AkashBrowserStack wants to merge 5 commits into
Conversation
Adds real, emulator-free unit test coverage for the :espresso library and a
CI coverage gate. Achieves 97.79% line coverage (266/272 lines); the only 6
uncovered lines are dead/defensive remote-CSV-fallback code in MetadataHelper
that cannot run on the JVM (or a device) without a live network call.
- 71 JVM unit tests across all 9 production classes:
- pure-JVM: Environment, Cache, ScreenshotOptions, Tile, CliWrapper
(CliWrapper driven against a small ServerSocket-based StubHttpServer that
covers every response / version-gate / exception branch)
- Robolectric: Metadata, MetadataHelper, AppPercy, GenericProvider
- Mockito (inline + mockStatic/mockConstruction) for AppPercy's internal
CliWrapper/GenericProvider and the androidx Screenshot.capture() bridge
- GenericProvider: extract Screenshot bitmap acquisition into an overridable
protected captureBitmap() seam. Behavior-preserving: production still calls
Screenshot.capture(); tests can supply a fake Bitmap. Public API unchanged.
- espresso/build.gradle: apply jacoco; add test deps (junit, org.json,
robolectric 4.10.3, mockito 5.2); includeAndroidResources + returnDefaultValues;
includeNoLocationClasses=true so Robolectric-loaded classes are counted;
jacocoTestReport + jacocoTestCoverageVerification (LINE floor 0.977).
- .github/workflows/test.yml: PR + push gate on JDK 17 + setup-android (no
emulator) running :espresso:testDebugUnitTest + jacocoTestCoverageVerification.
NOTE (latent bug, NOT fixed here): espresso/src/main/resources/devices.csv is
UTF-16 LE but MetadataHelper.parseBufferReader reads it with the default charset.
It works only because sanitizedString() strips the interleaved NUL bytes; tests
assert against the ACTUAL parsed output rather than masking this.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
| name: ":espresso unit tests + JaCoCo coverage gate" | ||
| runs-on: ubuntu-latest | ||
| steps: | ||
| - name: Check out code | ||
| uses: actions/checkout@v4 | ||
|
|
||
| - name: Set up JDK 17 | ||
| uses: actions/setup-java@v4 | ||
| with: | ||
| distribution: temurin | ||
| java-version: '17' | ||
|
|
||
| - name: Set up Android SDK | ||
| uses: android-actions/setup-android@v3 | ||
|
|
||
| - name: Make gradlew executable | ||
| run: chmod +x ./gradlew | ||
|
|
||
| - name: Run :espresso unit tests and coverage verification | ||
| run: ./gradlew :espresso:testDebugUnitTest :espresso:jacocoTestCoverageVerification --stacktrace | ||
|
|
||
| - name: Upload JaCoCo HTML report | ||
| if: always() | ||
| uses: actions/upload-artifact@v4 | ||
| with: | ||
| name: jacoco-report | ||
| path: espresso/build/reports/jacoco/jacocoTestReport | ||
| if-no-files-found: warn | ||
|
|
||
| - name: Upload unit test results | ||
| if: always() | ||
| uses: actions/upload-artifact@v4 | ||
| with: | ||
| name: unit-test-results | ||
| path: espresso/build/reports/tests/testDebugUnitTest | ||
| if-no-files-found: warn |
GitHub's built-in Automatic Dependency Submission (the 'submit-gradle' check) fails repo-wide because it runs './gradlew help' without the Android SDK. This workflow installs the SDK via setup-android before submitting the dependency graph. A repo admin should disable the built-in feature (Settings -> Code security -> Automatic dependency submission) so the failing check stops. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude Code PR ReviewPR: #13 • Head: 86f710d • Reviewers: stack:code-reviewer SummaryAdds an emulator-free JVM test suite (Robolectric + Mockito + an in-process stub HTTP server) and a JaCoCo line-coverage gate (floor 0.977) to the Review Table
FindingsNo Critical or High findings. All findings are Low / informational and do not block merge.
Independently verified (no issue)
Verdict: PASS |
…ndency submission The built-in Automatic Dependency Submission check (dynamic / submit-gradle) runs a newer Gradle (8+) than the repo wrapper (7.5). Plugin 1.1.0 relies on org.gradle.util.ConfigureUtil, removed in Gradle 8, so it fails evaluating scripts/publish-root.gradle with NoClassDefFoundError: org/gradle/util/ConfigureUtil. 1.3.0 is the first release that dropped that internal-API usage while still supporting Gradle 6.2+, so the normal 7.5 build is unaffected. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Raises the JaCoCo LINE gate 0.977 -> 1.00. The previously-uncovered remote-CSV fallback in MetadataHelper.deviceNameFromCSV (the storage.googleapis.com/.../supported_devices.csv fetch plus its catch(IOException)/return-null tail) is now exercised by real tests. No JaCoCo excludes, no @generated, no pragmas. The remote fetch was made reachable by minimal, behavior-preserving testability seams in MetadataHelper (localLookup / openLocalCsvReader / openRemoteCsvReader / remoteCsvUrl), letting MetadataHelperTest force a local miss and drive the fallback against a loopback StubHttpServer (success path) or an injected IOException (catch/return-null path). Production control flow is unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The
:espressolibrary module had zero JVM unit tests (only emulator-boundandroidTest) and no coverage gate in CI. This adds a real emulator-free suite + a JaCoCo gate.What
espresso/src/test/java/...covering all 9 production classes: Environment, Cache, ScreenshotOptions, Tile, CliWrapper (stub HTTP server), AppPercy, MetadataHelper, Metadata, GenericProvider. Uses Robolectric (forBuild/Resources/Bitmap/Base64— no emulator), Mockito, and ajava.netStubHttpServer.espresso/build.gradle: appliesjacoco, adds test deps,includeAndroidResources/returnDefaultValues, and ajacocoTestCoverageVerification(LINE floor 0.977) wired into the gate..github/workflows/test.yml(PR + push):setup-java@v4(JDK 17) +setup-android@v3(installs the SDK platform — no emulator), runs:espresso:testDebugUnitTest :espresso:jacocoTestCoverageVerification.Behavior-preserving testability seam
GenericProvider.captureBitmap()extracts theScreenshot.capture().getBitmap()call into aprotectedoverridable method — production behavior is identical (it still callsScreenshot.capture()), but a test subclass can supply a fakeBitmap. No public-API change.Coverage (local, Android SDK installed)
71 tests, 0 failures, line coverage 97.79% (266/272). 8 of 9 classes at 100%;
MetadataHelper38/44.Honest scope note
The 6 uncovered lines are the
deviceNameFromCSVremote-storage.googleapis.comfallback + itscatch(IOException)/return nulltail — unreachable on the JVM becauseparseBufferReadernever returns null (it returns a CSV match orBuild.MANUFACTURER + " " + Build.MODEL). Defensive/dead code that would need a live network call to exercise. No JaCoCo excludes used; the floor is the honest achieved value, documented inline.(Also noted, not fixed:
devices.csvis UTF-16 LE but read with the default charset — tests assert against the actual parsed output rather than altering prod.)🤖 Generated with Claude Code