Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 52 additions & 3 deletions .github/workflows/oss-licenses.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@ jobs:
dependency-graph: generate-and-submit

- name: Build and run unit tests
# Integration tests live in their own source set + task (integrationTestTask),
# so the default 'test' task only runs the unit suite.
# Integration tests (integrationTestTask) and e2e tests (e2eTestTask) live in
# their own source sets; the default 'test' task only runs the unit suite.
run: ./gradlew assemble test
working-directory: ./oss-licenses-plugin

Expand All @@ -49,7 +49,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
fail-fast: false
# Keep this list in sync with the integrationVersions map in
# Keep this list in sync with the integrationOnlyVersions + e2eVersions maps in
# oss-licenses-plugin/build.gradle.kts.
matrix:
agp-version-key: [AGP74, AGP87, AGP812, AGP_STABLE, AGP_ALPHA]
Expand Down Expand Up @@ -83,11 +83,60 @@ jobs:
run: ./gradlew integrationTestTask --tests "com.google.android.gms.oss.licenses.plugin.IntegrationTest_${{ matrix.agp-version-key }}" -x publishAllPublicationsToLocalRepository
working-directory: ./oss-licenses-plugin

# Run end-to-end tests: build the full testapp against the AGP/Gradle version matrix.
# Uses the locally-published plugin from oss-licenses-build via -PusePublishedPluginFrom.
oss-licenses-e2e:
needs: oss-licenses-build
runs-on: ubuntu-latest
strategy:
fail-fast: false
# Keep this list in sync with the e2eVersions map in
# oss-licenses-plugin/build.gradle.kts.
matrix:
agp-version-key: [AGP812, AGP_STABLE, AGP_ALPHA]
env:
ANDROID_USER_HOME: /home/runner/.android
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # ratchet:actions/checkout@v6.0.2

- name: Download local repo artifact
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # ratchet:actions/download-artifact@v8.0.1
with:
name: oss-licenses-local-repo
path: oss-licenses-plugin/build/repo/

- name: Set up JDK 17
uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # ratchet:actions/setup-java@v5.2.0
with:
java-version: '17'
distribution: 'temurin'

- name: Set up JDK 21
uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # ratchet:actions/setup-java@v5.2.0
with:
java-version: '21'
distribution: 'temurin'

- name: Setup Gradle
uses: gradle/actions/setup-gradle@50e97c2cd7a37755bbfafc9c5b7cafaece252f6e # ratchet:gradle/actions/setup-gradle@v6.1.0

# setup-gradle's cache-restore step runs before any step creates $ANDROID_USER_HOME,
# and it fails if the directory it's asked to restore into doesn't exist. Pre-creating
# it here keeps the cache path stable across runs.
- name: Pre-create Android SDK cache directory
run: mkdir -p "$ANDROID_USER_HOME/cache"

- name: Build testapp (verify ${{ matrix.agp-version-key }})
# Skip the local-repo publish here because we want to use the pre-built plugin from the artifact.
run: ./gradlew e2eTestTask --tests "com.google.android.gms.oss.licenses.plugin.EndToEndTest_${{ matrix.agp-version-key }}" -x publishAllPublicationsToLocalRepository
working-directory: oss-licenses-plugin

# Aggregate status
oss-licenses-success:
needs:
- oss-licenses-build
- oss-licenses-integration-test
- oss-licenses-e2e
if: always()
runs-on: ubuntu-latest
steps:
Expand Down
22 changes: 16 additions & 6 deletions oss-licenses-plugin/GEMINI.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ This document provides essential information for AI agents and developers workin

## Test Architecture

The project uses a two-tier testing strategy to ensure both internal logic and integration across the Android Gradle Plugin (AGP) and Gradle version matrix.
The project uses a three-tier testing strategy: fast unit tests for task logic, a GradleTestKit integration matrix for plugin-on-Gradle behavior, and heavy end-to-end tests that build the full testapp against the AGP/Gradle matrix.

### 1. Unit Tests (`src/test/`)
These tests verify the logic of individual tasks and utility classes.
Expand All @@ -20,9 +20,18 @@ These tests verify the plugin's integration with the Gradle lifecycle and its be
* **File:** `IntegrationTest.kt`
* **Mechanism:** Uses `GradleTestKit` (`GradleRunner`) to execute the plugin against a set of static test projects.
* **Focus:** Task wiring, Configuration Cache compatibility, and relocatability.
* **Matrix:** Defined in `build.gradle.kts` (`integrationVersions`).
* **Matrix:** Defined in `build.gradle.kts` as `integrationOnlyVersions + e2eVersions` (integration covers the full backward-compat range).
* **Execution:** `./gradlew integrationTestTask` (also runs as part of `check`).

