[Gradle] Split & migrate AbstractKotlinAndroidGradleTests to the new dsl

#KT-45748 In Progress
This commit is contained in:
Alexander Likhachev
2022-10-19 12:12:59 +02:00
committed by Space Team
parent 8bbf98f24c
commit fc088bb84a
11 changed files with 1435 additions and 1141 deletions
@@ -450,6 +450,208 @@ class Kapt3AndroidIT : Kapt3BaseIT() {
}
}
@DisplayName("kapt uses AGP annotation processor option providers as nested inputs")
@GradleAndroidTest
fun testKaptUsingApOptionProvidersAsNestedInputOutput(
gradleVersion: GradleVersion,
agpVersion: String,
jdkVersion: JdkVersions.ProvidedJdk,
) {
project(
"AndroidProject",
gradleVersion,
buildOptions = defaultBuildOptions.copy(androidVersion = agpVersion, kaptOptions = null),
buildJdk = jdkVersion.location
) {
subProject("Android").buildGradle.appendText(
//language=Gradle
"""
apply plugin: 'kotlin-kapt'
class MyNested implements org.gradle.process.CommandLineArgumentProvider {
private final File argsFile
MyNested(File argsFile) {
this.argsFile = argsFile
}
@InputFile
File inputFile = null
@Override
Iterable<String> asArguments() {
// Read the arguments from a file, because changing them in a build script is treated as an
// implementation change by Gradle:
return [argsFile.text]
}
}
def nested = new MyNested(rootProject.file("args.txt"))
nested.inputFile = file("${'$'}projectDir/in.txt")
android.applicationVariants.all {
it.javaCompileOptions.annotationProcessorOptions.compilerArgumentProviders.add(nested)
}
""".trimIndent()
)
val inFile = subProject("Android").projectPath.resolve("in.txt")
inFile.writeText("1234")
val argsFile = projectPath.resolve("args.txt")
argsFile.writeText("1234")
val kaptTasks = listOf(":Android:kaptFlavor1DebugKotlin")
val javacTasks = listOf(":Android:compileFlavor1DebugJavaWithJavac")
val buildTasks = (kaptTasks + javacTasks).toTypedArray()
build(*buildTasks) {
assertTasksExecuted(kaptTasks + javacTasks)
}
inFile.appendText("5678")
build(*buildTasks) {
assertTasksExecuted(kaptTasks)
assertTasksUpToDate(javacTasks)
}
// Changing only the annotation provider arguments should not trigger the tasks to run, as the arguments may be outputs,
// internals or neither:
argsFile.appendText("5678")
build(*buildTasks) {
assertTasksUpToDate(javacTasks + kaptTasks)
}
}
}
@DisplayName("agp annotation processor nested arguments are not evaluated at configuration time")
@GradleAndroidTest
fun testAgpNestedArgsNotEvaluatedDuringConfiguration(
gradleVersion: GradleVersion,
agpVersion: String,
jdkVersion: JdkVersions.ProvidedJdk,
) {
project(
"AndroidProject",
gradleVersion,
buildOptions = defaultBuildOptions.copy(androidVersion = agpVersion),
buildJdk = jdkVersion.location
) {
subProject("Android").buildGradle.appendText(
//language=Gradle
"""
apply plugin: 'kotlin-kapt'
class MyNested implements org.gradle.process.CommandLineArgumentProvider {
@Override
Iterable<String> asArguments() {
throw new RuntimeException("This should not be invoked during configuration.")
}
}
def nested = new MyNested()
android.applicationVariants.all {
it.javaCompileOptions.annotationProcessorOptions.compilerArgumentProviders.add(nested)
}
""".trimIndent()
)
build(":Android:kaptFlavor1DebugKotlin", "--dry-run")
build(
":Android:kaptFlavor1DebugKotlin", "--dry-run",
buildOptions = buildOptions.copy(kaptOptions = BuildOptions.KaptOptions(verbose = false))
)
}
}
@DisplayName("incremental compilation works with dagger")
@GradleAndroidTest
fun testAndroidDaggerIC(
gradleVersion: GradleVersion,
agpVersion: String,
jdkVersion: JdkVersions.ProvidedJdk,
) {
project(
"AndroidDaggerProject",
gradleVersion,
buildOptions = defaultBuildOptions.copy(androidVersion = agpVersion),
buildJdk = jdkVersion.location
) {
build("assembleDebug")
val androidModuleKt = subProject("app").javaSourcesDir().resolve("com/example/dagger/kotlin/AndroidModule.kt")
androidModuleKt.modify {
it.replace(
"fun provideApplicationContext(): Context {",
"fun provideApplicationContext(): Context? {"
)
}
build(":app:assembleDebug", buildOptions = buildOptions.copy(logLevel = LogLevel.DEBUG)) {
assertTasksExecuted(
":app:kaptGenerateStubsDebugKotlin",
":app:kaptDebugKotlin",
":app:compileDebugKotlin",
":app:compileDebugJavaWithJavac"
)
// Output is combined with previous build, but we are only interested in the compilation
// from second build to avoid false positive test failure
val filteredOutput = output
.lineSequence()
.filter { it.contains("[KOTLIN] compile iteration:") }
.drop(1)
.joinToString(separator = "/n")
assertCompiledKotlinSources(listOf(androidModuleKt).relativizeTo(projectPath), output = filteredOutput)
}
}
}
@DisplayName("incremental compilation with android and kapt")
@GradleAndroidTest
fun testAndroidWithKaptIncremental(
gradleVersion: GradleVersion,
agpVersion: String,
jdkVersion: JdkVersions.ProvidedJdk,
) {
project(
"AndroidIncrementalMultiModule",
gradleVersion,
buildOptions = defaultBuildOptions.copy(androidVersion = agpVersion),
buildJdk = jdkVersion.location
) {
val appProject = subProject("app")
appProject.buildGradle.modify {
//language=Gradle
"""
apply plugin: 'org.jetbrains.kotlin.kapt'
$it
""".trimIndent()
}
build(":app:testDebugUnitTest")
appProject.kotlinSourcesDir().resolve("com/example/KotlinActivity.kt").appendText(
//language=kt
"""
{
private val x = 1
}
""".trimIndent()
)
build(":app:testDebugUnitTest") {
assertOutputDoesNotContain(NON_INCREMENTAL_COMPILATION_WILL_BE_PERFORMED)
}
}
}
private fun TestProject.setupDataBinding() {
buildGradle.appendText(
//language=Gradle
@@ -0,0 +1,284 @@
/*
* Copyright 2010-2022 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
import org.gradle.api.logging.LogLevel
import org.gradle.testkit.runner.BuildResult
import org.gradle.util.GradleVersion
import org.jetbrains.kotlin.gradle.testbase.*
import org.jetbrains.kotlin.gradle.util.checkedReplace
import org.junit.jupiter.api.DisplayName
import java.nio.file.Files
import kotlin.io.path.appendText
import kotlin.io.path.readLines
import kotlin.io.path.writeText
import kotlin.test.assertContains
@DisplayName("base kotlin-android tests")
@AndroidGradlePluginTests
class KotlinAndroidIT : KGPBaseTest() {
@DisplayName("android project compilation smoke test")
@GradleAndroidTest
fun testSimpleCompile(
gradleVersion: GradleVersion,
agpVersion: String,
jdkVersion: JdkVersions.ProvidedJdk,
) {
fun BuildResult.assertKotlinGradleBuildServicesAreInitialized() {
assertOutputContainsExactTimes("Initialized KotlinGradleBuildServices", expectedRepetitionTimes = 1)
assertOutputContainsExactTimes("Disposed KotlinGradleBuildServices", expectedRepetitionTimes = 1)
}
project(
"AndroidProject",
gradleVersion,
buildOptions = defaultBuildOptions.copy(androidVersion = agpVersion, logLevel = LogLevel.DEBUG),
buildJdk = jdkVersion.location
) {
val modules = listOf("Android", "Lib")
val flavors = listOf("Flavor1", "Flavor2")
val buildTypes = listOf("Debug")
val expectedTasks = mutableListOf<String>()
for (module in modules) {
for (flavor in flavors) {
for (buildType in buildTypes) {
expectedTasks.add(":$module:compile$flavor${buildType}Kotlin")
}
}
}
build("assembleDebug", "test") {
val pattern = ":Test:compile[\\w\\d]+Kotlin"
expectedTasks.addAll(findTasksByPattern(pattern.toRegex()))
assertTasksExecuted(expectedTasks)
assertOutputContains("InternalDummyTest PASSED")
assertKotlinGradleBuildServicesAreInitialized()
}
// Run the a build second time, assert everything is up-to-date
build("assembleDebug") {
assertTasksUpToDate(expectedTasks)
assertKotlinGradleBuildServicesAreInitialized()
}
}
}
@DisplayName("KT-16897: the `assembleAndroidTest` task builds fine without the `build` task")
@GradleAndroidTest
fun testAssembleAndroidTest(
gradleVersion: GradleVersion,
agpVersion: String,
jdkVersion: JdkVersions.ProvidedJdk,
) {
project(
"AndroidProject",
gradleVersion,
buildOptions = defaultBuildOptions.copy(androidVersion = agpVersion),
buildJdk = jdkVersion.location
) {
build("assembleAndroidTest")
}
}
@DisplayName("KT-12303: compilation works fine with icepick")
@GradleAndroidTest
fun testAndroidIcepickProject(
gradleVersion: GradleVersion,
agpVersion: String,
jdkVersion: JdkVersions.ProvidedJdk,
) {
project(
"AndroidIcepickProject",
gradleVersion,
buildOptions = defaultBuildOptions.copy(androidVersion = agpVersion),
buildJdk = jdkVersion.location
) {
build("assembleDebug")
}
}
@DisplayName("compilation works fine with parcelize")
@GradleAndroidTest
fun testParcelize(
gradleVersion: GradleVersion,
agpVersion: String,
jdkVersion: JdkVersions.ProvidedJdk,
) {
project(
"AndroidParcelizeProject",
gradleVersion,
buildOptions = defaultBuildOptions.copy(androidVersion = agpVersion),
buildJdk = jdkVersion.location
) {
build("assembleDebug")
}
}
@DisplayName("KT-46626: kotlin-android produces clear error message when no android plugin is applied")
@GradleAndroidTest
fun shouldAllowToApplyPluginWhenAndroidPluginIsMissing(
gradleVersion: GradleVersion,
agpVersion: String,
jdkVersion: JdkVersions.ProvidedJdk,
) {
project(
"simpleProject",
gradleVersion,
buildOptions = defaultBuildOptions.copy(androidVersion = agpVersion),
buildJdk = jdkVersion.location
) {
fun replaceKotlinJvmByKotlinAndroid(specifyPluginVersion: Boolean): (String) -> CharSequence = { line ->
if (line.contains("id \"org.jetbrains.kotlin.jvm\"")) {
" id \"org.jetbrains.kotlin.android\"" + if (specifyPluginVersion) " version \"${'$'}kotlin_version\"" else ""
} else {
line
}
}
buildGradle.modify {
it.lines().joinToString(
separator = "\n",
transform = replaceKotlinJvmByKotlinAndroid(false)
)
}
settingsGradle.modify {
it.lines().joinToString(
separator = "\n",
transform = replaceKotlinJvmByKotlinAndroid(true)
)
}
buildAndFail("tasks") {
assertOutputContains("'kotlin-android' plugin requires one of the Android Gradle plugins.")
}
}
}
@DisplayName("KT-49483: lint dependencies are resolved properly")
@GradleAndroidTest
fun testLintDependencyResolutionKt49483(
gradleVersion: GradleVersion,
agpVersion: String,
jdkVersion: JdkVersions.ProvidedJdk,
) {
project(
"AndroidProject",
gradleVersion,
buildOptions = defaultBuildOptions.copy(androidVersion = agpVersion),
buildJdk = jdkVersion.location
) {
buildGradle.modify {
it.replace(
"plugins {",
"""
plugins {
id("com.android.lint")
""".trimIndent()
)
}
subProject("Lib").buildGradle.appendText(
//language=Gradle
"""
android {
lintOptions.checkDependencies = true
}
dependencies {
implementation(project(":java-lib"))
}
""".trimIndent()
)
settingsGradle.appendText(
//language=Gradle
"""
include("java-lib")
""".trimIndent()
)
subProject("java-lib").buildGradle.apply {
Files.createDirectories(parent)
writeText(
//language=Gradle
"""
plugins {
id("java-library")
id("com.android.lint")
}
""".trimIndent()
)
}
// Gradle 6 + AGP 4 produce a deprecation warning on parallel executions about resolving a configuration from another project
build(":Lib:lintFlavor1Debug", buildOptions = buildOptions.copy(parallel = gradleVersion >= GradleVersion.version("7.0"))) {
assertOutputDoesNotContain("as an external dependency and not analyze it.")
}
}
}
@DisplayName("publish with omitted stdlib version sets proper version")
@GradleAndroidTest
fun testOmittedStdlibVersion(
gradleVersion: GradleVersion,
agpVersion: String,
jdkVersion: JdkVersions.ProvidedJdk,
) {
project(
"AndroidProject",
gradleVersion,
buildOptions = defaultBuildOptions.copy(androidVersion = agpVersion),
buildJdk = jdkVersion.location
) {
subProject("Lib").buildGradle.modify {
it.checkedReplace(
"kotlin-stdlib:\$kotlin_version",
"kotlin-stdlib"
) +
//language=Gradle
"""
apply plugin: 'maven-publish'
android {
defaultPublishConfig 'flavor1Debug'
}
afterEvaluate {
publishing {
publications {
flavorDebug(MavenPublication) {
from components.flavor1Debug
group = 'com.example'
artifactId = 'flavor1Debug'
version = '1.0'
}
}
repositories {
maven {
url = "file://${'$'}buildDir/repo"
}
}
}
}
""".trimIndent()
}
build(":Lib:assembleFlavor1Debug", ":Lib:publish") {
assertTasksExecuted(":Lib:compileFlavor1DebugKotlin", ":Lib:publishFlavorDebugPublicationToMavenRepository")
val pomLines = subProject("Lib").projectPath.resolve("build/repo/com/example/flavor1Debug/1.0/flavor1Debug-1.0.pom").readLines()
val stdlibVersionLineNumber = pomLines.indexOfFirst { "<artifactId>kotlin-stdlib</artifactId>" in it } + 1
val versionLine = pomLines[stdlibVersionLineNumber]
assertContains(versionLine, "<version>${buildOptions.kotlinVersion}</version>")
}
}
}
}
@@ -0,0 +1,183 @@
/*
* Copyright 2010-2022 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
import org.gradle.api.logging.LogLevel
import org.gradle.util.GradleVersion
import org.jetbrains.kotlin.gradle.testbase.*
import org.jetbrains.kotlin.gradle.util.getFileByName
import org.jetbrains.kotlin.gradle.util.getFilesByNames
import org.jetbrains.kotlin.gradle.util.modify
import org.junit.jupiter.api.DisplayName
import kotlin.io.path.appendText
import kotlin.io.path.writeText
@DisplayName("kotlin-android incremental compilation tests")
@AndroidGradlePluginTests
class KotlinAndroidIncrementalIT : KGPBaseTest() {
@DisplayName("incremental compilation works with single-module android projects")
@GradleAndroidTest
fun testIncrementalCompilation(
gradleVersion: GradleVersion,
agpVersion: String,
jdkVersion: JdkVersions.ProvidedJdk,
) {
project(
"AndroidIncrementalSingleModuleProject",
gradleVersion,
buildOptions = defaultBuildOptions.copy(androidVersion = agpVersion),
buildJdk = jdkVersion.location
) {
build("assembleDebug")
val modifiedSrc = subProject("app").kotlinSourcesDir().resolve("com/example/getSomething.kt")
modifiedSrc.writeText(
//language=kt
"""
package com.example
fun getSomething() = 10
""".trimIndent()
)
build("assembleDebug", buildOptions = buildOptions.copy(logLevel = LogLevel.DEBUG)) {
val affectedKotlinFiles = listOf(
"app/src/main/kotlin/com/example/KotlinActivity1.kt",
"app/src/main/kotlin/com/example/getSomething.kt",
)
val affectedJavaFiles = listOf(
"app/src/main/java/com/example/JavaActivity.java",
)
assertCompiledKotlinSources(affectedKotlinFiles.toPaths(), output)
assertCompiledJavaSources(affectedJavaFiles.toPaths(), output)
}
}
}
@DisplayName("incremental compilation works with multi-module android projects")
@GradleAndroidTest
fun testMultiModuleIC(
gradleVersion: GradleVersion,
agpVersion: String,
jdkVersion: JdkVersions.ProvidedJdk,
) {
project(
"AndroidIncrementalMultiModule",
gradleVersion,
buildOptions = defaultBuildOptions.copy(androidVersion = agpVersion),
buildJdk = jdkVersion.location
) {
build("assembleDebug")
val libAndroidUtilKt = subProject("libAndroid").kotlinSourcesDir().resolve("com/example/libAndroidUtil.kt")
libAndroidUtilKt.modify { it.replace("fun libAndroidUtil(): String", "fun libAndroidUtil(): CharSequence") }
val optionsWithDebug = buildOptions.copy(logLevel = LogLevel.DEBUG)
build("assembleDebug", buildOptions = optionsWithDebug) {
val affectedSources = listOf(
libAndroidUtilKt,
subProject("app").kotlinSourcesDir().resolve("com/example/useLibAndroidUtil.kt")
).relativizeTo(projectPath)
assertCompiledKotlinSources(affectedSources, output)
}
val libAndroidClassesOnlyUtilKt = subProject("libAndroidClassesOnly").kotlinSourcesDir().resolve("com/example/LibAndroidClassesOnlyUtil.kt")
libAndroidClassesOnlyUtilKt.modify {
it.replace(
"fun libAndroidClassesOnlyUtil(): String",
"fun libAndroidClassesOnlyUtil(): CharSequence"
)
}
build("assembleDebug", buildOptions = optionsWithDebug) {
val affectedSources = listOf(
libAndroidClassesOnlyUtilKt,
subProject("app").kotlinSourcesDir().resolve("com/example/useLibAndroidClassesOnlyUtil.kt")
).relativizeTo(projectPath)
assertCompiledKotlinSources(affectedSources, output)
}
val libJvmUtilKt = subProject("libJvmClassesOnly").kotlinSourcesDir().resolve("com/example/LibJvmUtil.kt")
libJvmUtilKt.modify { it.replace("fun libJvmUtil(): String", "fun libJvmUtil(): CharSequence") }
build("assembleDebug", buildOptions = optionsWithDebug) {
val affectedSources = listOf(
libJvmUtilKt,
subProject("app").kotlinSourcesDir().resolve("com/example/useLibJvmUtil.kt")
).relativizeTo(projectPath)
assertCompiledKotlinSources(affectedSources, output)
}
}
}
@DisplayName("incremental compilation does nothing without changes")
@GradleAndroidTest
fun testIncrementalBuildWithNoChanges(
gradleVersion: GradleVersion,
agpVersion: String,
jdkVersion: JdkVersions.ProvidedJdk,
) {
project(
"AndroidIncrementalSingleModuleProject",
gradleVersion,
buildOptions = defaultBuildOptions.copy(androidVersion = agpVersion),
buildJdk = jdkVersion.location
) {
val expectedTasks = listOf(
":app:compileDebugKotlin",
":app:compileDebugJavaWithJavac"
)
build("assembleDebug") {
assertTasksExecuted(expectedTasks)
}
build("assembleDebug") {
assertTasksUpToDate(expectedTasks)
}
}
}
@DisplayName("KT-49066: incremental compilation with custom module name")
@GradleAndroidTest
fun testCustomModuleName(
gradleVersion: GradleVersion,
agpVersion: String,
jdkVersion: JdkVersions.ProvidedJdk,
) {
project(
"AndroidIncrementalMultiModule",
gradleVersion,
buildOptions = defaultBuildOptions.copy(androidVersion = agpVersion),
buildJdk = jdkVersion.location
) {
subProject("libAndroid").buildGradle.appendText(
//language=Gradle
"""
android.kotlinOptions {
moduleName = "custom_path"
}
""".trimIndent()
)
build("assembleDebug") {
assertFileInProjectExists("libAndroid/build/tmp/kotlin-classes/debug/META-INF/custom_path.kotlin_module")
}
val libAndroidUtilKt = subProject("libAndroid").kotlinSourcesDir().resolve("com/example/libAndroidUtil.kt")
libAndroidUtilKt.modify { it.replace("fun libAndroidUtil(): String", "fun libAndroidUtil(): CharSequence") }
build("assembleDebug", buildOptions = buildOptions.copy(logLevel = LogLevel.DEBUG)) {
val affectedSources = listOf(
libAndroidUtilKt,
subProject("app").kotlinSourcesDir().resolve("com/example/useLibAndroidUtil.kt")
).relativizeTo(projectPath)
assertCompiledKotlinSources(affectedSources, output)
}
}
}
}
@@ -0,0 +1,706 @@
/*
* Copyright 2010-2022 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
import org.gradle.util.GradleVersion
import org.jetbrains.kotlin.gradle.testbase.*
import org.jetbrains.kotlin.gradle.tooling.BuildKotlinToolingMetadataTask
import org.jetbrains.kotlin.gradle.util.AGPVersion
import org.jetbrains.kotlin.gradle.util.testResolveAllConfigurations
import org.junit.jupiter.api.Disabled
import org.junit.jupiter.api.DisplayName
import java.io.File
import java.nio.file.Files
import java.util.zip.ZipFile
import kotlin.io.path.appendText
import kotlin.io.path.extension
import kotlin.io.path.readText
import kotlin.io.path.writeText
import kotlin.streams.toList
import kotlin.test.*
@DisplayName("kotlin-android with mpp")
@AndroidGradlePluginTests
class KotlinAndroidMppIT : KGPBaseTest() {
@DisplayName("KT-50736: whenEvaluated waits for AGP being applied later")
@GradleAndroidTest
fun testAfterEvaluateOrdering(
gradleVersion: GradleVersion,
agpVersion: String,
jdkVersion: JdkVersions.ProvidedJdk,
) {
project(
"AndroidProject",
gradleVersion,
buildOptions = defaultBuildOptions.copy(androidVersion = agpVersion),
buildJdk = jdkVersion.location
) {
subProject("Lib").buildGradle.writeText(
//language=Gradle
"""
buildscript {
repositories {
mavenLocal()
google()
gradlePluginPortal()
}
dependencies {
classpath "com.android.tools.build:gradle:${'$'}android_tools_version"
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:${'$'}kotlin_version"
}
}
plugins {
id 'org.jetbrains.kotlin.multiplatform'
}
class MyAction implements kotlin.jvm.functions.Function1<Project, Void> {
Void invoke (Project p) {
println("compilations: " + p.kotlin.targets.getByName("android").compilations.names)
}
}
org.jetbrains.kotlin.gradle.plugin.KotlinMultiplatformPluginKt.whenEvaluated(project, new MyAction ())
apply plugin : "android-library"
android {
compileSdkVersion 22
}
kotlin { android("android") { } }
""".trimIndent()
)
build("help") {
val reportedCompilations = output.lines()
.single { it.contains("compilations: ") }
.substringAfter("compilations: ")
.removeSurrounding("[", "]")
.split(", ")
.toSet()
assertEquals(
setOf("debug", "debugAndroidTest", "debugUnitTest", "release", "releaseUnitTest"),
reportedCompilations
)
}
}
}
@DisplayName("KotlinToolingMetadataArtifact is bundled into apk")
@GradleAndroidTest
fun testKotlinToolingMetadataBundle(
gradleVersion: GradleVersion,
agpVersion: String,
jdkVersion: JdkVersions.ProvidedJdk,
) {
project(
"kotlinToolingMetadataAndroid",
gradleVersion,
buildOptions = defaultBuildOptions.copy(androidVersion = agpVersion),
buildJdk = jdkVersion.location
) {
build("assembleDebug") {
assertTasksAreNotInTaskGraph(":${BuildKotlinToolingMetadataTask.defaultTaskName}")
val debugApk = projectPath.resolve("build/outputs/apk/debug/project-debug.apk")
assertFileExists(debugApk)
ZipFile(debugApk.toFile()).use { zip ->
assertNull(zip.getEntry("kotlin-tooling-metadata.json"), "Expected metadata *not* being packaged into debug apk")
}
}
build("assembleRelease") {
assertTasksExecuted(":${BuildKotlinToolingMetadataTask.defaultTaskName}")
val releaseApk = projectPath.resolve("build/outputs/apk/release/project-release-unsigned.apk")
assertFileExists(releaseApk)
ZipFile(releaseApk.toFile()).use { zip ->
assertNotNull(zip.getEntry("kotlin-tooling-metadata.json"), "Expected metadata being packaged into release apk")
}
}
}
}
@DisplayName("mpp source sets are registered in AGP")
@GradleAndroidTest
@AndroidTestVersions(minVersion = TestVersions.AGP.AGP_42)
fun testAndroidMppSourceSets(
gradleVersion: GradleVersion,
agpVersion: String,
jdkVersion: JdkVersions.ProvidedJdk,
) {
project(
"new-mpp-android-source-sets",
gradleVersion,
buildOptions = defaultBuildOptions.copy(androidVersion = agpVersion),
buildJdk = jdkVersion.location
) {
// AbstractReportTask#generate() task action was removed in Gradle 6.8+,
// that SourceSetTask is using: https://github.com/gradle/gradle/commit/4dac91ab87ea33ee8689d2a62b691b119198e7c7
// leading to the issue that ":sourceSets" task is always in 'UP-TO-DATE' state.
// Skipping this check until the test will start using AGP 7.0-alpha03+
// AGP 4.x is not compatible with Gradle 7.0, so just skip when the Gradle version is lower than 7.0
if (gradleVersion >= GradleVersion.version("7.0")) {
build("sourceSets") {
fun assertOutputContainsOsIndependent(expectedString: String) {
assertOutputContains(expectedString.replace("/", File.separator))
}
assertOutputContainsOsIndependent("Android resources: [lib/src/main/res, lib/src/androidMain/res]")
assertOutputContainsOsIndependent("Assets: [lib/src/main/assets, lib/src/androidMain/assets]")
assertOutputContainsOsIndependent("AIDL sources: [lib/src/main/aidl, lib/src/androidMain/aidl]")
assertOutputContainsOsIndependent("RenderScript sources: [lib/src/main/rs, lib/src/androidMain/rs]")
assertOutputContainsOsIndependent("JNI sources: [lib/src/main/jni, lib/src/androidMain/jni]")
assertOutputContainsOsIndependent("JNI libraries: [lib/src/main/jniLibs, lib/src/androidMain/jniLibs]")
assertOutputContainsOsIndependent("Java-style resources: [lib/src/main/resources, lib/src/androidMain/resources]")
assertOutputContainsOsIndependent("Android resources: [lib/src/androidTestDebug/res, lib/src/androidAndroidTestDebug/res]")
assertOutputContainsOsIndependent("Assets: [lib/src/androidTestDebug/assets, lib/src/androidAndroidTestDebug/assets]")
assertOutputContainsOsIndependent("AIDL sources: [lib/src/androidTestDebug/aidl, lib/src/androidAndroidTestDebug/aidl]")
assertOutputContainsOsIndependent("RenderScript sources: [lib/src/androidTestDebug/rs, lib/src/androidAndroidTestDebug/rs]")
assertOutputContainsOsIndependent("JNI sources: [lib/src/androidTestDebug/jni, lib/src/androidAndroidTestDebug/jni]")
assertOutputContainsOsIndependent("JNI libraries: [lib/src/androidTestDebug/jniLibs, lib/src/androidAndroidTestDebug/jniLibs]")
assertOutputContainsOsIndependent("Java-style resources: [lib/src/androidTestDebug/resources, lib/src/androidAndroidTestDebug/resources]")
assertOutputContainsOsIndependent("Java-style resources: [lib/betaSrc/paidBeta/resources, lib/src/androidPaidBeta/resources]")
assertOutputContainsOsIndependent("Java-style resources: [lib/betaSrc/paidBetaDebug/resources, lib/src/androidPaidBetaDebug/resources]")
assertOutputContainsOsIndependent("Java-style resources: [lib/betaSrc/paidBetaRelease/resources, lib/src/androidPaidBetaRelease/resources]")
assertOutputContainsOsIndependent("Java-style resources: [lib/betaSrc/freeBeta/resources, lib/src/androidFreeBeta/resources]")
assertOutputContainsOsIndependent("Java-style resources: [lib/betaSrc/freeBetaDebug/resources, lib/src/androidFreeBetaDebug/resources]")
assertOutputContainsOsIndependent("Java-style resources: [lib/betaSrc/freeBetaRelease/resources, lib/src/androidFreeBetaRelease/resources]")
}
}
buildAndFail("testFreeBetaDebug") {
assertOutputContains("CommonTest > fail FAILED")
assertOutputContains("TestKotlin > fail FAILED")
assertOutputContains("AndroidTestKotlin > fail FAILED")
assertOutputContains("TestJava > fail FAILED")
}
build("assemble")
buildAndFail("connectedAndroidTest") {
assertOutputContains("No connected devices!")
}
}
}
@DisplayName("android app can depend on mpp lib")
@GradleAndroidTest
fun testAndroidWithNewMppApp(
gradleVersion: GradleVersion,
agpVersion: String,
jdkVersion: JdkVersions.ProvidedJdk,
) {
project(
"new-mpp-android",
gradleVersion,
buildOptions = defaultBuildOptions.copy(androidVersion = agpVersion),
buildJdk = jdkVersion.location
) {
build("assemble", "compileDebugUnitTestJavaWithJavac", "printCompilerPluginOptions") {
// KT-30784
assertOutputDoesNotContain("API 'variant.getPackageLibrary()' is obsolete and has been replaced")
assertOutputContains("KT-29964 OK") // Output from lib/build.gradle
assertTasksExecuted(
":lib:compileDebugKotlinAndroidLib",
":lib:compileReleaseKotlinAndroidLib",
":lib:compileKotlinJvmLib",
":lib:compileKotlinJsLib",
":lib:compileCommonMainKotlinMetadata",
":app:compileDebugKotlinAndroidApp",
":app:compileReleaseKotlinAndroidApp",
":app:compileKotlinJvmApp",
":app:compileKotlinJsApp",
":app:compileCommonMainKotlinMetadata",
":lib:compileDebugUnitTestJavaWithJavac",
":app:compileDebugUnitTestJavaWithJavac"
)
listOf("debug", "release").forEach { variant ->
assertFileInProjectExists("lib/build/tmp/kotlin-classes/$variant/com/example/lib/ExpectedLibClass.class")
assertFileInProjectExists("lib/build/tmp/kotlin-classes/$variant/com/example/lib/CommonLibClass.class")
assertFileInProjectExists("lib/build/tmp/kotlin-classes/$variant/com/example/lib/AndroidLibClass.class")
assertFileInProjectExists("app/build/tmp/kotlin-classes/$variant/com/example/app/AKt.class")
assertFileInProjectExists("app/build/tmp/kotlin-classes/$variant/com/example/app/KtUsageKt.class")
}
}
val groupDir = subProject("lib").projectPath.resolve("build/repo/com/example")
build("publish") {
assertDirectoryExists(groupDir.resolve("lib-jvmlib"))
assertDirectoryExists(groupDir.resolve("lib-jslib"))
assertDirectoryExists(groupDir.resolve("lib-androidlib"))
assertDirectoryExists(groupDir.resolve("lib-androidlib-debug"))
}
groupDir.deleteRecursively()
// Choose a single variant to publish, check that it's there:
subProject("lib").buildGradle.appendText("\nkotlin.android('androidLib').publishLibraryVariants = ['release']")
build("publish") {
assertFileExists(groupDir.resolve("lib-androidlib/1.0/lib-androidlib-1.0.aar"))
assertFileExists(groupDir.resolve("lib-androidlib/1.0/lib-androidlib-1.0-sources.jar"))
assertFileInProjectNotExists("$groupDir/lib-androidlib-debug")
}
groupDir.deleteRecursively()
// Enable publishing for all Android variants:
subProject("lib").buildGradle.appendText("\nkotlin.android('androidLib') { publishAllLibraryVariants() }")
build("publish") {
assertFileExists(groupDir.resolve("lib-androidlib/1.0/lib-androidlib-1.0.aar"))
assertFileExists(groupDir.resolve("lib-androidlib/1.0/lib-androidlib-1.0-sources.jar"))
assertFileExists(groupDir.resolve("lib-androidlib-debug/1.0/lib-androidlib-debug-1.0.aar"))
assertFileExists(groupDir.resolve("lib-androidlib-debug/1.0/lib-androidlib-debug-1.0-sources.jar"))
}
groupDir.deleteRecursively()
// Then group the variants by flavor and check that only one publication is created:
subProject("lib").buildGradle.appendText("\nkotlin.android('androidLib').publishLibraryVariantsGroupedByFlavor = true")
build("publish") {
assertFileExists(groupDir.resolve("lib-androidlib/1.0/lib-androidlib-1.0.aar"))
assertFileExists(groupDir.resolve("lib-androidlib/1.0/lib-androidlib-1.0-sources.jar"))
assertFileExists(groupDir.resolve("lib-androidlib/1.0/lib-androidlib-1.0-debug.aar"))
assertFileExists(groupDir.resolve("lib-androidlib/1.0/lib-androidlib-1.0-debug-sources.jar"))
}
groupDir.deleteRecursively()
// Add one flavor dimension with two flavors, check that the flavors produce grouped publications:
subProject("lib").buildGradle.appendText(
"\nandroid { flavorDimensions('foo'); productFlavors { fooBar { dimension 'foo' }; fooBaz { dimension 'foo' } } }"
)
build("publish") {
listOf("foobar", "foobaz").forEach { flavor ->
assertFileExists(groupDir.resolve("lib-androidlib-$flavor/1.0/lib-androidlib-$flavor-1.0.aar"))
assertFileExists(groupDir.resolve("lib-androidlib-$flavor/1.0/lib-androidlib-$flavor-1.0-sources.jar"))
assertFileExists(groupDir.resolve("lib-androidlib-$flavor/1.0/lib-androidlib-$flavor-1.0-debug.aar"))
assertFileExists(groupDir.resolve("lib-androidlib-$flavor/1.0/lib-androidlib-$flavor-1.0-debug-sources.jar"))
}
}
groupDir.deleteRecursively()
// Disable the grouping and check that all the variants are published under separate artifactIds:
subProject("lib").buildGradle.appendText(
"\nkotlin.android('androidLib') { publishLibraryVariantsGroupedByFlavor = false }"
)
build("publish") {
listOf("foobar", "foobaz").forEach { flavor ->
listOf("-debug", "").forEach { buildType ->
assertFileExists(groupDir.resolve("lib-androidlib-$flavor$buildType/1.0/lib-androidlib-$flavor$buildType-1.0.aar"))
assertFileExists(groupDir.resolve("lib-androidlib-$flavor$buildType/1.0/lib-androidlib-$flavor$buildType-1.0-sources.jar"))
}
}
}
groupDir.deleteRecursively()
// Convert the 'app' project to a library, publish the two without metadata,
// check that the dependencies in the POMs are correctly rewritten:
val appGroupDir = subProject("app").projectPath.resolve("build/repo/com/example")
subProject("app").buildGradle.modify {
it.replace("com.android.application", "com.android.library")
.replace("applicationId", "//") +
//language=Gradle
"""
apply plugin: 'maven-publish'
publishing { repositories { maven { url = uri("${'$'}buildDir/repo") } } }
kotlin.android('androidApp') { publishAllLibraryVariants() }
android { flavorDimensions('foo'); productFlavors { fooBar { dimension 'foo' }; fooBaz { dimension 'foo' } } }
""".trimIndent()
}
build("publish") {
listOf("foobar", "foobaz").forEach { flavor ->
listOf("-debug", "").forEach { buildType ->
assertFileExists(appGroupDir.resolve("app-androidapp-$flavor$buildType/1.0/app-androidapp-$flavor$buildType-1.0.aar"))
assertFileExists(appGroupDir.resolve("app-androidapp-$flavor$buildType/1.0/app-androidapp-$flavor$buildType-1.0-sources.jar"))
val pomText =
appGroupDir.resolve("app-androidapp-$flavor$buildType/1.0/app-androidapp-$flavor$buildType-1.0.pom").readText()
.replace("\\s+".toRegex(), "")
assertContains(
pomText,
"<artifactId>lib-androidlib-$flavor$buildType</artifactId><version>1.0</version><scope>runtime</scope>"
)
}
}
}
appGroupDir.deleteRecursively()
// Also check that api and runtimeOnly MPP dependencies get correctly published with the appropriate scope, KT-29476:
subProject("app").buildGradle.modify {
it.replace("implementation project(':lib')", "api project(':lib')") +
//language=Gradle
"""
kotlin.sourceSets.commonMain.dependencies {
runtimeOnly(kotlin('reflect'))
}
""".trimIndent()
}
build("publish") {
listOf("foobar", "foobaz").forEach { flavor ->
listOf("-debug", "").forEach { buildType ->
val pomText =
appGroupDir.resolve("app-androidapp-$flavor$buildType/1.0/app-androidapp-$flavor$buildType-1.0.pom").readText()
.replace("\\s+".toRegex(), "")
assertContains(
pomText,
"<artifactId>lib-androidlib-$flavor$buildType</artifactId><version>1.0</version><scope>compile</scope>"
)
assertContains(
pomText,
"<artifactId>kotlin-reflect</artifactId><version>${buildOptions.kotlinVersion}</version><scope>runtime</scope>"
)
}
}
}
}
}
@DisplayName("KT-29343: mpp source set dependencies are propagated to android tests")
@GradleAndroidTest
fun testAndroidMppProductionDependenciesInTests(
gradleVersion: GradleVersion,
agpVersion: String,
jdkVersion: JdkVersions.ProvidedJdk,
) {
project(
"new-mpp-android",
gradleVersion,
buildOptions = defaultBuildOptions.copy(androidVersion = agpVersion),
buildJdk = jdkVersion.location
) {
// Test the fix for KT-29343
subProject("lib").buildGradle.appendText(
//language=Gradle
"""
kotlin.sourceSets {
commonMain {
dependencies {
implementation kotlin("stdlib-common")
}
}
androidLibDebug {
dependencies {
implementation kotlin("reflect")
}
}
androidLibRelease {
dependencies {
implementation kotlin("test-junit")
}
}
}
""".trimIndent()
)
val kotlinVersion = buildOptions.kotlinVersion
testResolveAllConfigurations("lib") { _, buildResult ->
// androidLibDebug:
buildResult.assertOutputContains(">> :lib:debugCompileClasspath --> kotlin-reflect-$kotlinVersion.jar")
buildResult.assertOutputDoesNotContain(">> :lib:releaseCompileClasspath --> kotlin-reflect-$kotlinVersion.jar")
buildResult.assertOutputContains(">> :lib:debugAndroidTestCompileClasspath --> kotlin-reflect-$kotlinVersion.jar")
buildResult.assertOutputContains(">> :lib:debugUnitTestCompileClasspath --> kotlin-reflect-$kotlinVersion.jar")
buildResult.assertOutputDoesNotContain(">> :lib:releaseUnitTestCompileClasspath --> kotlin-reflect-$kotlinVersion.jar")
// androidLibRelease:
buildResult.assertOutputDoesNotContain(">> :lib:debugCompileClasspath --> kotlin-test-junit-$kotlinVersion.jar")
buildResult.assertOutputContains(">> :lib:releaseCompileClasspath --> kotlin-test-junit-$kotlinVersion.jar")
buildResult.assertOutputDoesNotContain(">> :lib:debugAndroidTestCompileClasspath --> kotlin-test-junit-$kotlinVersion.jar")
buildResult.assertOutputDoesNotContain(">> :lib:debugUnitTestCompileClasspath --> kotlin-test-junit-$kotlinVersion.jar")
buildResult.assertOutputContains(">> :lib:releaseUnitTestCompileClasspath --> kotlin-test-junit-$kotlinVersion.jar")
}
}
}
@DisplayName("KT-27714: custom attributes are copied to android compilation configurations")
@GradleAndroidTest
fun testCustomAttributesInAndroidTargets(
gradleVersion: GradleVersion,
agpVersion: String,
jdkVersion: JdkVersions.ProvidedJdk,
) {
project(
"new-mpp-android",
gradleVersion,
buildOptions = defaultBuildOptions.copy(androidVersion = agpVersion),
buildJdk = jdkVersion.location
) {
val libBuildScript = subProject("lib").buildGradle
val appBuildScript = subProject("app").buildGradle
// Enable publishing for all Android variants:
libBuildScript.appendText(
//language=Gradle
"""
kotlin.android('androidLib') { publishAllLibraryVariants() }
""".trimIndent()
)
val groupDir = subProject("lib").projectPath.resolve("build/repo/com/example")
build("publish") {
// Also check that custom user-specified attributes are written in all Android modules metadata:
assertFileContains(
groupDir.resolve("lib-androidlib/1.0/lib-androidlib-1.0.module"),
"\"com.example.target\": \"androidLib\"",
"\"com.example.compilation\": \"release\""
)
assertFileContains(
groupDir.resolve("lib-androidlib-debug/1.0/lib-androidlib-debug-1.0.module"),
"\"com.example.target\": \"androidLib\"",
"\"com.example.compilation\": \"debug\""
)
}
groupDir.deleteRecursively()
// Check that the consumer side uses custom attributes specified in the target and compilations:
val appBuildScriptBackup = appBuildScript.readText()
libBuildScript.appendText(
//language=Gradle
"""
kotlin.targets.all {
attributes.attribute(
Attribute.of("com.example.target", String),
targetName
)
}
""".trimIndent()
)
appBuildScript.appendText(
//language=Gradle
"""
kotlin.targets.androidApp.attributes.attribute(
Attribute.of("com.example.target", String),
"notAndroidLib"
)
""".trimIndent()
)
buildAndFail(":app:compileDebugKotlinAndroidApp") {
// dependency resolution should fail
assertOutputContainsAny(
"Required com.example.target 'notAndroidLib'",
"attribute 'com.example.target' with value 'notAndroidLib'",
)
}
libBuildScript.writeText(
appBuildScriptBackup +
//language=Gradle
"""
kotlin.targets.all {
compilations.all {
attributes.attribute(
Attribute.of("com.example.compilation", String),
targetName + compilationName.capitalize()
)
}
}
""".trimIndent()
)
appBuildScript.writeText(
appBuildScriptBackup +
//language=Gradle
"""
kotlin.targets.androidApp.compilations.all {
attributes.attribute(
Attribute.of("com.example.compilation", String),
"notDebug"
)
}
""".trimIndent()
)
buildAndFail(":app:compileDebugKotlinAndroidApp") {
assertOutputContainsAny(
"Required com.example.compilation 'notDebug'",
"attribute 'com.example.compilation' with value 'notDebug'",
)
}
}
}
@DisplayName("KT-27170: android lint works with dependency on non-android mpp project")
@GradleAndroidTest
@Disabled // not fully migrated yet
fun testLintInAndroidProjectsDependingOnMppWithoutAndroid(
gradleVersion: GradleVersion,
agpVersion: String,
jdkVersion: JdkVersions.ProvidedJdk,
) {
project(
"AndroidProject",
gradleVersion,
buildOptions = defaultBuildOptions.copy(androidVersion = agpVersion),
buildJdk = jdkVersion.location
) {
// embedProject(Project("sample-lib", directoryPrefix = "new-mpp-lib-and-app")) // TODO
subProject("lib").buildGradle.appendText(
//language=Gradle
"""
dependencies { implementation(project(':sample-lib')) }
""".trimIndent()
)
val lintTask = ":Lib:lintFlavor1Debug"
build(lintTask) {
assertTasksExecuted(lintTask) // Check that the lint task ran successfully, KT-27170
}
}
}
@DisplayName("MPP allTests task depending on Android unit tests")
@GradleAndroidTest
fun testMppAllTests(
gradleVersion: GradleVersion,
agpVersion: String,
jdkVersion: JdkVersions.ProvidedJdk,
) {
project(
"new-mpp-android",
gradleVersion,
buildOptions = defaultBuildOptions.copy(androidVersion = agpVersion),
buildJdk = jdkVersion.location
) {
build(":lib:allTests", "--dry-run") {
assertOutputContains(":lib:testDebugUnitTest SKIPPED")
assertOutputContains(":lib:testReleaseUnitTest SKIPPED")
}
}
}
/**
* Starting from AGP version 7.1.0-alpha13, a new attribute com.android.build.api.attributes.AgpVersionAttr was added.
* This attribute is *not intended* to be published.
*/
@DisplayName("KT-49798: com.android.build.api.attributes.AgpVersionAttr is not published")
@GradleAndroidTest
@AndroidTestVersions(minVersion = TestVersions.AGP.AGP_71)
fun testKT49798AgpVersionAttrNotPublished(
gradleVersion: GradleVersion,
agpVersion: String,
jdkVersion: JdkVersions.ProvidedJdk,
) {
project(
"new-mpp-android",
gradleVersion,
buildOptions = defaultBuildOptions.copy(androidVersion = agpVersion),
buildJdk = jdkVersion.location
) {
build("publish") {
val libProject = subProject("lib")
val debugPublicationDirectory = libProject.projectPath.resolve("build/repo/com/example/lib-androidlib-debug")
val releasePublicationDirectory = libProject.projectPath.resolve("build/repo/com/example/lib-androidlib")
listOf(debugPublicationDirectory, releasePublicationDirectory).forEach { publicationDirectory ->
assertDirectoryExists(publicationDirectory)
val moduleFiles = Files.walk(publicationDirectory).use { it.filter { file -> file.extension == "module" }.toList() }
assertTrue(moduleFiles.isNotEmpty(), "Missing .module file in $publicationDirectory")
assertTrue(moduleFiles.size == 1, "Multiple .module files in $publicationDirectory: $moduleFiles")
val moduleFile = moduleFiles.single()
val moduleFileText = moduleFile.readText()
assertTrue("AgpVersionAttr" !in moduleFileText, ".module file $moduleFile leaks AgpVersionAttr")
}
}
}
}
@DisplayName("produced artifacts are consumable by projects with various AGP versions")
@GradleAndroidTest
@AndroidTestVersions(minVersion = TestVersions.AGP.AGP_71)
fun testAndroidMultiplatformPublicationAGPCompatibility(
gradleVersion: GradleVersion,
agpVersion: String,
jdkVersion: JdkVersions.ProvidedJdk,
) {
project(
"new-mpp-android-agp-compatibility",
gradleVersion,
buildOptions = defaultBuildOptions.copy(androidVersion = agpVersion),
buildJdk = jdkVersion.location
) {
/* Publish a producer library with the current version of AGP */
build(":producer:publishAllPublicationsToBuildDirRepository") {
/* Check expected publication layout */
assertDirectoryInProjectExists("build/repo/com/example/producer-android")
assertDirectoryInProjectExists("build/repo/com/example/producer-android-debug")
assertDirectoryInProjectExists("build/repo/com/example/producer-jvm")
}
val checkedConsumerAGPVersions = AGPVersion.testedVersions
// Special version added for testing KT-49798
.plus(AGPVersion.fromString("7.1.0-beta02"))
.filter { version -> version >= AGPVersion.fromString(TestVersions.AGP.AGP_42) }
.filter { version -> version < AGPVersion.fromString(TestVersions.AGP.MAX_SUPPORTED) }
.map { it.toString() }
checkedConsumerAGPVersions.forEach { consumerAgpVersion ->
println("Testing compatibility for AGP consumer version $consumerAgpVersion (Producer: $agpVersion)")
val buildOptions = buildOptions.copy(androidVersion = consumerAgpVersion)
/*
Project: multiplatformAndroidConsumer is a mpp project with jvm and android targets.
This project depends on the previous publication as 'commonMainImplementation' dependency
*/
build(":multiplatformAndroidConsumer:assemble", buildOptions = buildOptions)
/*
Project: plainAndroidConsumer only uses the 'kotlin("android")' plugin
This project depends on the previous publication as 'implementation' dependency
*/
build(":plainAndroidConsumer:assemble", buildOptions = buildOptions)
println("Successfully tested compatibility for AGP consumer version $consumerAgpVersion (Producer: $agpVersion)")
}
}
}
@DisplayName("KT-49877, KT-35916: associate compilation dependencies are passed correctly to android test compilations")
@GradleAndroidTest
@AndroidTestVersions(minVersion = TestVersions.AGP.AGP_71)
fun testAssociateCompilationDependenciesArePassedToAndroidTestCompilations(
gradleVersion: GradleVersion,
agpVersion: String,
jdkVersion: JdkVersions.ProvidedJdk,
) {
project(
"kt-49877",
gradleVersion,
buildOptions = defaultBuildOptions.copy(androidVersion = agpVersion),
buildJdk = jdkVersion.location
) {
build("allTests") {
assertTasksExecuted(
":compileDebugKotlinAndroid",
":compileReleaseKotlinAndroid",
":compileDebugUnitTestKotlinAndroid",
":compileReleaseUnitTestKotlinAndroid",
":testDebugUnitTest",
":testReleaseUnitTest",
)
}
// instrumented tests don't work without a device, so we only compile them
build("packageDebugAndroidTest") {
assertTasksExecuted(
":compileDebugAndroidTestKotlinAndroid",
)
}
}
}
}
@@ -85,6 +85,20 @@ fun assertCompiledKotlinSources(
assertSameFiles(expectedSources, actualSources, "Compiled Kotlin files differ${errorMessageSuffix}:\n")
}
/**
* Asserts all the .java files from [expectedSources] and only they are compiled
*
* Note: log level of output should be set to [LogLevel.DEBUG]
*/
fun GradleProject.assertCompiledJavaSources(
expectedSources: Iterable<Path>,
output: String,
errorMessageSuffix: String = ""
) {
val actualSources = extractCompiledJavaFiles(projectPath.toRealPath().toFile(), output)
assertSameFiles(expectedSources, actualSources, "Compiled Java files differ${errorMessageSuffix}:\n")
}
/**
* Asserts that compilation was non-incremental.
*
@@ -5,6 +5,7 @@
package org.jetbrains.kotlin.gradle.testbase
import org.gradle.api.logging.LogLevel
import org.gradle.testkit.runner.BuildResult
/**
@@ -19,6 +20,18 @@ fun BuildResult.assertOutputContains(
}
}
/**
* Asserts Gradle output contains any of [expectedSubString] strings.
*/
fun BuildResult.assertOutputContainsAny(
vararg expectedSubStrings: String
) {
assert(expectedSubStrings.any { output.contains(it) }) {
printBuildOutput()
"Build output does not contain any of \"$expectedSubStrings\""
}
}
/**
* Asserts Gradle output contains [expectedSubString] string exact times.
*/
@@ -8,6 +8,26 @@ package org.jetbrains.kotlin.gradle.testbase
import org.gradle.testkit.runner.BuildResult
import org.gradle.testkit.runner.TaskOutcome
/**
* Asserts given tasks are not present in the build task graph
*/
fun BuildResult.assertTasksAreNotInTaskGraph(vararg tasks: String) {
val presentTasks = tasks.filter { task(it) != null }
assert(presentTasks.isEmpty()) {
printBuildOutput()
"Tasks ${tasks.joinToString(prefix = "[", postfix = "]")} shouldn't be present in the task graph, but $presentTasks were present"
}
}
/**
* Returns all the affected during the build tasks, whose [org.gradle.api.Task.getPath] satisfies the [pattern]
*/
fun BuildResult.findTasksByPattern(pattern: Regex): Set<String> {
return tasks.map { it.path }.filter { taskPath ->
pattern.matches(taskPath)
}.toSet()
}
/**
* Asserts given [tasks] have 'SUCCESS' execution state.
*/
@@ -51,6 +71,13 @@ fun BuildResult.assertTasksUpToDate(vararg tasks: String) {
}
}
/**
* Asserts given [tasks] have 'UP-TO-DATE' execution state.
*/
fun BuildResult.assertTasksUpToDate(tasks: Collection<String>) {
assertTasksUpToDate(*tasks.toTypedArray())
}
/**
* Asserts given [tasks] have 'SKIPPED' execution state.
*/
@@ -53,12 +53,13 @@ fun TestProject.testResolveAllConfigurations(
}
) {
if (!skipSetup) {
val targetProject = subproject?.let { subProject(it) } ?: this
when {
Files.exists(buildGradle) -> buildGradle
Files.exists(targetProject.buildGradle) -> targetProject.buildGradle
.append("\n${generateResolveAllConfigurationsTask(excludeConfigurations)}")
Files.exists(buildGradleKts) -> buildGradleKts
Files.exists(targetProject.buildGradleKts) -> targetProject.buildGradleKts
.append("\n${generateResolveAllConfigurationsTaskKts(excludeConfigurations)}")
else -> error("Build script does not exists under $projectPath")
else -> error("Build script does not exist under $projectPath")
}
}
@@ -49,5 +49,6 @@ dependencies {
implementation 'com.google.dagger:dagger:2.9'
kapt 'com.google.dagger:dagger-compiler:2.9'
kapt 'javax.annotation:javax.annotation-api:1.3.2'
compileOnly 'org.glassfish:javax.annotation:10.0-b28'
}
@@ -9,7 +9,7 @@ import org.gradle.api.logging.Logger
import java.lang.ref.WeakReference
import java.util.HashMap
// todo: remove when https://github.com/gradle/gradle/issues/2678 is resolved
// todo: remove when https://github.com/gradle/gradle/issues/16991 is resolved
internal object TaskLoggers {
private val taskLoggers = HashMap<String, WeakReference<Logger>>()