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
28 changes: 28 additions & 0 deletions oss-licenses-plugin/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,22 @@ dependencies {
"e2eTestImplementation"(gradleTestKit())
}

// Pre-process the testapp into a clean directory using an allow-list.
// This excludes redundant build artifacts and IDE/Gradle internal folders.
// The Gradle wrapper is also excluded because E2E tests use GradleRunner.withGradleVersion().
val prepareTestApp by tasks.registering(Sync::class) {
from("testapp") {
include("app/src/**")
include("app/build.gradle.kts")
include("gradle/*.toml")
include("gradle/*.properties")
include("build.gradle.kts")
include("settings.gradle.kts")
include("gradle.properties")
}
into(layout.buildDirectory.dir("testapp-prepared"))
}

val e2eTestTask by tasks.registering(Test::class) {
description = "Runs end-to-end tests that build the full testapp against multiple AGP versions"
group = "verification"
Expand All @@ -189,6 +205,18 @@ val e2eTestTask by tasks.registering(Test::class) {

configureTestKitDefaults()

// Wire the prepared directory to inputs and system properties via Providers.
// This implicitly handles the task dependency on prepareTestApp.
val testAppDirProvider = prepareTestApp.map { it.destinationDir }
inputs.dir(testAppDirProvider)
.withPathSensitivity(PathSensitivity.RELATIVE)
.withPropertyName("testapp")

// Pass as a system property lazily to maintain configuration cache compatibility.
jvmArgumentProviders.add(CommandLineArgumentProvider {
listOf("-Dtestapp.dir=${testAppDirProvider.get().absolutePath}")
})

// Inject AGP/Gradle version pairs as system properties for each e2e subclass.
e2eTestVersions.forEach { (className, versions) ->
systemProperties["$className.agpVersion"] = versions.first
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,12 +44,6 @@ abstract class EndToEndTest {
private val AGP_VERSION_REGEX = Regex("""agp = ".*"""")
private val KOTLIN_VERSION_REGEX = Regex("""kotlin = ".*"""")

// Files to copy from the testapp source into the temp project directory
private val TESTAPP_ALLOW_LIST = listOf(
"app", "gradle", "build.gradle.kts", "settings.gradle.kts", "gradle.properties",
"gradlew", "gradlew.bat"
)

// AGP 9+ has built-in Kotlin support; AGP 8.x requires the standalone KGP with legacy config.
private val AGP_9_KOTLIN_BLOCK = """
kotlin {
Expand Down Expand Up @@ -78,16 +72,19 @@ abstract class EndToEndTest {
projectDir = tempDirectory.newFolder("testapp")

val currentDir = File(System.getProperty("user.dir")!!) // if this is missing then something is very wrong
val testAppSourceDir = File(currentDir, "testapp")
val testappDirPath = System.getProperty("testapp.dir")
requireNotNull(testappDirPath) { "testapp.dir system property is missing" }

val testAppSourceDir = File(testappDirPath)
require(testAppSourceDir.exists()) {
"Test app source not found at: ${testAppSourceDir.absolutePath}"
}

configureAndroidSdk(currentDir)
copyTestApp(testAppSourceDir)
testAppSourceDir.copyRecursively(projectDir, overwrite = true)

// Remove the Gradle daemon JVM file — the JAVA_HOME injection in createRunner() handles
// JVM selection more cleanly across all Gradle versions.
// Remove the Gradle daemon JVM file if present — the JAVA_HOME injection in createRunner()
// handles JVM selection more cleanly across all Gradle versions.
File(projectDir, "gradle/gradle-daemon-jvm.properties").delete()

patchVersions()
Expand All @@ -102,13 +99,6 @@ abstract class EndToEndTest {
File(projectDir, "local.properties").writeText("sdk.dir=${sdkDir.replace("\\", "\\\\")}\n")
}

private fun copyTestApp(sourceDir: File) {
TESTAPP_ALLOW_LIST
.map { sourceDir.resolve(it) }
.filter { it.exists() }
.forEach { it.copyRecursively(projectDir.resolve(it.name), overwrite = true) }
}

private fun patchVersions() {
val agpBundlesKgp = agpVersion.substringBefore('.').toIntOrNull()?.let { it >= 9 } ?: false

Expand Down
34 changes: 34 additions & 0 deletions oss-licenses-plugin/testapp/app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,40 @@ dependencies {
debugImplementation(libs.androidx.compose.ui.test.manifest)
}

// Log the actual resolved versions of the library and plugin.
tasks.register("logOssVersions") {
val runtimeConfig = configurations.getByName("releaseRuntimeClasspath")
val buildConfig = rootProject.buildscript.configurations.getByName("classpath")

// Use providers to resolve versions safely.
val libVersion = provider {
runtimeConfig.incoming.resolutionResult.allComponents
.map { it.id }
.filterIsInstance<org.gradle.api.artifacts.component.ModuleComponentIdentifier>()
.find { it.group == "com.google.android.gms" && it.module == "play-services-oss-licenses" }
?.version ?: "UNKNOWN"
}

val plugVersion = provider {
buildConfig.incoming.resolutionResult.allComponents
.map { it.id }
.filterIsInstance<org.gradle.api.artifacts.component.ModuleComponentIdentifier>()
.find { it.group == "com.google.android.gms" && it.module == "oss-licenses-plugin" }
?.version ?: "LOCAL"
}

doFirst {
println("------------------------------------------------------------")
println("OSS Licenses Library (Resolved): ${libVersion.get()}")
println("OSS Licenses Plugin (Resolved): ${plugVersion.get()}")
println("------------------------------------------------------------")
}
}

tasks.matching { it.name == "preBuild" || it.name == "test" }.configureEach {
dependsOn("logOssVersions")
}

abstract class GenerateVersionTask : DefaultTask() {
@get:org.gradle.api.tasks.InputFile
@get:org.gradle.api.tasks.Optional
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ package com.google.android.gms.oss.licenses.testapp

import android.content.Intent
import androidx.compose.ui.test.junit4.createEmptyComposeRule
import androidx.compose.ui.test.onNodeWithContentDescription
import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.test.performClick
import androidx.test.core.app.ActivityScenario
Expand Down Expand Up @@ -45,6 +46,27 @@ class OssLicensesV2Test {
}
}

@Test
fun testV2DoubleTapUpButton() {
// Reproduces Issue 397: Rapid double-tap on Up button leads to IllegalArgumentException
ActivityScenario.launch(OssLicensesMenuActivity::class.java).use {
// Navigate to detail
composeTestRule.onNodeWithText("Activity", ignoreCase = true).performClick()

// Find the "Up" button (usually the first IconButton in the TopAppBar or has "Back" content description)
// In Nav3 default TopAppBar, it might be the back button.
// Let's assume it has a content description "Back" or "Navigate up"
val backButton = composeTestRule.onNodeWithContentDescription("Back", ignoreCase = true)

// Rapidly double tap
backButton.performClick()
backButton.performClick()

// If it crashes, the test will fail with the exception.
composeTestRule.onNodeWithText("Open source licenses", ignoreCase = true).assertExists()
}
}

@Test
fun testV2DetailNavigation() {
ActivityScenario.launch(OssLicensesMenuActivity::class.java).use {
Expand Down
Loading