### 3. End-to-End Tests (`src/e2eTest/`)
These tests build the full standalone testapp under `testapp/` against multiple AGP/Gradle versions, exercising the plugin from the outside just like a real downstream project.

* **File:** `EndToEndTest.kt`
* **Mechanism:** Uses `GradleTestKit` to run `./gradlew build` inside a copy of `testapp/` whose `libs.versions.toml` has been patched to the matrix-selected AGP (and, on AGP 8.x, the Kotlin block in `app/build.gradle.kts` is rewritten to the legacy KGP form).
* **Focus:** Real consumer build — verifies the testapp compiles, resources shrink, and the plugin integrates cleanly.
* **Matrix:** Defined in `build.gradle.kts` as `e2eVersions` (modern AGP only — the e2e path is heavy).
* **Execution:** `./gradlew e2eTestTask` (also runs as part of `check`).

---

## Testing Infrastructure & Matrix
Expand All @@ -31,9 +40,9 @@ The complexity of testing across multiple AGP/Gradle versions is managed through

### Centralized Version Matrix
The `build.gradle.kts` file is the **single source of truth** for all versions.
* It defines a map (`integrationVersions`) of version pairs.
* It configures the manually declared test subclasses by injecting these version values as **system properties** (e.g., `IntegrationTest_AGP74.agpVersion`).
* To add a new version to the matrix: Add the entry to the map in `build.gradle.kts` and create the corresponding empty subclass in `IntegrationTest.kt`.
* It defines two maps of version pairs: `e2eVersions` (modern AGPs exercised by both integration and e2e) and `integrationOnlyVersions` (older AGPs kept for backward-compat coverage, integration-only).
* It configures the manually declared test subclasses by injecting these version values as **system properties** (e.g., `IntegrationTest_AGP74.agpVersion`, `EndToEndTest_AGP_STABLE.gradleVersion`).
* To add a new version: add the entry to `e2eVersions` (if it should run e2e) or `integrationOnlyVersions` (integration-only), then create the matching empty subclass in `IntegrationTest.kt` and/or `EndToEndTest.kt`. Keep the key in sync with the `agp-version-key` matrices in `.github/workflows/oss-licenses.yml`.

### Test Isolation
To allow safe parallel execution, each test subclass uses a dedicated `TestKit` directory (set via `.withTestKitDir()`). This prevents different AGP versions from clobbering each other's Gradle User Home caches.
Expand All @@ -47,7 +56,8 @@ To ensure tests run consistently regardless of the host environment, the build s

