[BTA tests] Implement basic infrastructure for compilation tests

^KT-61860 In Progress
This commit is contained in:
Alexander.Likhachev
2023-11-10 00:50:25 +01:00
committed by Space Team
parent 90fee75eba
commit c952d74f7c
9 changed files with 301 additions and 0 deletions
@@ -15,6 +15,8 @@ dependencies {
kotlin {
compilerOptions {
optIn.add("org.jetbrains.kotlin.buildtools.api.ExperimentalBuildToolsApi")
optIn.add("kotlin.ExperimentalStdlibApi")
optIn.add("kotlin.io.path.ExperimentalPathApi")
}
}
@@ -0,0 +1,39 @@
/*
* Copyright 2010-2023 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.buildtools.api.tests.compilation.model
import java.nio.file.Path
abstract class AbstractModule(
val project: Project,
override val moduleName: String,
val moduleDirectory: Path,
val dependencies: List<Dependency>,
override val additionalCompilationArguments: List<String> = emptyList(),
) : Module {
override val sourcesDirectory: Path
get() = moduleDirectory.resolve("src")
override val buildDirectory: Path
get() = moduleDirectory.resolve("build")
override val outputDirectory: Path
get() = buildDirectory.resolve("output")
override val location: Path
get() = outputDirectory
override val icWorkingDir: Path
get() = buildDirectory.resolve("ic")
override val snapshotFile: Path
get() = icWorkingDir.resolve("$moduleName.snapshot")
override val icCachesDir: Path
get() = icWorkingDir.resolve("caches")
override fun toString() = moduleName
}
@@ -0,0 +1,14 @@
/*
* Copyright 2010-2023 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.buildtools.api.tests.compilation.model
import org.junit.jupiter.api.io.TempDir
import java.nio.file.Path
abstract class BaseCompilationTest {
@TempDir
lateinit var workingDirectory: Path
}
@@ -0,0 +1,18 @@
/*
* Copyright 2010-2023 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.buildtools.api.tests.compilation.model
import org.junit.jupiter.params.ParameterizedTest
import org.junit.jupiter.params.provider.ArgumentsSource
@Target(AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY_GETTER, AnnotationTarget.PROPERTY_SETTER)
@Retention(AnnotationRetention.RUNTIME)
@ParameterizedTest(name = "{0}: {displayName}")
@ArgumentsSource(
DefaultStrategyAgnosticCompilationTestArgumentProvider::class
)
annotation class DefaultStrategyAgnosticCompilationTest
@@ -0,0 +1,25 @@
/*
* Copyright 2010-2023 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.buildtools.api.tests.compilation.model
import org.jetbrains.kotlin.buildtools.api.CompilationService
import org.junit.jupiter.api.Named.named
import org.junit.jupiter.api.extension.ExtensionContext
import org.junit.jupiter.params.provider.Arguments
import org.junit.jupiter.params.provider.ArgumentsProvider
import java.util.stream.Stream
import kotlin.streams.asStream
class DefaultStrategyAgnosticCompilationTestArgumentProvider : ArgumentsProvider {
private val compilationService = CompilationService.loadImplementation(this.javaClass.classLoader)
override fun provideArguments(context: ExtensionContext): Stream<out Arguments> {
return sequenceOf(
named("in-process", compilationService.makeCompilerExecutionStrategyConfiguration().useInProcessStrategy()),
named("within daemon", compilationService.makeCompilerExecutionStrategyConfiguration().useDaemonStrategy(emptyList())),
).map { Arguments.of(it) }.asStream()
}
}
@@ -0,0 +1,13 @@
/*
* Copyright 2010-2023 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.buildtools.api.tests.compilation.model
import java.nio.file.Path
interface Dependency {
val location: Path
val snapshotFile: Path
}
@@ -0,0 +1,116 @@
/*
* Copyright 2010-2023 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.buildtools.api.tests.compilation.model
import org.jetbrains.kotlin.buildtools.api.CompilationResult
import org.jetbrains.kotlin.buildtools.api.CompilationService
import org.jetbrains.kotlin.buildtools.api.CompilerExecutionStrategyConfiguration
import org.jetbrains.kotlin.buildtools.api.SourcesChanges
import org.jetbrains.kotlin.buildtools.api.jvm.AccessibleClassSnapshot
import org.jetbrains.kotlin.buildtools.api.jvm.ClassSnapshotGranularity
import org.jetbrains.kotlin.buildtools.api.jvm.ClasspathSnapshotBasedIncrementalCompilationApproachParameters
import org.jetbrains.kotlin.buildtools.api.jvm.JvmCompilationConfiguration
import org.jetbrains.kotlin.buildtools.api.tests.buildToolsVersion
import org.jetbrains.kotlin.tooling.core.KotlinToolingVersion
import java.io.File
import java.nio.file.Path
import kotlin.io.path.absolutePathString
import kotlin.io.path.createParentDirectories
import kotlin.io.path.listDirectoryEntries
import kotlin.io.path.toPath
class JvmModule(
project: Project,
moduleName: String,
moduleDirectory: Path,
dependencies: List<Dependency>,
additionalCompilationArguments: List<String> = emptyList(),
) : AbstractModule(
project,
moduleName,
moduleDirectory,
dependencies,
additionalCompilationArguments,
) {
private val compilationService = CompilationService.loadImplementation(this.javaClass.classLoader)
override fun compile(
strategyConfig: CompilerExecutionStrategyConfiguration,
compilationConfigAction: (JvmCompilationConfiguration) -> Unit,
): CompilationResult {
val stdlibLocation =
KotlinVersion::class.java.protectionDomain.codeSource.location.toURI().toPath() // compile against the provided stdlib
val dependencyFiles = dependencies.map { it.location }.plusElement(stdlibLocation)
val compilationConfig = compilationService.makeJvmCompilationConfiguration()
compilationConfigAction(compilationConfig)
val defaultCompilationArguments = listOf(
"-no-reflect",
"-no-stdlib",
"-d", outputDirectory.absolutePathString(),
"-cp", dependencyFiles.joinToString(File.pathSeparator),
"-module-name", moduleName,
)
return compilationService.compileJvm(
project.projectId,
strategyConfig,
compilationConfig,
sourcesDirectory.listDirectoryEntries().map { it.toFile() },
defaultCompilationArguments + additionalCompilationArguments,
)
}
private fun generateClasspathSnapshot(dependency: Dependency): Path {
val snapshot =
compilationService.calculateClasspathSnapshot(dependency.location.toFile(), ClassSnapshotGranularity.CLASS_MEMBER_LEVEL)
val hash = snapshot.classSnapshots.values
.filterIsInstance<AccessibleClassSnapshot>()
.withIndex()
.sumOf { (index, snapshot) -> index * 31 + snapshot.classAbiHash }
// see details in docs for `CachedClasspathSnapshotSerializer` for details why we can't use a fixed name
val snapshotFile = icWorkingDir.resolve("dep-$hash.snapshot")
snapshotFile.createParentDirectories()
snapshot.saveSnapshot(snapshotFile.toFile())
return snapshotFile
}
override fun compileIncrementally(
strategyConfig: CompilerExecutionStrategyConfiguration,
sourcesChanges: SourcesChanges,
forceNonIncrementalCompilation: Boolean,
compilationConfigAction: (JvmCompilationConfiguration) -> Unit,
): CompilationResult {
return compile(strategyConfig) { compilationConfig ->
val snapshots = dependencies.map {
generateClasspathSnapshot(it).toFile()
}
val shrunkClasspathSnapshotFile = icWorkingDir.resolve("shrunk-classpath-snapshot.bin")
val params = ClasspathSnapshotBasedIncrementalCompilationApproachParameters(
snapshots,
shrunkClasspathSnapshotFile.toFile()
)
val options = compilationConfig.makeClasspathSnapshotBasedIncrementalCompilationConfiguration()
options.setBuildDir(buildDirectory.toFile())
options.setRootProjectDir(project.projectDirectory.toFile())
if (buildToolsVersion < KotlinToolingVersion(2, 0, 0, "Beta2")) {
// workaround for the incorrect default value
options.useOutputDirs(setOf(icCachesDir.toFile(), outputDirectory.toFile()))
}
if (forceNonIncrementalCompilation) {
options.forceNonIncrementalMode()
}
compilationConfig.useIncrementalCompilation(
icCachesDir.toFile(),
sourcesChanges,
params,
options,
)
}
}
}
@@ -0,0 +1,34 @@
/*
* Copyright 2010-2023 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.buildtools.api.tests.compilation.model
import org.jetbrains.kotlin.buildtools.api.CompilationResult
import org.jetbrains.kotlin.buildtools.api.CompilerExecutionStrategyConfiguration
import org.jetbrains.kotlin.buildtools.api.SourcesChanges
import org.jetbrains.kotlin.buildtools.api.jvm.JvmCompilationConfiguration
import java.nio.file.Path
interface Module : Dependency {
val moduleName: String
val additionalCompilationArguments: List<String>
val sourcesDirectory: Path
val buildDirectory: Path
val outputDirectory: Path
val icWorkingDir: Path
val icCachesDir: Path
fun compile(
strategyConfig: CompilerExecutionStrategyConfiguration,
compilationConfigAction: (JvmCompilationConfiguration) -> Unit = {},
): CompilationResult
fun compileIncrementally(
strategyConfig: CompilerExecutionStrategyConfiguration,
sourcesChanges: SourcesChanges,
forceNonIncrementalCompilation: Boolean = false,
compilationConfigAction: (JvmCompilationConfiguration) -> Unit = {},
): CompilationResult
}
@@ -0,0 +1,40 @@
/*
* Copyright 2010-2023 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.buildtools.api.tests.compilation.model
import org.jetbrains.kotlin.buildtools.api.ProjectId
import java.nio.file.Path
import java.nio.file.Paths
import java.util.*
import kotlin.io.path.copyToRecursively
import kotlin.io.path.createDirectories
import kotlin.io.path.isDirectory
class Project(
val projectDirectory: Path,
) {
val projectId = ProjectId.ProjectUUID(UUID.randomUUID())
fun module(
moduleName: String,
dependencies: List<Module> = emptyList(),
additionalCompilationArguments: List<String> = emptyList(),
): Module {
val moduleDirectory = projectDirectory.resolve(moduleName)
val module = JvmModule(this, moduleName, moduleDirectory, dependencies, additionalCompilationArguments)
module.sourcesDirectory.createDirectories()
val templatePath = Paths.get("src/testCommon/resources/modules/$moduleName")
assert(templatePath.isDirectory()) {
"Template for $moduleName not found. Expected template directory path is $templatePath"
}
templatePath.copyToRecursively(module.sourcesDirectory, followLinks = false)
return module
}
}
fun BaseCompilationTest.project(action: Project.() -> Unit) {
Project(workingDirectory).action()
}