diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/BaseGradleIT.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/BaseGradleIT.kt index fd0d2a4c7b9..7ef36abb488 100644 --- a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/BaseGradleIT.kt +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/BaseGradleIT.kt @@ -147,7 +147,8 @@ abstract class BaseGradleIT { val freeCommandLineArgs: List = emptyList(), val kotlinVersion: String = KOTLIN_VERSION, val kotlinDaemonDebugPort: Int? = null, - val usePreciseJavaTracking: Boolean? = null + val usePreciseJavaTracking: Boolean? = null, + val withBuildCache: Boolean = false ) open inner class Project( diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/BuildCacheIT.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/BuildCacheIT.kt new file mode 100644 index 00000000000..2a19a2e1110 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/BuildCacheIT.kt @@ -0,0 +1,214 @@ +/* + * Copyright 2010-2017 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jetbrains.kotlin.gradle + +import org.jetbrains.kotlin.gradle.util.checkBytecodeContains +import org.jetbrains.kotlin.gradle.util.modify +import org.junit.Test +import java.io.File +import java.nio.file.Files +import kotlin.test.assertEquals + +class BuildCacheIT : BaseGradleIT() { + override fun defaultBuildOptions(): BuildOptions = + super.defaultBuildOptions().copy(withBuildCache = true) + + companion object { + private val GRADLE_VERSION = "4.3.1" + } + + @Test + fun noCacheWithGradlePre43() = with(Project("simpleProject", "4.2")) { + // Check that even with the build cache enabled, the Kotlin tasks are not cacheable with Gradle < 4.3: + val optionsWithCache = defaultBuildOptions().copy(withBuildCache = true) + + build("assemble", options = optionsWithCache) { + assertSuccessful() + assertNotContains("Packing task ':compileKotlin'") + } + build("clean", "assemble", options = optionsWithCache) { + assertSuccessful() + assertNotContains(":compileKotlin FROM-CACHE") + assertContains(":compileJava FROM-CACHE") + } + } + + @Test + fun testCacheHitAfterClean() = with(Project("simpleProject", GRADLE_VERSION)) { + prepareLocalBuildCache() + + build("assemble") { + assertSuccessful() + assertContains("Packing task ':compileKotlin'") + } + build("clean", "assemble") { + assertSuccessful() + assertContains(":compileKotlin FROM-CACHE") + } + } + + @Test + fun testCacheHitAfterCacheHit() = with(Project("simpleProject", GRADLE_VERSION)) { + prepareLocalBuildCache() + + build("assemble") { + assertSuccessful() + // Should store the output into the cache: + assertContains("Packing task ':compileKotlin'") + } + + val sourceFile = File(projectDir, "src/main/kotlin/helloWorld.kt") + val originalSource: String = sourceFile.readText() + val modifiedSource: String = originalSource.replace(" and ", " + ") + sourceFile.writeText(modifiedSource) + + build("assemble") { + assertSuccessful() + assertContains("Packing task ':compileKotlin'") + } + + sourceFile.writeText(originalSource) + + build("assemble") { + assertSuccessful() + // Should load the output from cache: + assertContains(":compileKotlin FROM-CACHE") + } + + sourceFile.writeText(modifiedSource) + + build("assemble") { + assertSuccessful() + // And should load the output from cache again, without compilation: + assertContains(":compileKotlin FROM-CACHE") + } + } + + @Test + fun testCorrectBuildAfterCacheHit() = with(Project("simpleProject", GRADLE_VERSION)) { + prepareLocalBuildCache() + + val fooKt = File(projectDir, "src/main/kotlin/foo/foo.kt").apply { + parentFile.mkdirs() + writeText("package foo; fun foo(i: Int): Int = 1") + } + + // bar.kt references foo.kt; we expect it to recompile during the first build after a cache hit. + File(projectDir, "src/main/kotlin/bar/bar.kt").apply { + parentFile.mkdirs() + writeText("package bar; import foo.*; fun bar() = foo(1)") + } + + // First build, should be stored into the build cache: + build("assemble") { + assertSuccessful() + assertContains("Packing task ':compileKotlin'") + } + + // A cache hit: + build("clean", "assemble") { + assertSuccessful() + assertContains(":compileKotlin FROM-CACHE") + } + + // Change the return type Int to String, so that bar.kt should be recompiled, and check that: + fooKt.modify { it.replace("Int = 1", "String = \"abc\"") } + build("assemble") { + assertSuccessful() + checkBytecodeContains( + File(projectDir, "build/classes/kotlin/main/bar/BarKt.class"), + "INVOKESTATIC foo/FooKt.foo (I)Ljava/lang/String;") + } + } + + @Test + fun testKaptCachingDisabledByDefault() = with(Project("simple", GRADLE_VERSION, directoryPrefix = "kapt2")) { + prepareLocalBuildCache() + + build("build") { + assertSuccessful() + assertContains("Packing task ':kaptGenerateStubsKotlin'") + assertNotContains("Packing task ':kaptKotlin'") + assertContains("Caching disabled for task ':kaptKotlin': 'Caching is disabled by default for kapt") + } + + File(projectDir, "build.gradle").appendText("\n" + """ + afterEvaluate { + kaptKotlin.useBuildCache = true + } + """.trimIndent()) + + build("clean", "build") { + assertSuccessful() + assertContains(":kaptGenerateStubsKotlin FROM-CACHE") + assertContains("Packing task ':kaptKotlin'") + } + + build("clean", "build") { + assertSuccessful() + assertContains(":kaptGenerateStubsKotlin FROM-CACHE") + assertContains(":kaptKotlin FROM-CACHE") + } + } + + @Test + fun testCacheSameFiles() = testSameFiles(listOf( + Project("simpleProject", GRADLE_VERSION), + Project("simple", GRADLE_VERSION, directoryPrefix = "kapt2").apply { + setupWorkingDir() + File(projectDir, "build.gradle").appendText("\nafterEvaluate { kaptKotlin.useBuildCache = true }") + })) + + private fun testSameFiles(projects: List) { + projects.forEach { project -> + try { + project.prepareLocalBuildCache() + + lateinit var nonCacheOutputHashes: Map + + project.build("build") { + assertSuccessful() + nonCacheOutputHashes = getOutputFiles(File(project.projectDir, "build")) + + } + + project.build("clean", "build") { + assertSuccessful() + val outputFromCacheHashes = getOutputFiles(File(project.projectDir, "build")) + assertEquals(nonCacheOutputHashes, outputFromCacheHashes) + } + } + catch (e: AssertionError) { + throw AssertionError("Failed for project ${project.projectName}", e) + } + } + } + + private fun Project.prepareLocalBuildCache() { + if (!projectDir.exists()) { + setupWorkingDir() + } + val newCacheDirPath = Files.createTempDirectory("GradleTestBuildCache").toFile().absolutePath.replace("\\", "/") + File(projectDir, "settings.gradle").appendText("\nbuildCache.local.directory = '$newCacheDirPath'") + } + + private val outputExtensions = setOf("java", "kt", "class", "kotlin_module") + + private fun getOutputFiles(directory: File) = directory.walkTopDown() + .filter { it.extension in outputExtensions } + .associate { it to it.readBytes().contentHashCode() } +} \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/UpToDatenessIT.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/UpToDatenessIT.kt index b2276412b29..c04b2b5d757 100644 --- a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/UpToDatenessIT.kt +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/UpToDatenessIT.kt @@ -26,6 +26,7 @@ class UpToDateIT : BaseGradleIT() { OptionMutation("compileKotlin.kotlinOptions.freeCompilerArgs", "[]", "['-Xallow-kotlin-package']"), OptionMutation("kotlin.experimental.coroutines", "'error'", "'enable'"), OptionMutation("archivesBaseName", "'someName'", "'otherName'"), + subpluginOptionMutation, externalOutputMutation, compilerClasspathMutation) } @@ -90,6 +91,26 @@ class UpToDateIT : BaseGradleIT() { } } + private val subpluginOptionMutation = object : ProjectMutation { + override val name: String get() = "subpluginOptionMutation" + + override fun initProject(project: Project) = with(project) { + buildGradle.appendText("\n" + """ + buildscript { dependencies { classpath "org.jetbrains.kotlin:kotlin-allopen:${'$'}kotlin_version" } } + apply plugin: "kotlin-allopen" + allOpen { annotation("allopen.Foo"); annotation("allopen.Bar") } + """.trimIndent()) + } + + override fun mutateProject(project: Project) = with(project) { + buildGradle.modify { it.replace("allopen.Foo", "allopen.Baz") } + } + + override fun checkAfterRebuild(compiledProject: CompiledProject) = with(compiledProject) { + assertTasksExecuted(listOf(":compileKotlin")) + } + } + private val externalOutputMutation = object : ProjectMutation { override val name = "externalOutputMutation"