KT-45777: Clean up build cache tests for incremental compilation

- Clean up BuildCacheRelocationIT.testKotlinIncrementalCompilation
 - Delete BuildCacheIT.testKotlinCompileIncrementalBuildWithoutRelocation
   as it is already covered by the test in BuildCacheRelocationIT
 - Add BuildCacheRelocationIT.testKotlinIncrementalCompilation_withClasspathSnapshot
- Updated BuildCacheRelocationIT.testKotlinIncrementalCompilation_withClasspathSnapshot
This commit is contained in:
Hung Nguyen
2022-01-30 13:38:26 +03:00
committed by nataliya.valtman
parent 200cc9f75f
commit d018031cbf
16 changed files with 133 additions and 214 deletions
@@ -16,6 +16,7 @@ enum class BuildAttributeKind : Serializable {
} }
enum class BuildAttribute(val kind: BuildAttributeKind, val readableString: String) : Serializable { enum class BuildAttribute(val kind: BuildAttributeKind, val readableString: String) : Serializable {
CACHE_DIRECTORY_NOT_POPULATED(BuildAttributeKind.REBUILD_REASON, "Cache directory not populated"),
NO_BUILD_HISTORY(BuildAttributeKind.REBUILD_REASON, "Build history file not found"), NO_BUILD_HISTORY(BuildAttributeKind.REBUILD_REASON, "Build history file not found"),
NO_ABI_SNAPSHOT(BuildAttributeKind.REBUILD_REASON, "ABI snapshot not found"), NO_ABI_SNAPSHOT(BuildAttributeKind.REBUILD_REASON, "ABI snapshot not found"),
CLASSPATH_SNAPSHOT_NOT_FOUND(BuildAttributeKind.REBUILD_REASON, "Classpath snapshot not found"), CLASSPATH_SNAPSHOT_NOT_FOUND(BuildAttributeKind.REBUILD_REASON, "Classpath snapshot not found"),
@@ -288,13 +288,13 @@ data class LookupSymbol(val name: String, val scope: String) : Comparable<Lookup
} }
/** /**
* Wrapper of a [LookupMap] which tracks changes to the map after the initialization of this [TrackedLookupMap] instance, (unless * Wrapper of a [LookupMap] which tracks changes to the map after the initialization of this [TrackedLookupMap] instance (unless
* [trackChanges] is set to `false`). * [trackChanges] is set to `false`).
*/ */
private class TrackedLookupMap(private val lookupMap: LookupMap, private val trackChanges: Boolean) { private class TrackedLookupMap(private val lookupMap: LookupMap, private val trackChanges: Boolean) {
// Note that there may be multiple operations on the same key, and the following sets contain the *latest* differences with the original // Note that there may be multiple operations on the same key, and the following sets contain the *aggregated* differences with the
// set of keys in the map. For example, if a key is added then removed, or vice versa, it will not be present in either set. // original set of keys in the map. For example, if a key is added then removed, or vice versa, it will not be present in either set.
val addedKeys = if (trackChanges) mutableSetOf<LookupSymbolKey>() else null val addedKeys = if (trackChanges) mutableSetOf<LookupSymbolKey>() else null
val removedKeys = if (trackChanges) mutableSetOf<LookupSymbolKey>() else null val removedKeys = if (trackChanges) mutableSetOf<LookupSymbolKey>() else null
@@ -129,7 +129,15 @@ abstract class IncrementalCompilerRunner<
else -> providedChangedFiles else -> providedChangedFiles
} }
val compilationMode = sourcesToCompile(caches, changedFiles, args, messageCollector, classpathAbiSnapshot) // Check whether the cache directory is populated (note that it may be deleted upon a Gradle build cache hit if the directory is
// marked as @LocalState in the Gradle task).
val cacheDirectoryNotPopulated = cacheDirectory.walk().none { it.isFile }
val compilationMode = if (cacheDirectoryNotPopulated) {
CompilationMode.Rebuild(BuildAttribute.CACHE_DIRECTORY_NOT_POPULATED)
} else {
sourcesToCompile(caches, changedFiles, args, messageCollector, classpathAbiSnapshot)
}
val exitCode = when (compilationMode) { val exitCode = when (compilationMode) {
is CompilationMode.Incremental -> { is CompilationMode.Incremental -> {
@@ -256,7 +256,7 @@ class IncrementalJvmCompilerRunner(
is NotAvailableDueToMissingClasspathSnapshot -> ChangesEither.Unknown(BuildAttribute.CLASSPATH_SNAPSHOT_NOT_FOUND) is NotAvailableDueToMissingClasspathSnapshot -> ChangesEither.Unknown(BuildAttribute.CLASSPATH_SNAPSHOT_NOT_FOUND)
is NotAvailableForNonIncrementalRun -> ChangesEither.Unknown(BuildAttribute.UNKNOWN_CHANGES_IN_GRADLE_INPUTS) is NotAvailableForNonIncrementalRun -> ChangesEither.Unknown(BuildAttribute.UNKNOWN_CHANGES_IN_GRADLE_INPUTS)
is ClasspathSnapshotDisabled -> reporter.measure(BuildTime.IC_ANALYZE_CHANGES_IN_DEPENDENCIES) { is ClasspathSnapshotDisabled -> reporter.measure(BuildTime.IC_ANALYZE_CHANGES_IN_DEPENDENCIES) {
val lastBuildInfo = BuildInfo.read(lastBuildInfoFile) ?: return CompilationMode.Rebuild(BuildAttribute.IC_IS_NOT_ENABLED) val lastBuildInfo = BuildInfo.read(lastBuildInfoFile) ?: return CompilationMode.Rebuild(BuildAttribute.NO_BUILD_HISTORY)
reporter.reportVerbose { "Last Kotlin Build info -- $lastBuildInfo" } reporter.reportVerbose { "Last Kotlin Build info -- $lastBuildInfo" }
val scopes = caches.lookupCache.lookupSymbols.map { it.scope.ifBlank { it.name } }.distinct() val scopes = caches.lookupCache.lookupSymbols.map { it.scope.ifBlank { it.name } }.distinct()
@@ -1030,8 +1030,7 @@ fun getSomething() = 10
.filter { it.contains("[KOTLIN] compile iteration:") } .filter { it.contains("[KOTLIN] compile iteration:") }
.drop(1) .drop(1)
.joinToString(separator = "/n") .joinToString(separator = "/n")
val actualSources = getCompiledKotlinSources(filteredOutput).projectRelativePaths(project) assertCompiledKotlinSources(project.relativize(androidModuleKt), output = filteredOutput)
assertSameFiles(project.relativize(androidModuleKt), actualSources, "Compiled Kotlin files differ:\n ")
} }
} }
@@ -11,26 +11,19 @@ import org.gradle.api.logging.configuration.WarningMode
import org.gradle.tooling.GradleConnector import org.gradle.tooling.GradleConnector
import org.gradle.util.GradleVersion import org.gradle.util.GradleVersion
import org.intellij.lang.annotations.Language import org.intellij.lang.annotations.Language
import org.jetbrains.kotlin.cli.common.CompilerSystemProperties.COMPILE_INCREMENTAL_WITH_ARTIFACT_TRANSFORM
import org.jetbrains.kotlin.gradle.model.ModelContainer import org.jetbrains.kotlin.gradle.model.ModelContainer
import org.jetbrains.kotlin.gradle.model.ModelFetcherBuildAction import org.jetbrains.kotlin.gradle.model.ModelFetcherBuildAction
import org.jetbrains.kotlin.gradle.plugin.KotlinJsCompilerType import org.jetbrains.kotlin.gradle.plugin.KotlinJsCompilerType
import org.jetbrains.kotlin.gradle.report.BuildReportType import org.jetbrains.kotlin.gradle.report.BuildReportType
import org.jetbrains.kotlin.gradle.testbase.applyAndroidTestFixes import org.jetbrains.kotlin.gradle.testbase.*
import org.jetbrains.kotlin.gradle.testbase.enableCacheRedirector
import org.jetbrains.kotlin.gradle.testbase.extractJavaCompiledSources
import org.jetbrains.kotlin.gradle.testbase.extractKotlinCompiledSources
import org.jetbrains.kotlin.gradle.testbase.prettyPrintXml
import org.jetbrains.kotlin.gradle.testbase.readAndCleanupTestResults
import org.jetbrains.kotlin.gradle.testbase.addPluginManagementToSettings
import org.jetbrains.kotlin.gradle.util.* import org.jetbrains.kotlin.gradle.util.*
import org.jetbrains.kotlin.gradle.util.modify import org.jetbrains.kotlin.gradle.util.modify
import org.jetbrains.kotlin.konan.target.HostManager import org.jetbrains.kotlin.konan.target.HostManager
import org.jetbrains.kotlin.konan.target.KonanTarget import org.jetbrains.kotlin.konan.target.KonanTarget
import org.jetbrains.kotlin.test.RunnerWithMuteInDatabase import org.jetbrains.kotlin.test.RunnerWithMuteInDatabase
import org.jetbrains.kotlin.test.util.trimTrailingWhitespaces
import org.junit.After import org.junit.After
import org.junit.AfterClass import org.junit.AfterClass
import org.junit.Assert
import org.junit.Before import org.junit.Before
import org.junit.runner.RunWith import org.junit.runner.RunWith
import java.io.File import java.io.File
@@ -386,14 +379,7 @@ abstract class BaseGradleIT {
} }
} }
class CompiledProject(val project: Project, val output: String, val resultCode: Int) { class CompiledProject(val project: Project, val output: String, val resultCode: Int)
fun getCompiledKotlinSources(output: String) =
extractKotlinCompiledSources(project.projectDir.toPath(), output).map { sourcePath -> sourcePath.toFile() }
val compiledJavaSources: Iterable<File> by lazy {
extractJavaCompiledSources(output).map { sourcePath -> sourcePath.toFile() }
}
}
// Basically the same as `Project.build`, tells gradle to wait for debug on 5005 port // Basically the same as `Project.build`, tells gradle to wait for debug on 5005 port
// Faster to type than `project.build("-Dorg.gradle.debug=true")` or `project.build(options = defaultBuildOptions().copy(debug = true))` // Faster to type than `project.build("-Dorg.gradle.debug=true")` or `project.build(options = defaultBuildOptions().copy(debug = true))`
@@ -593,27 +579,6 @@ abstract class BaseGradleIT {
return map { it.canonicalFile.toRelativeString(project.projectDir) } return map { it.canonicalFile.toRelativeString(project.projectDir) }
} }
fun CompiledProject.assertSameFiles(expected: Iterable<String>, actual: Iterable<String>, messagePrefix: String): CompiledProject {
val expectedSet = expected.map { it.normalizePath() }.toSortedSet().joinToString("\n")
val actualSet = actual.map { it.normalizePath() }.toSortedSet().joinToString("\n")
Assert.assertEquals(messagePrefix, expectedSet, actualSet)
return this
}
fun CompiledProject.assertContainFiles(
expected: Iterable<String>,
actual: Iterable<String>,
messagePrefix: String = ""
): CompiledProject {
val expectedNormalized = expected.map(::normalizePath).toSortedSet()
val actualNormalized = actual.map(::normalizePath).toSortedSet()
assertTrue(
actualNormalized.containsAll(expectedNormalized),
messagePrefix + "expected files: ${expectedNormalized.joinToString()}\n !in actual files: ${actualNormalized.joinToString()}"
)
return this
}
fun CompiledProject.findTasksByPattern(pattern: String): Set<String> { fun CompiledProject.findTasksByPattern(pattern: String): Set<String> {
return "task '($pattern)'".toRegex().findAll(output).mapTo(HashSet()) { it.groupValues[1] } return "task '($pattern)'".toRegex().findAll(output).mapTo(HashSet()) { it.groupValues[1] }
} }
@@ -778,13 +743,14 @@ Finished executing task ':$taskName'|
output: String = this.output, output: String = this.output,
suffix: String = "" suffix: String = ""
): CompiledProject { ): CompiledProject {
val messagePrefix = "Compiled Kotlin files differ${suffix}:\n " val messagePrefix = "Compiled Kotlin files differ${suffix}:\n"
val actualSources = getCompiledKotlinSources(output).projectRelativePaths(this.project) val actualSources = extractCompiledKotlinFiles(output)
return if (weakTesting) { if (weakTesting) {
assertContainFiles(expectedSourcesRelativePaths, actualSources, messagePrefix) assertContainsFiles(expectedSourcesRelativePaths.toPaths(), actualSources, messagePrefix)
} else { } else {
assertSameFiles(expectedSourcesRelativePaths, actualSources, messagePrefix) assertSameFiles(expectedSourcesRelativePaths.toPaths(), actualSources, messagePrefix)
} }
return this
} }
fun CompiledProject.assertCompiledKotlinFiles(expectedFiles: Iterable<File>): CompiledProject = fun CompiledProject.assertCompiledKotlinFiles(expectedFiles: Iterable<File>): CompiledProject =
@@ -799,11 +765,15 @@ Finished executing task ':$taskName'|
fun CompiledProject.assertCompiledJavaSources( fun CompiledProject.assertCompiledJavaSources(
sources: Iterable<String>, sources: Iterable<String>,
weakTesting: Boolean = false weakTesting: Boolean = false
): CompiledProject = ): CompiledProject {
val messagePrefix = "Compiled Java files differ:\n"
val actualSources = extractCompiledJavaFiles(project.projectDir, output)
if (weakTesting) if (weakTesting)
assertContainFiles(sources, compiledJavaSources.projectRelativePaths(this.project), "Compiled Java files differ:\n ") assertContainsFiles(sources.toPaths(), actualSources, messagePrefix)
else else
assertSameFiles(sources, compiledJavaSources.projectRelativePaths(this.project), "Compiled Java files differ:\n ") assertSameFiles(sources.toPaths(), actualSources, messagePrefix)
return this
}
fun Project.resourcesDir(subproject: String? = null, sourceSet: String = "main"): String = fun Project.resourcesDir(subproject: String? = null, sourceSet: String = "main"): String =
(subproject?.plus("/") ?: "") + "build/resources/$sourceSet/" (subproject?.plus("/") ?: "") + "build/resources/$sourceSet/"
@@ -904,7 +874,7 @@ Finished executing task ':$taskName'|
options.incrementalJsKlib?.let { add("-Pkotlin.incremental.js.klib=$it") } options.incrementalJsKlib?.let { add("-Pkotlin.incremental.js.klib=$it") }
options.jsIrBackend?.let { add("-Pkotlin.js.useIrBackend=$it") } options.jsIrBackend?.let { add("-Pkotlin.js.useIrBackend=$it") }
options.usePreciseJavaTracking?.let { add("-Pkotlin.incremental.usePreciseJavaTracking=$it") } options.usePreciseJavaTracking?.let { add("-Pkotlin.incremental.usePreciseJavaTracking=$it") }
options.useClasspathSnapshot?.let { add("-Pkotlin.incremental.useClasspathSnapshot=$it") } options.useClasspathSnapshot?.let { add("-P${COMPILE_INCREMENTAL_WITH_ARTIFACT_TRANSFORM.property}=$it") }
options.androidGradlePluginVersion?.let { add("-Pandroid_tools_version=$it") } options.androidGradlePluginVersion?.let { add("-Pandroid_tools_version=$it") }
if (options.debug) { if (options.debug) {
add("-Dorg.gradle.debug=true") add("-Dorg.gradle.debug=true")
@@ -103,33 +103,6 @@ class BuildCacheIT : KGPBaseTest() {
} }
} }
@DisplayName("Incremental compilation works with cache")
@GradleTest
fun testKotlinCompileIncrementalBuildWithoutRelocation(gradleVersion: GradleVersion) {
project("buildCacheSimple", gradleVersion) {
enableLocalBuildCache(localBuildCacheDir)
build("assemble") {
assertTasksPackedToCache(":compileKotlin")
}
build("clean", "assemble") {
assertTasksFromCache(":compileKotlin")
}
val fooKtSourceFile = kotlinSourcesDir().resolve("foo.kt")
fooKtSourceFile.modify { it.replace("Int = 1", "String = \"abc\"") }
build("assemble") {
assertIncrementalCompilation(modifiedFiles = setOf(fooKtSourceFile))
}
fooKtSourceFile.modify { it.replace("String = \"abc\"", "Int = 1") }
build("clean", "assemble") {
assertTasksFromCache(":compileKotlin")
}
}
}
@DisplayName("Debug log level should not break build cache") @DisplayName("Debug log level should not break build cache")
@GradleTest @GradleTest
fun testDebugLogLevelCaching(gradleVersion: GradleVersion) { fun testDebugLogLevelCaching(gradleVersion: GradleVersion) {
@@ -16,11 +16,13 @@
package org.jetbrains.kotlin.gradle package org.jetbrains.kotlin.gradle
import org.gradle.api.logging.LogLevel
import org.gradle.api.logging.configuration.WarningMode import org.gradle.api.logging.configuration.WarningMode
import org.gradle.util.GradleVersion import org.gradle.util.GradleVersion
import org.jetbrains.kotlin.gradle.testbase.* import org.jetbrains.kotlin.gradle.testbase.*
import org.junit.jupiter.api.DisplayName import org.junit.jupiter.api.DisplayName
import kotlin.io.path.createDirectory import kotlin.io.path.createDirectory
import kotlin.io.path.pathString
@DisplayName("Build cache relocation") @DisplayName("Build cache relocation")
@SimpleGradlePluginTests @SimpleGradlePluginTests
@@ -260,20 +262,6 @@ class BuildCacheRelocationIT : KGPBaseTest() {
) )
} }
@DisplayName("Incremental compilation build cache does not break relocated cache")
@GradleTest
fun testKotlinCompileCachingIncrementalBuildWithRelocation(gradleVersion: GradleVersion) {
val firstProject = project("buildCacheSimple", gradleVersion) {
enableLocalBuildCache(localBuildCacheDir)
}
val secondProject = project("buildCacheSimple", gradleVersion) {
enableLocalBuildCache(localBuildCacheDir)
}
checkKotlinCompileCachingIncrementalBuild(firstProject, secondProject)
}
@DisplayName("Kapt incremental compilation works with cache") @DisplayName("Kapt incremental compilation works with cache")
@GradleTest @GradleTest
fun testKaptCachingIncrementalBuildWithoutRelocation(gradleVersion: GradleVersion) { fun testKaptCachingIncrementalBuildWithoutRelocation(gradleVersion: GradleVersion) {
@@ -332,10 +320,22 @@ class BuildCacheRelocationIT : KGPBaseTest() {
} }
} }
private fun checkKotlinCompileCachingIncrementalBuild( @DisplayName("Kotlin incremental compilation should work correctly")
firstProject: TestProject, @GradleTest
secondProject: TestProject fun testKotlinIncrementalCompilation(gradleVersion: GradleVersion) {
) { checkKotlinIncrementalCompilation(gradleVersion)
}
@DisplayName("Kotlin incremental compilation with `kotlin.incremental.useClasspathSnapshot` feature should work correctly")
@GradleTest
fun testKotlinIncrementalCompilation_withClasspathSnapshot(gradleVersion: GradleVersion) {
checkKotlinIncrementalCompilation(gradleVersion, useClasspathSnapshot = true)
}
private fun checkKotlinIncrementalCompilation(gradleVersion: GradleVersion, useClasspathSnapshot: Boolean? = null) {
val buildOptions = defaultBuildOptions.copy(useClasspathSnapshot = useClasspathSnapshot)
val (firstProject, secondProject) = prepareTestProjects("buildCacheSimple", gradleVersion, buildOptions)
// First build, should be stored into the build cache: // First build, should be stored into the build cache:
firstProject.build("assemble") { firstProject.build("assemble") {
assertTasksPackedToCache(":compileKotlin") assertTasksPackedToCache(":compileKotlin")
@@ -348,9 +348,12 @@ class BuildCacheRelocationIT : KGPBaseTest() {
// Change the return type of foo() from Int to String in foo.kt, and check that fooUsage.kt is recompiled as well: // Change the return type of foo() from Int to String in foo.kt, and check that fooUsage.kt is recompiled as well:
val fooKtSourceFile = secondProject.kotlinSourcesDir().resolve("foo.kt") val fooKtSourceFile = secondProject.kotlinSourcesDir().resolve("foo.kt")
val fooUsageKtSourceFile = secondProject.kotlinSourcesDir().resolve("fooUsage.kt")
fooKtSourceFile.modify { it.replace("Int = 1", "String = \"abc\"") } fooKtSourceFile.modify { it.replace("Int = 1", "String = \"abc\"") }
secondProject.build("assemble") { secondProject.build("assemble", buildOptions = buildOptions.copy(logLevel = LogLevel.DEBUG)) {
assertIncrementalCompilation(modifiedFiles = setOf(fooKtSourceFile)) assertIncrementalCompilation(
listOf(fooKtSourceFile, fooUsageKtSourceFile).relativizeTo(secondProject.projectPath).map { it.pathString }
)
} }
// Revert the change to the return type of foo(), and check if we get a cache hit // Revert the change to the return type of foo(), and check if we get a cache hit
@@ -8,6 +8,7 @@ package org.jetbrains.kotlin.gradle.testbase
import org.gradle.api.logging.LogLevel import org.gradle.api.logging.LogLevel
import org.gradle.api.logging.configuration.WarningMode import org.gradle.api.logging.configuration.WarningMode
import org.gradle.util.GradleVersion import org.gradle.util.GradleVersion
import org.jetbrains.kotlin.cli.common.CompilerSystemProperties.COMPILE_INCREMENTAL_WITH_ARTIFACT_TRANSFORM
import org.jetbrains.kotlin.gradle.BaseGradleIT import org.jetbrains.kotlin.gradle.BaseGradleIT
import org.jetbrains.kotlin.gradle.plugin.KotlinJsCompilerType import org.jetbrains.kotlin.gradle.plugin.KotlinJsCompilerType
import org.jetbrains.kotlin.gradle.report.BuildReportType import org.jetbrains.kotlin.gradle.report.BuildReportType
@@ -21,6 +22,7 @@ data class BuildOptions(
val configurationCacheProblems: BaseGradleIT.ConfigurationCacheProblems = BaseGradleIT.ConfigurationCacheProblems.FAIL, val configurationCacheProblems: BaseGradleIT.ConfigurationCacheProblems = BaseGradleIT.ConfigurationCacheProblems.FAIL,
val parallel: Boolean = true, val parallel: Boolean = true,
val incremental: Boolean? = null, val incremental: Boolean? = null,
val useClasspathSnapshot: Boolean? = null,
val maxWorkers: Int = (Runtime.getRuntime().availableProcessors() / 4 - 1).coerceAtLeast(2), val maxWorkers: Int = (Runtime.getRuntime().availableProcessors() / 4 - 1).coerceAtLeast(2),
val fileSystemWatchEnabled: Boolean = false, val fileSystemWatchEnabled: Boolean = false,
val buildCacheEnabled: Boolean = false, val buildCacheEnabled: Boolean = false,
@@ -81,6 +83,8 @@ data class BuildOptions(
arguments.add("-Pkotlin.incremental=$incremental") arguments.add("-Pkotlin.incremental=$incremental")
} }
useClasspathSnapshot?.let { arguments.add("-P${COMPILE_INCREMENTAL_WITH_ARTIFACT_TRANSFORM.property}=$it") }
if (gradleVersion >= GradleVersion.version("6.5")) { if (gradleVersion >= GradleVersion.version("6.5")) {
if (fileSystemWatchEnabled) { if (fileSystemWatchEnabled) {
arguments.add("--watch-fs") arguments.add("--watch-fs")
@@ -5,14 +5,16 @@
package org.jetbrains.kotlin.gradle.testbase package org.jetbrains.kotlin.gradle.testbase
import java.nio.file.Path
import java.nio.file.Paths
import kotlin.io.path.relativeTo
import org.gradle.api.logging.LogLevel import org.gradle.api.logging.LogLevel
import org.gradle.testkit.runner.BuildResult import org.gradle.testkit.runner.BuildResult
import org.intellij.lang.annotations.Language import org.intellij.lang.annotations.Language
import java.io.File
import java.nio.file.Path
import java.nio.file.Paths
private val kotlinSrcRegex by lazy { Regex("\\[KOTLIN\\] compile iteration: ([^\\r\\n]*)") } private val kotlinSrcRegex by lazy { Regex("\\[KOTLIN] compile iteration: ([^\\r\\n]*)") }
private val javaSrcRegex by lazy { Regex("\\[DEBUG] \\[[^]]*JavaCompiler] Compiler arguments: ([^\\r\\n]*)") }
@Language("RegExp") @Language("RegExp")
private fun taskOutputRegex( private fun taskOutputRegex(
@@ -42,45 +44,56 @@ fun BuildResult.getOutputForTask(taskName: String): String = taskOutputRegex(tas
?: error("Could not find output for task $taskName") ?: error("Could not find output for task $taskName")
/** /**
* Extracts list of compiled .kt files from task output * Extracts the list of compiled .kt files from the build output.
* *
* Note: log level of output should be set to [LogLevel.DEBUG] * The returned paths are relative to the project directory.
*
* Note: Log level of output must be set to [LogLevel.DEBUG].
*/ */
fun extractKotlinCompiledSources(projectDirectory: Path, output: String) = fun extractCompiledKotlinFiles(output: String): List<Path> {
kotlinSrcRegex.findAll(output) return kotlinSrcRegex.findAll(output).asIterable()
.asIterable() .flatMap { matchResult -> matchResult.groups[1]!!.value.split(", ") }
.flatMap { result -> .toPaths()
result.groups[1]!!.value.split(", ") }
.map { source -> projectDirectory.resolve(source).normalize() }
}
private val javaSrcRegex by lazy { Regex("\\[DEBUG\\] \\[[^\\]]*JavaCompiler\\] Compiler arguments: ([^\\r\\n]*)") }
/** /**
* Extracts list of compiled .java files from task output * Extracts the list of compiled .java files from the build output.
* *
* Note: log level of output should be set to [LogLevel.DEBUG] * The returned paths are relative to the project directory.
*
* Note: Log level of output must be set to [LogLevel.DEBUG].
*/ */
fun extractJavaCompiledSources(output: String): List<Path> = fun extractCompiledJavaFiles(projectDir: File, output: String): List<Path> {
javaSrcRegex.findAll(output).asIterable().flatMap { return javaSrcRegex.findAll(output).asIterable()
it.groups[1]!!.value .flatMap { matchResult -> matchResult.groups[1]!!.value.split(" ") }
.split(" ") .filter { filePath -> filePath.endsWith(".java", ignoreCase = true) }
.filter { source -> source.endsWith(".java", ignoreCase = true) } .map { javaFilePath -> projectDir.toPath().relativize(Paths.get(javaFilePath)) }
.map { source -> Paths.get(source).normalize() } }
}
/** /**
* Asserts all the .kt files from [expectedSources] and only they are compiled * Asserts all the .kt files from [expectedSources] and only they are compiled
* *
* Note: log level of output should be set to [LogLevel.DEBUG] * Note: log level of output should be set to [LogLevel.DEBUG]
*/ */
fun GradleProject.assertCompiledKotlinSources( fun assertCompiledKotlinSources(
expectedSources: Iterable<Path>, expectedSources: Iterable<Path>,
output: String, output: String,
errorMessageSuffix: String = "" errorMessageSuffix: String = ""
) { ) {
val actualSources = extractKotlinCompiledSources(projectPath, output).map { val actualSources = extractCompiledKotlinFiles(output)
it.relativeTo(projectPath)
}
assertSameFiles(expectedSources, actualSources, "Compiled Kotlin files differ${errorMessageSuffix}:\n") assertSameFiles(expectedSources, actualSources, "Compiled Kotlin files differ${errorMessageSuffix}:\n")
} }
/**
* Asserts that compilation was incremental and the set of compiled .kt files exactly match [expectedCompiledKotlinFiles].
*
* [expectedCompiledKotlinFiles] are relative to the project directory.
*
* Note: Log level of output must be set to [LogLevel.DEBUG].
*/
fun BuildResult.assertIncrementalCompilation(expectedCompiledKotlinFiles: Iterable<String>) {
assertOutputDoesNotContain("Non-incremental compilation will be performed")
val actualCompiledKotlinFiles = extractCompiledKotlinFiles(output)
assertSameFiles(expectedCompiledKotlinFiles.toPaths(), actualCompiledKotlinFiles, "Compiled Kotlin files differ:\n")
}
@@ -55,7 +55,7 @@ fun TestProject.assertSimpleConfigurationCacheScenarioWorks(
*/ */
@OptIn(ExperimentalPathApi::class) @OptIn(ExperimentalPathApi::class)
private fun copyReportToTempDir(htmlReportFile: Path): Path = private fun copyReportToTempDir(htmlReportFile: Path): Path =
createTempDir("report").let { tempDir -> createTempDirDeleteOnExit("report").let { tempDir ->
htmlReportFile.parent.toFile().copyRecursively(tempDir.toFile()) htmlReportFile.parent.toFile().copyRecursively(tempDir.toFile())
tempDir.resolve(htmlReportFile.name) tempDir.resolve(htmlReportFile.name)
} }
@@ -10,7 +10,7 @@ import java.nio.file.Path
import kotlin.io.path.exists import kotlin.io.path.exists
import kotlin.io.path.isDirectory import kotlin.io.path.isDirectory
import kotlin.io.path.readText import kotlin.io.path.readText
import kotlin.test.assertEquals import kotlin.test.asserter
/** /**
* Asserts file under [file] path exists and is a regular file. * Asserts file under [file] path exists and is a regular file.
@@ -189,8 +189,24 @@ fun GradleProject.assertFileDoesNotContain(
} }
} }
fun GradleProject.assertSameFiles(expected: Iterable<Path>, actual: Iterable<Path>, messagePrefix: String) { fun assertSameFiles(expected: Iterable<Path>, actual: Iterable<Path>, messagePrefix: String) {
val expectedSet = expected.map { it.toString().normalizePath() }.toSortedSet().joinToString("\n") val expectedSet = expected.map { it.toString().normalizePath() }.toSet()
val actualSet = actual.map { it.toString().normalizePath() }.toSortedSet().joinToString("\n") val actualSet = actual.map { it.toString().normalizePath() }.toSet()
assertEquals(expectedSet, actualSet, messagePrefix) asserter.assertTrue(lazyMessage = {
messagePrefix +
"Actual set does not exactly match expected set.\n" +
"Expected set: ${expectedSet.sorted().joinToString(", ")}\n" +
"Actual set: ${actualSet.sorted().joinToString(", ")}\n"
}, actualSet.size == expectedSet.size && actualSet.containsAll(expectedSet))
}
fun assertContainsFiles(expected: Iterable<Path>, actual: Iterable<Path>, messagePrefix: String) {
val expectedSet = expected.map { it.toString().normalizePath() }.toSet()
val actualSet = actual.map { it.toString().normalizePath() }.toSet()
asserter.assertTrue(lazyMessage = {
messagePrefix +
"Actual set does not contain all of expected set.\n" +
"Expected set: ${expectedSet.sorted().joinToString(", ")}\n" +
"Actual set: ${actualSet.sorted().joinToString(", ")}\n"
}, actualSet.containsAll(expectedSet))
} }
@@ -6,7 +6,6 @@
package org.jetbrains.kotlin.gradle.testbase package org.jetbrains.kotlin.gradle.testbase
import org.gradle.testkit.runner.BuildResult import org.gradle.testkit.runner.BuildResult
import java.nio.file.Path
/** /**
* Asserts Gradle output contains [expectedSubString] string. * Asserts Gradle output contains [expectedSubString] string.
@@ -127,70 +126,6 @@ fun BuildResult.assertNoBuildWarnings(
} }
} }
fun BuildResult.assertIncrementalCompilation(
modifiedFiles: Set<Path> = emptySet(),
deletedFiles: Set<Path> = emptySet()
) {
val incrementalCompilationOptions = output
.lineSequence()
.filter { it.trim().startsWith("Options for KOTLIN DAEMON: IncrementalCompilationOptions(") }
.map {
it
.removePrefix("Options for KOTLIN DAEMON: IncrementalCompilationOptions(")
.removeSuffix(")")
}
.toList()
assert(incrementalCompilationOptions.isNotEmpty()) {
printBuildOutput()
"No incremental compilation options were found in the build"
}
val modifiedFilesPath = modifiedFiles.map { it.toAbsolutePath().toString() }
val deletedFilesPath = deletedFiles.map { it.toAbsolutePath().toString() }
val hasMatch = incrementalCompilationOptions
.firstOrNull {
val optionModifiedFiles = it
.substringAfter("modifiedFiles=[")
.substringBefore("]")
.split(",")
.filter(String::isNotEmpty)
val modifiedFilesFound = if (modifiedFilesPath.isEmpty()) {
optionModifiedFiles.isEmpty()
} else {
modifiedFilesPath.subtract(optionModifiedFiles).isEmpty()
}
val optionDeletedFiles = it
.substringAfter("deletedFiles=[")
.substringBefore("]")
.split(",")
.filter(String::isNotEmpty)
val deletedFilesFound = if (deletedFilesPath.isEmpty()) {
optionDeletedFiles.isEmpty()
} else {
deletedFilesPath.subtract(optionDeletedFiles).isEmpty()
}
modifiedFilesFound && deletedFilesFound
} != null
assert(hasMatch) {
printBuildOutput()
"""
|Expected incremental compilation options with:
|- modified files: ${modifiedFilesPath.joinToString()}
|- deleted files: ${deletedFilesPath.joinToString()}
|
|but none of following compilation options match:
|${incrementalCompilationOptions.joinToString(separator = "\n")}
""".trimMargin()
}
}
/** /**
* Asserts compilation is running via Kotlin daemon with given jvm arguments. * Asserts compilation is running via Kotlin daemon with given jvm arguments.
*/ */
@@ -6,10 +6,7 @@
package org.jetbrains.kotlin.gradle.testbase package org.jetbrains.kotlin.gradle.testbase
import java.io.IOException import java.io.IOException
import java.nio.file.FileVisitResult import java.nio.file.*
import java.nio.file.Files
import java.nio.file.Path
import java.nio.file.SimpleFileVisitor
import java.nio.file.attribute.BasicFileAttributes import java.nio.file.attribute.BasicFileAttributes
import kotlin.io.path.* import kotlin.io.path.*
import kotlin.streams.asSequence import kotlin.streams.asSequence
@@ -50,7 +47,7 @@ internal val Path.allJavaSources: List<Path>
* *
* Prefer using JUnit5 `@TempDir` over this method when possible. * Prefer using JUnit5 `@TempDir` over this method when possible.
*/ */
internal fun createTempDir(prefix: String): Path = Files internal fun createTempDirDeleteOnExit(prefix: String): Path = Files
.createTempDirectory(prefix) .createTempDirectory(prefix)
.apply { toFile().deleteOnExit() } .apply { toFile().deleteOnExit() }
@@ -108,3 +105,5 @@ internal fun Path.deleteRecursively() {
} }
}) })
} }
internal fun Iterable<String>.toPaths(): List<Path> = map { Paths.get(it) }
@@ -5,10 +5,8 @@
package org.jetbrains.kotlin.gradle.util package org.jetbrains.kotlin.gradle.util
import org.jetbrains.kotlin.gradle.testbase.createTempDirDeleteOnExit
import java.io.File import java.io.File
import java.nio.file.Files
import java.nio.file.Path
import kotlin.io.path.relativeTo
fun File.getFileByName(name: String): File = fun File.getFileByName(name: String): File =
findFileByName(name) ?: throw AssertionError("Could not find file with name '$name' in $this") findFileByName(name) ?: throw AssertionError("Could not find file with name '$name' in $this")
@@ -36,10 +34,7 @@ fun File.addNewLine() {
modify { "$it\n" } modify { "$it\n" }
} }
fun createTempDir(prefix: String): File = fun createTempDir(prefix: String): File = createTempDirDeleteOnExit(prefix).toFile()
Files.createTempDirectory(prefix).toFile().apply {
deleteOnExit()
}
/** /**
* converts back slashes to forward slashes * converts back slashes to forward slashes
@@ -0,0 +1,3 @@
// This class is used so that the set of compiled source files in an incremental compilation is a proper subset of the set of compiled
// source files in a non-incremental compilation, so that the two cases are not ambiguous when we check the set of compiled source files.
class StandAloneClass