| Task | Command | Description |
| :--- | :--- | :--- |
| **Full Check** | `./gradlew check` | Runs unit tests and the full integration matrix. |
| **Full Check** | `./gradlew check` | Runs unit, integration, and e2e matrices. |
| **Unit only** | `./gradlew test` | Runs internal plugin unit tests only. |
| **Integration only** | `./gradlew integrationTestTask` | Runs the GradleTestKit integration matrix. |
| **E2E only** | `./gradlew e2eTestTask` | Runs the testapp build e2e matrix. Requires `ANDROID_HOME` or a `local.properties` with `sdk.dir=`. |
| **Publish** | `./gradlew publishAllPublicationsToLocalRepository` | Publishes the plugin to the internal `build/repo`. |
85 changes: 62 additions & 23 deletions oss-licenses-plugin/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -71,18 +71,23 @@ dependencies {
// AGP/Gradle version matrix — single source of truth for all GradleTestKit tests.
// Each entry maps a test subclass name to its (AGP, Gradle) version pair.
// The versions are injected as system properties so the test files contain no hardcoded versions.
// Keep the keys in sync with the agp-version-key matrix in
// Keep the keys in sync with the agp-version-key matrices in
// .github/workflows/oss-licenses.yml.
val integrationVersions = mapOf(
"AGP74" to ("7.4.2" to "7.5.1"), // oldest supported
"AGP87" to ("8.7.3" to "8.9"), // mainstream
"AGP812" to ("8.12.2" to "8.14.1"), // latest stable 8.x
"AGP_STABLE" to ("9.0.1" to "9.1.0"), // latest stable 9.x
"AGP_ALPHA" to ("9.2.0-alpha02" to "9.4.0"), // latest alpha
// E2E versions are a subset of the integration versions. Integration tests extend the E2E set
// with older AGP versions to ensure broad backward compatibility.
val e2eVersions = mapOf(
"AGP812" to ("8.12.2" to "8.14.1"), // latest stable 8.x
"AGP_STABLE" to ("9.1.1" to "9.4.1"), // latest stable 9.x
"AGP_ALPHA" to ("9.3.0-alpha01" to "9.5.0-rc-3"), // latest alpha
)
val integrationOnlyVersions = mapOf(
"AGP74" to ("7.4.2" to "7.5.1"), // oldest supported
"AGP87" to ("8.7.3" to "8.9"), // mainstream mid-range
)

// Build the full maps with class-name prefixes
val integrationTestVersions = integrationVersions.mapKeys { "IntegrationTest_${it.key}" }
val e2eTestVersions = e2eVersions.mapKeys { "EndToEndTest_${it.key}" }
val integrationTestVersions = (e2eVersions + integrationOnlyVersions).mapKeys { "IntegrationTest_${it.key}" }

// Separate source set for GradleTestKit integration tests that run the plugin
// against the AGP/Gradle matrix. Keeps the default 'test' task fast.
Expand Down Expand Up @@ -112,15 +117,11 @@ tasks.withType<Test>().configureEach {
}
}

val integrationTestTask by tasks.registering(Test::class) {
description = "Runs GradleTestKit integration tests against the AGP/Gradle version matrix"
group = "verification"
testClassesDirs = integrationTest.output.classesDirs
classpath = integrationTest.runtimeClasspath

// Common TestKit setup: both integration and e2e tasks spawn GradleRunner instances
// that need the locally-published plugin, a Java 21 toolchain path for newer AGP, and
// a repo_path pointing at the local publication directory.
fun Test.configureTestKitDefaults() {
val localRepo = repo
// Prepare the path to the Java 21 JVM used by the main build to inject into the
// integration test's environment. Required for some AGP versions (9.0+)
val javaToolchains = project.extensions.getByType<JavaToolchainService>()
val java21Home = javaToolchains.launcherFor {
languageVersion.set(JavaLanguageVersion.of(21))
Expand All @@ -139,25 +140,63 @@ val integrationTestTask by tasks.registering(Test::class) {
).withPathSensitivity(PathSensitivity.RELATIVE).withPropertyName("repo")

val localVersion = project.version.toString()
systemProperties["plugin_version"] = localVersion // value used by IntegrationTest.kt
// Point TestKit to a directory inside the host Gradle User Home so it can be cached by CI (setup-gradle)
systemProperties["plugin_version"] = localVersion
// Point TestKit to a directory inside the host Gradle User Home so it can be cached by CI (setup-gradle).
systemProperties["testkit_path"] = File(System.getProperty("user.home"), ".gradle/testkit").absolutePath
doFirst {
// Resolved inside doFirst so contributors without JDK 21 can still run ./gradlew help, tasks, etc.
// — the toolchain is only required when a Test task actually executes.
systemProperties["java21_home"] = java21Home.get() // value used by IntegrationTest.kt
// Inside doFirst to make sure that absolute path is not considered to be input to the task
systemProperties["repo_path"] = localRepo.get().asFile.absolutePath // value used by IntegrationTest.kt
systemProperties["java21_home"] = java21Home.get()
// Inside doFirst to keep absolute paths out of the task input fingerprint.
systemProperties["repo_path"] = localRepo.get().asFile.absolutePath
}
}

// Inject AGP/Gradle version pairs as system properties for each test subclass
val integrationTestTask by tasks.registering(Test::class) {
description = "Runs GradleTestKit integration tests against the AGP/Gradle version matrix"
group = "verification"
testClassesDirs = integrationTest.output.classesDirs
classpath = integrationTest.runtimeClasspath

configureTestKitDefaults()

// Inject AGP/Gradle version pairs as system properties for each integration test subclass.
integrationTestVersions.forEach { (className, versions) ->
systemProperties["$className.agpVersion"] = versions.first
systemProperties["$className.gradleVersion"] = versions.second
}
}

tasks.named("check") { dependsOn(integrationTestTask) }
// Separate source set for heavy E2E tests that build the full testapp against multiple AGP versions.
// Lives in src/e2eTest/kotlin/ — fully independent from the unit/integration test source set.
val e2eTest by sourceSets.creating {
compileClasspath += sourceSets.main.get().output
runtimeClasspath += sourceSets.main.get().output
}

configurations[e2eTest.implementationConfigurationName].extendsFrom(configurations.testImplementation.get())
configurations[e2eTest.runtimeOnlyConfigurationName].extendsFrom(configurations.testRuntimeOnly.get())

dependencies {
"e2eTestImplementation"(gradleTestKit())
}

val e2eTestTask by tasks.registering(Test::class) {
description = "Runs end-to-end tests that build the full testapp against multiple AGP versions"
group = "verification"
testClassesDirs = e2eTest.output.classesDirs
classpath = e2eTest.runtimeClasspath

configureTestKitDefaults()

// Inject AGP/Gradle version pairs as system properties for each e2e subclass.
e2eTestVersions.forEach { (className, versions) ->
systemProperties["$className.agpVersion"] = versions.first
systemProperties["$className.gradleVersion"] = versions.second
}
}

tasks.named("check") { dependsOn(integrationTestTask, e2eTestTask) }

publishing {
repositories {
Expand Down
Loading
Loading