-
Notifications
You must be signed in to change notification settings - Fork 0
Add E2E integration test for Gradle plugin #160
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -54,7 +54,24 @@ mavenPublishing { | |
| } | ||
| } | ||
|
|
||
| // A separate configuration whose resolved jars are appended to the pluginUnderTestMetadata | ||
| // classpath. This makes GradleRunner.withPluginClasspath() inject them into the TestKit | ||
| // subprocess, which is necessary for compileOnly dependencies (like AGP) that the plugin | ||
| // needs at runtime but that java-gradle-plugin does not include from runtimeClasspath. | ||
| val testPluginClasspath: Configuration by configurations.creating { | ||
| isCanBeResolved = true | ||
| isCanBeConsumed = false | ||
| isVisible = false | ||
| } | ||
|
|
||
| tasks.pluginUnderTestMetadata { | ||
| pluginClasspath.from(testPluginClasspath) | ||
| } | ||
|
|
||
| dependencies { | ||
| // Inject AGP into the TestKit subprocess via pluginUnderTestMetadata so that the Featured | ||
| // plugin can access AndroidComponentsExtension when wireProguardToVariants() is called. | ||
| testPluginClasspath("com.android.tools.build:gradle:9.1.0") | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 1. Hardcoded agp in testpluginclasspath The build script adds an AGP dependency with a literal version string (9.1.0) instead of sourcing the version from the Gradle version catalog. This can lead to version drift and violates the requirement to declare dependency versions centrally. Agent Prompt
|
||
| testImplementation(gradleTestKit()) | ||
| testImplementation(libs.kotlin.testJunit) | ||
| testImplementation(libs.r8) | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,29 @@ | ||||||
| plugins { | ||||||
| id("com.android.application") version "9.1.0" | ||||||
|
||||||
| id("com.android.application") version "9.1.0" | |
| id("com.android.application") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
2. Fixture uses plugin version literal 📘 Rule violation ⚙ Maintainability
The fixture project applies com.android.application with a hardcoded version (9.1.0) in `plugins
{}`. This violates the requirement to avoid literal version strings in Gradle build scripts.
Agent Prompt
## Issue description
The TestKit fixture `build.gradle.kts` applies `com.android.application` with a hardcoded plugin version string (`9.1.0`).
## Issue Context
This fixture is used for TestKit E2E tests and should not hardcode versions in the build script. If AGP is injected via the TestKit classpath, the fixture can typically apply the plugin without specifying a version.
## Fix Focus Areas
- featured-gradle-plugin/src/test/fixtures/android-project/build.gradle.kts[1-4]
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
4. Agp double-resolution risk 🐞 Bug ☼ Reliability
AGP is appended to the TestKit plugin classpath via pluginUnderTestMetadata, but the fixture still applies com.android.application with an explicit version. This can cause Gradle plugin resolution to fail because the plugin is already on the classpath while also being requested with a version.
Agent Prompt
## Issue description
AGP is injected into the TestKit subprocess classpath via `pluginUnderTestMetadata`, but the fixture still requests the Android plugin with an explicit version. This can break Gradle’s plugin resolution (plugin already on classpath + versioned request).
## Issue Context
The goal of `testPluginClasspath` is to make AGP available in the TestKit subprocess without relying on external plugin resolution.
## Fix Focus Areas
- featured-gradle-plugin/build.gradle.kts[57-75]
- featured-gradle-plugin/src/test/fixtures/android-project/build.gradle.kts[1-4]
- featured-gradle-plugin/src/test/fixtures/android-project/settings.gradle.kts[1-15]
## What to change
- Prefer one mechanism:
1) **Injected-classpath approach (recommended here):**
- Change fixture to `plugins { id("com.android.application"); id("dev.androidbroadcast.featured") }` (no version for AGP).
- Optionally simplify/remove `pluginManagement.repositories` if it’s no longer needed.
OR
2) **Repository resolution approach:**
- Remove AGP from `testPluginClasspath` injection and keep the fixture’s versioned plugin request.
Ensure the chosen approach is consistent with the comments in the fixture/settings files.
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,31 @@ | ||
| // AGP and the Featured plugin are injected via GradleRunner.withPluginClasspath(). | ||
| // No pluginManagement repositories needed — the plugins are resolved from the injected classpath. | ||
| pluginManagement { | ||
| repositories { | ||
| google { | ||
| mavenContent { | ||
| includeGroupAndSubgroups("androidx") | ||
| includeGroupAndSubgroups("com.android") | ||
| includeGroupAndSubgroups("com.google") | ||
| } | ||
| } | ||
| mavenCentral() | ||
| gradlePluginPortal() | ||
| } | ||
| } | ||
|
|
||
| dependencyResolutionManagement { | ||
| @Suppress("UnstableApiUsage") | ||
| repositories { | ||
| google { | ||
| mavenContent { | ||
| includeGroupAndSubgroups("androidx") | ||
| includeGroupAndSubgroups("com.android") | ||
| includeGroupAndSubgroups("com.google") | ||
| } | ||
| } | ||
| mavenCentral() | ||
| } | ||
| } | ||
|
|
||
| rootProject.name = "android-project" |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| <manifest xmlns:android="http://schemas.android.com/apk/res/android" /> |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,198 @@ | ||||||||||||||||||||||||||||||||||||||||||||||
| package dev.androidbroadcast.featured.gradle | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| import org.gradle.testkit.runner.GradleRunner | ||||||||||||||||||||||||||||||||||||||||||||||
| import org.gradle.testkit.runner.TaskOutcome | ||||||||||||||||||||||||||||||||||||||||||||||
| import org.junit.Assume.assumeTrue | ||||||||||||||||||||||||||||||||||||||||||||||
| import org.junit.Before | ||||||||||||||||||||||||||||||||||||||||||||||
| import org.junit.Rule | ||||||||||||||||||||||||||||||||||||||||||||||
| import org.junit.Test | ||||||||||||||||||||||||||||||||||||||||||||||
| import org.junit.rules.TemporaryFolder | ||||||||||||||||||||||||||||||||||||||||||||||
| import java.io.File | ||||||||||||||||||||||||||||||||||||||||||||||
| import kotlin.test.assertEquals | ||||||||||||||||||||||||||||||||||||||||||||||
| import kotlin.test.assertTrue | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||||||||||||||
| * End-to-end integration test that verifies the Featured Gradle plugin: | ||||||||||||||||||||||||||||||||||||||||||||||
| * 1. Generates a ProGuard file at `build/featured/proguard-featured.pro` with correct | ||||||||||||||||||||||||||||||||||||||||||||||
| * `-assumevalues` rules for declared local flags. | ||||||||||||||||||||||||||||||||||||||||||||||
| * 2. Auto-wires that file into the AGP release variant so the `generateProguardRules` | ||||||||||||||||||||||||||||||||||||||||||||||
| * task participates in `assembleRelease`. | ||||||||||||||||||||||||||||||||||||||||||||||
| * | ||||||||||||||||||||||||||||||||||||||||||||||
| * The test uses a minimal Android application fixture copied from | ||||||||||||||||||||||||||||||||||||||||||||||
| * `src/test/fixtures/android-project/`. It runs via Gradle TestKit with the plugin | ||||||||||||||||||||||||||||||||||||||||||||||
| * classpath injected automatically by the `java-gradle-plugin` metadata. | ||||||||||||||||||||||||||||||||||||||||||||||
| * | ||||||||||||||||||||||||||||||||||||||||||||||
| * Skipped when `ANDROID_HOME` / `ANDROID_SDK_ROOT` is not set — the test requires a | ||||||||||||||||||||||||||||||||||||||||||||||
| * real Android SDK to compile the AGP-driven release build. | ||||||||||||||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||||||||||||||
| class FeaturedPluginIntegrationTest { | ||||||||||||||||||||||||||||||||||||||||||||||
| @get:Rule | ||||||||||||||||||||||||||||||||||||||||||||||
| val tempFolder = TemporaryFolder() | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| private lateinit var projectDir: File | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| @Before | ||||||||||||||||||||||||||||||||||||||||||||||
| fun setUp() { | ||||||||||||||||||||||||||||||||||||||||||||||
| val sdkDir = androidSdkDir() | ||||||||||||||||||||||||||||||||||||||||||||||
| assumeTrue( | ||||||||||||||||||||||||||||||||||||||||||||||
| "ANDROID_HOME or ANDROID_SDK_ROOT must be set to run integration tests", | ||||||||||||||||||||||||||||||||||||||||||||||
| sdkDir != null, | ||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| projectDir = tempFolder.newFolder("android-project") | ||||||||||||||||||||||||||||||||||||||||||||||
| copyFixture(projectDir) | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| // Write local.properties with sdk.dir so AGP can locate the Android SDK. | ||||||||||||||||||||||||||||||||||||||||||||||
| projectDir.resolve("local.properties").writeText("sdk.dir=${sdkDir!!.absolutePath}\n") | ||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| // ── Tests ───────────────────────────────────────────────────────────────── | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| @Test | ||||||||||||||||||||||||||||||||||||||||||||||
| fun `generateProguardRules task produces correct assumevalues rule for boolean local flag`() { | ||||||||||||||||||||||||||||||||||||||||||||||
| val result = | ||||||||||||||||||||||||||||||||||||||||||||||
| gradleRunner(projectDir) | ||||||||||||||||||||||||||||||||||||||||||||||
| .withArguments("generateProguardRules", "--stacktrace") | ||||||||||||||||||||||||||||||||||||||||||||||
| .build() | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| val outcome = result.task(":generateProguardRules")?.outcome | ||||||||||||||||||||||||||||||||||||||||||||||
| assertEquals( | ||||||||||||||||||||||||||||||||||||||||||||||
| TaskOutcome.SUCCESS, | ||||||||||||||||||||||||||||||||||||||||||||||
| outcome, | ||||||||||||||||||||||||||||||||||||||||||||||
| "Expected :generateProguardRules to succeed, got $outcome\n${result.output}", | ||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| val proFile = projectDir.resolve("build/featured/proguard-featured.pro") | ||||||||||||||||||||||||||||||||||||||||||||||
| assertTrue(proFile.exists(), "Expected proguard-featured.pro to be generated at ${proFile.path}") | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| val content = proFile.readText() | ||||||||||||||||||||||||||||||||||||||||||||||
| assertContainsAssumevaluesBlock(content) | ||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| @Test | ||||||||||||||||||||||||||||||||||||||||||||||
| fun `assembleRelease wires proguard rules and completes successfully`() { | ||||||||||||||||||||||||||||||||||||||||||||||
| val result = | ||||||||||||||||||||||||||||||||||||||||||||||
| gradleRunner(projectDir) | ||||||||||||||||||||||||||||||||||||||||||||||
| .withArguments("assembleRelease", "--stacktrace") | ||||||||||||||||||||||||||||||||||||||||||||||
| .build() | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| // generateProguardRules must have run as part of the release build. | ||||||||||||||||||||||||||||||||||||||||||||||
| val proguardOutcome = result.task(":generateProguardRules")?.outcome | ||||||||||||||||||||||||||||||||||||||||||||||
| assertTrue( | ||||||||||||||||||||||||||||||||||||||||||||||
| proguardOutcome == TaskOutcome.SUCCESS || proguardOutcome == TaskOutcome.UP_TO_DATE, | ||||||||||||||||||||||||||||||||||||||||||||||
| "Expected :generateProguardRules to participate in assembleRelease, got $proguardOutcome\n${result.output}", | ||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+73
to
+83
|
||||||||||||||||||||||||||||||||||||||||||||||
| fun `assembleRelease wires proguard rules and completes successfully`() { | |
| val result = | |
| gradleRunner(projectDir) | |
| .withArguments("assembleRelease", "--stacktrace") | |
| .build() | |
| // generateProguardRules must have run as part of the release build. | |
| val proguardOutcome = result.task(":generateProguardRules")?.outcome | |
| assertTrue( | |
| proguardOutcome == TaskOutcome.SUCCESS || proguardOutcome == TaskOutcome.UP_TO_DATE, | |
| "Expected :generateProguardRules to participate in assembleRelease, got $proguardOutcome\n${result.output}", | |
| fun `generateProguardRules and assembleRelease complete successfully`() { | |
| val result = | |
| gradleRunner(projectDir) | |
| .withArguments("generateProguardRules", "assembleRelease", "--stacktrace") | |
| .build() | |
| // Explicitly invoke generateProguardRules before assembleRelease for the current plugin behavior. | |
| val proguardOutcome = result.task(":generateProguardRules")?.outcome | |
| assertTrue( | |
| proguardOutcome == TaskOutcome.SUCCESS || proguardOutcome == TaskOutcome.UP_TO_DATE, | |
| "Expected :generateProguardRules to succeed before assembleRelease, got $proguardOutcome\n${result.output}", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
3. Assemblerelease not wired 🐞 Bug ≡ Correctness
FeaturedPluginIntegrationTest asserts :generateProguardRules runs during assembleRelease, but
the Featured plugin currently only registers the task and does not wire it into AGP variants, and
the fixture does not include the generated .pro file in proguardFiles. This will cause the test
to fail because result.task(":generateProguardRules") will be null or not executed during
assembleRelease.
Agent Prompt
## Issue description
The new E2E test expects `assembleRelease` to trigger `generateProguardRules`, but the plugin/fixture currently do not wire the generated ProGuard file into AGP’s release variant. This makes the test fail because `:generateProguardRules` will not be in the task graph for `assembleRelease`.
## Issue Context
- The plugin registers `generateProguardRules` and writes `build/featured/proguard-featured.pro`.
- The fixture’s `release` build type does not reference this file.
- There is no Android Components/Variant API integration in the plugin to add this file automatically.
## Fix Focus Areas
- featured-gradle-plugin/src/main/kotlin/dev/androidbroadcast/featured/gradle/FeaturedPlugin.kt[37-58]
- featured-gradle-plugin/src/main/kotlin/dev/androidbroadcast/featured/gradle/FeaturedPlugin.kt[99-111]
- featured-gradle-plugin/src/test/fixtures/android-project/build.gradle.kts[15-22]
- featured-gradle-plugin/src/test/kotlin/dev/androidbroadcast/featured/gradle/FeaturedPluginIntegrationTest.kt[72-97]
## What to change
1. Implement actual AGP integration in the Featured plugin so that for release variants it:
- adds `build/featured/proguard-featured.pro` to the variant’s ProGuard/R8 configuration, and
- ensures the relevant R8/minify task depends on `generateProguardRules` (or otherwise consumes its output via the Variant API).
2. Keep the fixture minimal (it should not manually add the file if the plugin is supposed to auto-wire it).
3. If auto-wiring is not intended yet, adjust the test and fixture accordingly (but then remove the “auto-wires” claim and the `assembleRelease` participation assertion).
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
Copilot
AI
Apr 10, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The KDoc for fixtureDir() says the module directory is either provided via user.dir or derived relative to this class file's location, but the implementation only uses System.getProperty("user.dir") and has no fallback. Either implement the described fallback (e.g., resolve via a classpath resource) or update the KDoc to match the actual behavior to avoid confusion and brittle test setup.
Copilot
AI
Apr 10, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The gradleRunner() KDoc claims “no extra classpath injection is needed”, but this PR adds testPluginClasspath to inject AGP into pluginUnderTestMetadata. This comment is now misleading (and the classloader explanation is likely incorrect in TestKit). Please update the KDoc to reflect the actual mechanism being used so future changes don’t accidentally remove required classpath setup.
| * AGP is declared as `compileOnly` in this module — the applying build provides it at runtime. | |
| * In the TestKit subprocess, AGP is loaded by the build's own classloader when `com.android.application` | |
| * is applied from the fixture's `plugins {}` block (resolved from Google Maven). The Featured plugin | |
| * code that references [com.android.build.api.variant.AndroidComponentsExtension] is loaded in the | |
| * same classloader context, so no extra classpath injection is needed. | |
| * The runner uses [GradleRunner.withPluginClasspath] so TestKit loads the plugin-under-test from | |
| * the generated `pluginUnderTestMetadata` classpath. | |
| * | |
| * This test setup also relies on additional classpath wiring for that metadata so AGP is available | |
| * to the plugin under test, even though AGP is declared as `compileOnly` in this module. Keep that | |
| * wiring in place when changing the test configuration; removing it can break access to | |
| * [com.android.build.api.variant.AndroidComponentsExtension] during the TestKit build. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
AGP version is hard-coded as
9.1.0in the newtestPluginClasspathdependency. Since the repo already tracks the AGP version in the version catalog (libs.versions.agp), this should be sourced from there to avoid accidental drift (tests may break if the plugin’s compileOnly AGP version changes but this stays pinned).