diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/android/externalAndroidTarget/AndroidLibraryWithJavaIT.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/android/externalAndroidTarget/AndroidLibraryWithJavaIT.kt index 69931fb886228..c78ba09351754 100644 --- a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/android/externalAndroidTarget/AndroidLibraryWithJavaIT.kt +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/android/externalAndroidTarget/AndroidLibraryWithJavaIT.kt @@ -5,8 +5,6 @@ package org.jetbrains.kotlin.gradle.android.externalAndroidTarget -import com.android.build.api.dsl.KotlinMultiplatformAndroidLibraryTarget -import org.gradle.kotlin.dsl.kotlin import org.gradle.util.GradleVersion import org.jetbrains.kotlin.gradle.testbase.* import java.util.zip.ZipFile @@ -380,37 +378,6 @@ class AndroidLibraryWithJavaIT : KGPBaseTest() { } } - private fun externalAndroidLibraryProject( - gradleVersion: GradleVersion, - androidVersion: String, - jdkVersion: JdkVersions.ProvidedJdk, - namespace: String, - withJava: Boolean = false, - androidLibraryConfiguration: KotlinMultiplatformAndroidLibraryTarget.() -> Unit = {}, - configureProject: TestProject.() -> Unit = {}, - ): TestProject = project( - "empty", - gradleVersion = gradleVersion, - buildOptions = defaultBuildOptions.copy(androidVersion = androidVersion), - buildJdk = jdkVersion.location, - ) { - plugins { - kotlin("multiplatform") - id("com.android.kotlin.multiplatform.library") - } - buildScriptInjection { - kotlinMultiplatform.apply { - targets.withType(KotlinMultiplatformAndroidLibraryTarget::class.java).configureEach { target -> - target.compileSdk = 34 - target.namespace = namespace - if (withJava) target.withJava() - target.androidLibraryConfiguration() - } - } - } - configureProject() - } - private fun TestProject.assertAarContainsClass(aarPath: String, classPath: String) { val aarFile = projectPath.resolve(aarPath).toFile() check(aarFile.exists()) { "AAR file does not exist: $aarPath" } diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/android/externalAndroidTarget/AndroidTestReportsExternalAndroidTargetIT.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/android/externalAndroidTarget/AndroidTestReportsExternalAndroidTargetIT.kt new file mode 100644 index 0000000000000..07bb2dfe58f4d --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/android/externalAndroidTarget/AndroidTestReportsExternalAndroidTargetIT.kt @@ -0,0 +1,169 @@ +/* + * Copyright 2010-2026 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.gradle.android.externalAndroidTarget + +import com.android.build.api.dsl.KotlinMultiplatformAndroidDeviceTestCompilation +import com.android.build.api.dsl.KotlinMultiplatformAndroidLibraryTarget +import org.gradle.util.GradleVersion +import org.jetbrains.kotlin.gradle.testbase.* +import kotlin.test.assertEquals +import kotlin.test.assertTrue + +// Used AGP 9.0 as the minimal stable version supporting the external Android target plugin (com.android.kotlin.multiplatform.library). +@AndroidTestVersions(minVersion = TestVersions.AGP.AGP_90) +@AndroidGradlePluginTests +class AndroidTestReportsExternalAndroidTargetIT : KGPBaseTest() { + + // Uses `com.android.kotlin.multiplatform.library`, requires AGP new DSL. + override val defaultBuildOptions: BuildOptions + get() = super.defaultBuildOptions.copy(enableLegacyAgpDsl = false) + + @GradleAndroidTest + fun `test - host tests execute and generate reports`( + gradleVersion: GradleVersion, + androidVersion: String, + jdkVersion: JdkVersions.ProvidedJdk, + ) { + externalAndroidLibraryProject( + gradleVersion = gradleVersion, + androidVersion = androidVersion, + jdkVersion = jdkVersion, + namespace = "org.jetbrains.sample.hosttestreports", + androidLibraryConfiguration = { + withHostTest {} + }, + ) { + buildScriptInjection { + kotlinMultiplatform.apply { + sourceSets.getByName("commonTest").dependencies { + implementation(kotlin("test")) + } + } + } + + kotlinSourcesDir("commonTest").resolve("CommonTestBase.kt").apply { + parent.toFile().mkdirs() + toFile().writeText( + """ + package org.jetbrains.sample + + import kotlin.test.Test + import kotlin.test.assertTrue + + open class CommonTestBase { + @Test + fun testInCommon() { + assertTrue(true) + } + } + """.trimIndent() + ) + } + + kotlinSourcesDir("androidHostTest").resolve("HostTest.kt").apply { + parent.toFile().mkdirs() + toFile().writeText( + """ + package org.jetbrains.sample + + import kotlin.test.Test + import kotlin.test.assertEquals + + class HostTest : CommonTestBase() { + @Test + fun testInHost() { + assertEquals("host", "host") + } + } + """.trimIndent() + ) + } + + build(":testAndroidHostTest") { + assertTasksExecuted(":compileAndroidHostTest") + assertTasksExecuted(":testAndroidHostTest") + assertFileInProjectExists("build/reports/tests/testAndroidHostTest/index.html") + assertExecutedTestCases( + ":testAndroidHostTest", + "org.jetbrains.sample.CommonTestBase#testInCommon", + "org.jetbrains.sample.HostTest#testInCommon", + "org.jetbrains.sample.HostTest#testInHost", + ) + } + } + } + + @GradleAndroidTest + fun `test - device test wiring`( + gradleVersion: GradleVersion, + androidVersion: String, + jdkVersion: JdkVersions.ProvidedJdk, + ) { + externalAndroidLibraryProject( + gradleVersion = gradleVersion, + androidVersion = androidVersion, + jdkVersion = jdkVersion, + namespace = "org.jetbrains.sample.devicetestwiring", + androidLibraryConfiguration = { + withHostTest {} + withDeviceTest { + instrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + } + }, + ) { + // `androidDeviceTest` belongs to the `instrumentedTest` source set tree and therefore is not + // connected to `commonTest` by the default hierarchy template: the edge has to be declared. + buildScriptInjection { + kotlinMultiplatform.apply { + sourceSets.getByName("androidDeviceTest").dependsOn(sourceSets.getByName("commonTest")) + } + } + + kotlinSourcesDir("commonTest").resolve("CommonTestUtils.kt").apply { + parent.toFile().mkdirs() + toFile().writeText( + """ + package org.jetbrains.sample + + object CommonTestUtils { + fun expected(): String = "expected" + } + """.trimIndent() + ) + } + + kotlinSourcesDir("androidDeviceTest").resolve("DeviceTest.kt").apply { + parent.toFile().mkdirs() + toFile().writeText( + """ + package org.jetbrains.sample + + class DeviceTest { + fun value(): String = CommonTestUtils.expected() + } + """.trimIndent() + ) + } + + val configuration = buildScriptReturn { + val hasConnectedTask = project.tasks.names.contains("connectedAndroidDeviceTest") + + val target = kotlinMultiplatform.targets.withType(KotlinMultiplatformAndroidLibraryTarget::class.java).single() + val compilation = target.compilations.getByName("deviceTest") as KotlinMultiplatformAndroidDeviceTestCompilation + + hasConnectedTask to compilation.instrumentationRunner + }.buildAndReturn(":help") + + assertTrue(configuration.first, "Task connectedAndroidDeviceTest must be registered in task graph") + assertEquals("androidx.test.runner.AndroidJUnitRunner", configuration.second) + + // The declared `dependsOn` edge is honoured: device test sources see `commonTest` code. + build(":compileAndroidDeviceTest") { + assertTasksExecuted(":compileAndroidDeviceTest") + } + } + } +} diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/testbase/testProjectsBuildScriptInjectionDsl.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/testbase/testProjectsBuildScriptInjectionDsl.kt index 615d263d5909c..250c91410ed6c 100644 --- a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/testbase/testProjectsBuildScriptInjectionDsl.kt +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/testbase/testProjectsBuildScriptInjectionDsl.kt @@ -5,11 +5,44 @@ package org.jetbrains.kotlin.gradle.testbase +import com.android.build.api.dsl.KotlinMultiplatformAndroidLibraryTarget import org.gradle.api.publish.PublishingExtension import org.gradle.jvm.toolchain.JavaLanguageVersion +import org.gradle.kotlin.dsl.kotlin import org.gradle.util.GradleVersion import java.io.File +fun KGPBaseTest.externalAndroidLibraryProject( + gradleVersion: GradleVersion, + androidVersion: String, + jdkVersion: JdkVersions.ProvidedJdk, + namespace: String = "org.jetbrains.sample.androidlibrary", + withJava: Boolean = false, + androidLibraryConfiguration: KotlinMultiplatformAndroidLibraryTarget.() -> Unit = {}, + configureProject: TestProject.() -> Unit = {}, +): TestProject = project( + "empty", + gradleVersion = gradleVersion, + buildOptions = defaultBuildOptions.copy(androidVersion = androidVersion), + buildJdk = jdkVersion.location, +) { + plugins { + kotlin("multiplatform") + id("com.android.kotlin.multiplatform.library") + } + buildScriptInjection { + kotlinMultiplatform.apply { + targets.withType(KotlinMultiplatformAndroidLibraryTarget::class.java).configureEach { target -> + target.compileSdk = 34 + target.namespace = namespace + if (withJava) target.withJava() + target.androidLibraryConfiguration() + } + } + } + configureProject() +} + fun KGPBaseTest.kotlinAndroidLibraryProject( gradleVersion: GradleVersion, agpVersion: String,