Implementing test infrastructure for chcking incremental builds and for importing and executing jps-lugin tests, adding several tests manually
KT-8487
This commit is contained in:
committed by
Alexey Tsvetkov
parent
cde89cb1e8
commit
7b54751092
+9
@@ -0,0 +1,9 @@
|
|||||||
|
package org.jetbrains.kotlin.gradle.plugin
|
||||||
|
|
||||||
|
import org.gradle.api.Task
|
||||||
|
import org.gradle.api.tasks.compile.AbstractCompile
|
||||||
|
|
||||||
|
abstract class AbstractCompileWithDependenciesTracking : AbstractCompile() {
|
||||||
|
open fun isDependentTaskOutOfDate(task: Task): Boolean = false
|
||||||
|
}
|
||||||
|
|
||||||
+38
-2
@@ -83,7 +83,7 @@ abstract class BaseGradleIT {
|
|||||||
|
|
||||||
open inner class Project(val projectName: String, val wrapperVersion: String = "1.4", val minLogLevel: LogLevel = LogLevel.DEBUG) {
|
open inner class Project(val projectName: String, val wrapperVersion: String = "1.4", val minLogLevel: LogLevel = LogLevel.DEBUG) {
|
||||||
open val resourcesRoot = File(resourcesRootFile, "testProject/$projectName")
|
open val resourcesRoot = File(resourcesRootFile, "testProject/$projectName")
|
||||||
val projectDir = File(workingDir, projectName)
|
val projectDir = File(workingDir.canonicalFile, projectName)
|
||||||
|
|
||||||
open fun setupWorkingDir() {
|
open fun setupWorkingDir() {
|
||||||
copyRecursively(this.resourcesRoot, workingDir)
|
copyRecursively(this.resourcesRoot, workingDir)
|
||||||
@@ -91,7 +91,14 @@ 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) {
|
||||||
|
companion object {
|
||||||
|
val kotlinSourcesListRegex = Regex("\\[KOTLIN\\] compile iteration: ([^\\r\\n]*)")
|
||||||
|
val javaSourcesListRegex = Regex("\\[DEBUG\\] \\[[^\\]]*JavaCompiler\\] Compiler arguments: ([^\\r\\n]*)")
|
||||||
|
}
|
||||||
|
val compiledKotlinSources : Iterable<File> by lazy { kotlinSourcesListRegex.findAll(output).asIterable().flatMap { it.groups[1]!!.value.split(", ").map { File(project.projectDir, it).canonicalFile } } }
|
||||||
|
val compiledJavaSources : Iterable<File> by lazy { javaSourcesListRegex.findAll(output).asIterable().flatMap { it.groups[1]!!.value.split(" ").filter { it.endsWith(".java", ignoreCase = true) }.map { File(it).canonicalFile } } }
|
||||||
|
}
|
||||||
|
|
||||||
fun Project.build(vararg tasks: String, options: BuildOptions = defaultBuildOptions(), check: CompiledProject.() -> Unit) {
|
fun Project.build(vararg tasks: String, options: BuildOptions = defaultBuildOptions(), check: CompiledProject.() -> Unit) {
|
||||||
val cmd = createBuildCommand(tasks, options)
|
val cmd = createBuildCommand(tasks, options)
|
||||||
@@ -173,6 +180,35 @@ abstract class BaseGradleIT {
|
|||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun Iterable<File>.projectRelativePaths(project: Project): Iterable<String> {
|
||||||
|
// val projectDir = File(workingDir.canonicalFile, project.projectName)
|
||||||
|
return map { it.canonicalFile.toRelativeString(project.projectDir) }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun CompiledProject.assertSameFiles(expected: Iterable<String>, actual: Iterable<String>, messagePrefix: String = ""): CompiledProject {
|
||||||
|
val expectedSet = expected.toSortedSet()
|
||||||
|
val actualSet = actual.toSortedSet()
|
||||||
|
assertTrue(actualSet == expectedSet, messagePrefix + "expected files: ${expectedSet.joinToString()}\n actual files: ${actualSet.joinToString()}")
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
fun CompiledProject.assertContainFiles(expected: Iterable<String>, actual: Iterable<String>, messagePrefix: String = ""): CompiledProject {
|
||||||
|
val expectedSet = expected.toSortedSet()
|
||||||
|
assertTrue(expectedSet.containsAll(actual.toList()), messagePrefix + "expected files: ${expectedSet.joinToString()}\n actual files: ${actual.toSortedSet().joinToString()}")
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
fun CompiledProject.assertCompiledKotlinSources(sources: Iterable<String>): CompiledProject = assertSameFiles(sources, compiledKotlinSources.projectRelativePaths(this.project), "Compiled Kotlin files differ:\n ")
|
||||||
|
fun CompiledProject.assertCompiledKotlinSources(vararg sources: String): CompiledProject = assertCompiledKotlinSources(sources.asIterable())
|
||||||
|
|
||||||
|
fun CompiledProject.assertCompiledKotlinSourcesContain(vararg sources: String): CompiledProject = assertContainFiles(sources.asIterable(), compiledKotlinSources.projectRelativePaths(this.project), "Compiled Kotlin files differ:\n ")
|
||||||
|
|
||||||
|
fun CompiledProject.assertCompiledJavaSources(sources: Iterable<String>): CompiledProject = assertSameFiles(sources, compiledJavaSources.projectRelativePaths(this.project), "Compiled Java files differ:\n ")
|
||||||
|
fun CompiledProject.assertCompiledJavaSources(vararg sources: String): CompiledProject = assertCompiledJavaSources(sources.asIterable())
|
||||||
|
|
||||||
|
fun CompiledProject.assertCompiledJavaSourcesContain(vararg sources: String): CompiledProject = assertContainFiles(sources.asIterable(), compiledJavaSources.projectRelativePaths(this.project), "Compiled Java files differ:\n ")
|
||||||
|
|
||||||
|
|
||||||
private fun Project.createBuildCommand(params: Array<out String>, options: BuildOptions): List<String> =
|
private fun Project.createBuildCommand(params: Array<out String>, options: BuildOptions): List<String> =
|
||||||
createGradleCommand(createGradleTailParameters(options, params))
|
createGradleCommand(createGradleTailParameters(options, params))
|
||||||
|
|
||||||
|
|||||||
+216
@@ -0,0 +1,216 @@
|
|||||||
|
package org.jetbrains.kotlin.gradle
|
||||||
|
|
||||||
|
import com.google.common.io.Files
|
||||||
|
import org.gradle.api.logging.LogLevel
|
||||||
|
import org.junit.Assume
|
||||||
|
import java.io.File
|
||||||
|
import java.util.*
|
||||||
|
import kotlin.test.assertNotNull
|
||||||
|
import kotlin.test.assertTrue
|
||||||
|
|
||||||
|
|
||||||
|
abstract class BaseIncrementalGradleIT : BaseGradleIT() {
|
||||||
|
|
||||||
|
open inner class IncrementalTestProject(name: String, wrapperVersion: String = "2.4", minLogLevel: LogLevel = LogLevel.DEBUG) : Project(name, wrapperVersion, minLogLevel) {
|
||||||
|
var modificationStage: Int = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
inner class JpsTestProject(val resourcesBase: File, val relPath: String, wrapperVersion: String = "1.6", minLogLevel: LogLevel = LogLevel.DEBUG) : IncrementalTestProject(File(relPath).name, wrapperVersion, minLogLevel) {
|
||||||
|
override val resourcesRoot = File(resourcesBase, relPath)
|
||||||
|
|
||||||
|
override fun setupWorkingDir() {
|
||||||
|
val srcDir = File(projectDir, "src")
|
||||||
|
srcDir.mkdirs()
|
||||||
|
resourcesRoot.walk()
|
||||||
|
.filter { it.isFile && (it.name.endsWith(".kt") || it.name.endsWith(".java")) }
|
||||||
|
.forEach { Files.copy(it, File(srcDir, it.name)) }
|
||||||
|
copyDirRecursively(File(resourcesRootFile, "GradleWrapper-$wrapperVersion"), projectDir)
|
||||||
|
File(projectDir, "build.gradle").writeText("""
|
||||||
|
buildscript {
|
||||||
|
repositories {
|
||||||
|
maven {
|
||||||
|
url 'file://' + pathToKotlinPlugin
|
||||||
|
}
|
||||||
|
}
|
||||||
|
dependencies {
|
||||||
|
classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:0.1-SNAPSHOT'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
apply plugin: "kotlin"
|
||||||
|
|
||||||
|
sourceSets {
|
||||||
|
main {
|
||||||
|
kotlin {
|
||||||
|
srcDir 'src'
|
||||||
|
}
|
||||||
|
java {
|
||||||
|
srcDir 'src'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
repositories {
|
||||||
|
maven {
|
||||||
|
url 'file://' + pathToKotlinPlugin
|
||||||
|
}
|
||||||
|
}
|
||||||
|
""")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun IncrementalTestProject.modify(runStage: Int? = null) {
|
||||||
|
// TODO: multimodule support
|
||||||
|
val projectSrcDir = File(File(workingDir, projectName), "src")
|
||||||
|
assertTrue(projectSrcDir.exists())
|
||||||
|
val actualStage = runStage ?: modificationStage
|
||||||
|
|
||||||
|
println("<--- Modify stage: ${runStage?.toString() ?: "single"}")
|
||||||
|
|
||||||
|
fun resource2project(f: File) = File(projectSrcDir, f.toRelativeString(resourcesRoot))
|
||||||
|
|
||||||
|
resourcesRoot.walk().filter { it.isFile }.forEach {
|
||||||
|
val nameParts = it.name.split(".")
|
||||||
|
if (nameParts.size > 2) {
|
||||||
|
val (fileStage, hasStage) = nameParts.last().toIntOr(0)
|
||||||
|
if (!hasStage || fileStage == actualStage) {
|
||||||
|
val orig = File(resource2project(it.parentFile), nameParts.dropLast(if (hasStage) 2 else 1).joinToString("."))
|
||||||
|
when (if (hasStage) nameParts[nameParts.size - 2] else nameParts.last()) {
|
||||||
|
"touch" -> {
|
||||||
|
assert(orig.exists())
|
||||||
|
orig.setLastModified(Date().time)
|
||||||
|
println("<--- Modify: touch $orig")
|
||||||
|
}
|
||||||
|
"new" -> {
|
||||||
|
it.copyTo(orig, overwrite = true)
|
||||||
|
orig.setLastModified(Date().time)
|
||||||
|
println("<--- Modify: new $orig from $it")
|
||||||
|
}
|
||||||
|
"delete" -> {
|
||||||
|
assert(orig.exists())
|
||||||
|
orig.delete()
|
||||||
|
println("<--- Modify: delete $orig")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
modificationStage = actualStage + 1
|
||||||
|
}
|
||||||
|
|
||||||
|
class StageResults(val stage: Int, val compiledKotlinFiles: HashSet<String> = hashSetOf(), val compiledJavaFiles: HashSet<String> = hashSetOf(), var compileSucceeded: Boolean = true)
|
||||||
|
|
||||||
|
fun parseTestBuildLog(file: File): List<StageResults> {
|
||||||
|
class StagedLines(val stage: Int, val line: String)
|
||||||
|
|
||||||
|
return file.readLines()
|
||||||
|
.map { if (it.startsWith("========== Step")) "" else it }
|
||||||
|
.fold(arrayListOf<StagedLines>()) { slines, line ->
|
||||||
|
val (curStage, prevWasBlank) = slines.lastOrNull()?.let{ Pair(it.stage, it.line.isBlank()) } ?: Pair(0, false)
|
||||||
|
slines.add(StagedLines(curStage + if (line.isBlank() && prevWasBlank) 1 else 0, line))
|
||||||
|
slines
|
||||||
|
}
|
||||||
|
.fold(arrayListOf<StageResults>()) { res, sline ->
|
||||||
|
// for lazy creation of the node
|
||||||
|
fun curStageResults(): StageResults {
|
||||||
|
if (res.isEmpty() || sline.stage > res.last().stage) {
|
||||||
|
res.add(StageResults(sline.stage))
|
||||||
|
}
|
||||||
|
return res.last()
|
||||||
|
}
|
||||||
|
|
||||||
|
when {
|
||||||
|
sline.line.endsWith(".java", ignoreCase = true) -> curStageResults().compiledJavaFiles.add(sline.line)
|
||||||
|
sline.line.endsWith(".kt", ignoreCase = true) -> curStageResults().compiledKotlinFiles.add(sline.line)
|
||||||
|
sline.line.equals("COMPILATION FAILED", ignoreCase = true) -> curStageResults().compileSucceeded = false
|
||||||
|
}
|
||||||
|
res
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fun IncrementalTestProject.performAndAssertBuildStages(options: BuildOptions = defaultBuildOptions()) {
|
||||||
|
|
||||||
|
val checkKnown = testIsKnownJpsTestProject(resourcesRoot)
|
||||||
|
Assume.assumeTrue(checkKnown.second ?: "", checkKnown.first)
|
||||||
|
|
||||||
|
build("build", options = options) {
|
||||||
|
assertSuccessful()
|
||||||
|
assertReportExists()
|
||||||
|
}
|
||||||
|
|
||||||
|
val buildLogFile = resourcesRoot.listFiles { f: File -> f.name.endsWith("build.log") }?.sortedBy { it.length() }?.firstOrNull()
|
||||||
|
assertNotNull(buildLogFile, "*build.log file not found" )
|
||||||
|
|
||||||
|
val buildLog = parseTestBuildLog(buildLogFile!!)
|
||||||
|
assertTrue(buildLog.any())
|
||||||
|
|
||||||
|
println("<--- Build log size: ${buildLog.size}")
|
||||||
|
buildLog.forEach {
|
||||||
|
println("<--- Build log stage: ${if (it.compileSucceeded) "succeeded" else "failed"}: kotlin: ${it.compiledKotlinFiles} java: ${it.compiledJavaFiles}")
|
||||||
|
}
|
||||||
|
|
||||||
|
if (buildLog.size == 1) {
|
||||||
|
modify()
|
||||||
|
buildAndAssertStageResults(buildLog.first())
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
buildLog.forEachIndexed { stage, stageResults ->
|
||||||
|
modify(stage + 1)
|
||||||
|
buildAndAssertStageResults(stageResults)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun IncrementalTestProject.buildAndAssertStageResults(expected: StageResults, options: BuildOptions = defaultBuildOptions()) {
|
||||||
|
build("build", options = options) {
|
||||||
|
if (expected.compileSucceeded) {
|
||||||
|
assertSuccessful()
|
||||||
|
assertCompiledJavaSources(expected.compiledJavaFiles)
|
||||||
|
assertCompiledKotlinSources(expected.compiledKotlinFiles)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
assertFailed()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private val knownExtensions = arrayListOf("kt", "java")
|
||||||
|
private val knownModifyExtensions = arrayListOf("new", "touch", "delete")
|
||||||
|
|
||||||
|
private fun String.toIntOr(defaultVal: Int): Pair<Int, Boolean> {
|
||||||
|
try {
|
||||||
|
return Pair(toInt(), true)
|
||||||
|
}
|
||||||
|
catch (e: NumberFormatException) {
|
||||||
|
return Pair(defaultVal, false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun isJpsTestProject(projectRoot: File): Boolean = projectRoot.listFiles { f: File -> f.name.endsWith("build.log") }?.any() ?: false
|
||||||
|
|
||||||
|
fun testIsKnownJpsTestProject(projectRoot: File): Pair<Boolean, String?> {
|
||||||
|
var hasKnownSources = false
|
||||||
|
projectRoot.walk().filter { it.isFile }.forEach {
|
||||||
|
if (it.name.equals("dependencies.txt", ignoreCase = true))
|
||||||
|
return@testIsKnownJpsTestProject Pair(false, "multimodule tests are not supported yet")
|
||||||
|
val nameParts = it.name.split(".")
|
||||||
|
if (nameParts.size > 1) {
|
||||||
|
val (fileStage, hasStage) = nameParts.last().toIntOr(0)
|
||||||
|
val modifyExt = nameParts[nameParts.size - (if (hasStage) 2 else 1)]
|
||||||
|
val ext = nameParts[nameParts.size - (if (hasStage) 3 else 2)]
|
||||||
|
if (modifyExt in knownModifyExtensions && ext !in knownExtensions)
|
||||||
|
return@testIsKnownJpsTestProject Pair(false, "unknown staged file ${it.name}")
|
||||||
|
}
|
||||||
|
if (!hasKnownSources && it.extension in knownExtensions) {
|
||||||
|
hasKnownSources = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return if (hasKnownSources) Pair(true, null)
|
||||||
|
else Pair(false, "no known sources found")
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
+98
@@ -0,0 +1,98 @@
|
|||||||
|
package org.jetbrains.kotlin.gradle
|
||||||
|
|
||||||
|
import org.junit.Test
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
|
class KotlinGradleIncrementalFromJpsIT(): BaseIncrementalGradleIT() {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
val jpsResourcesPath = File("../../../jps-plugin/testData/incremental")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun defaultBuildOptions(): BuildOptions = BuildOptions(withDaemon = true)
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testClassSignatureChanged() {
|
||||||
|
val project = JpsTestProject(jpsResourcesPath, "pureKotlin/classSignatureChanged", "2.4")
|
||||||
|
|
||||||
|
project.build("build") {
|
||||||
|
assertSuccessful()
|
||||||
|
assertReportExists()
|
||||||
|
assertCompiledKotlinSources("src/class.kt", "src/usage.kt")
|
||||||
|
}
|
||||||
|
|
||||||
|
project.modify()
|
||||||
|
|
||||||
|
project.build("build") {
|
||||||
|
assertSuccessful()
|
||||||
|
assertCompiledKotlinSources("src/class.kt", "src/usage.kt")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testKotlinInJavaFunRenamed() {
|
||||||
|
val project = JpsTestProject(jpsResourcesPath, "withJava/kotlinUsedInJava/funRenamed", "2.4")
|
||||||
|
|
||||||
|
project.build("build") {
|
||||||
|
assertSuccessful()
|
||||||
|
assertReportExists()
|
||||||
|
assertCompiledKotlinSources("src/fun.kt")
|
||||||
|
assertCompiledJavaSources("src/WillBeUnresolved.java")
|
||||||
|
}
|
||||||
|
|
||||||
|
project.modify()
|
||||||
|
|
||||||
|
project.build("build") {
|
||||||
|
assertCompiledKotlinSources("src/fun.kt")
|
||||||
|
assertFailed()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testJavaInKotlinChangeSignature() {
|
||||||
|
val project = JpsTestProject(jpsResourcesPath, "withJava/javaUsedInKotlin/changeSignature", "2.4")
|
||||||
|
|
||||||
|
project.build("build") {
|
||||||
|
assertSuccessful()
|
||||||
|
assertReportExists()
|
||||||
|
assertCompiledKotlinSources("src/usage.kt")
|
||||||
|
assertCompiledJavaSources("src/JavaClass.java")
|
||||||
|
}
|
||||||
|
|
||||||
|
project.modify()
|
||||||
|
|
||||||
|
project.build("build") {
|
||||||
|
assertSuccessful()
|
||||||
|
assertCompiledKotlinSources("src/usage.kt")
|
||||||
|
assertCompiledJavaSources("src/JavaClass.java")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testClassSignatureChangedAuto() {
|
||||||
|
val project = JpsTestProject(jpsResourcesPath, "pureKotlin/classSignatureChanged", "2.4")
|
||||||
|
|
||||||
|
project.performAndAssertBuildStages()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testAnnotationListChanged() {
|
||||||
|
val project = JpsTestProject(jpsResourcesPath, "classHierarchyAffected/annotationListChanged", "2.4")
|
||||||
|
|
||||||
|
project.performAndAssertBuildStages()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testClassBecameFinal() {
|
||||||
|
val project = JpsTestProject(jpsResourcesPath, "classHierarchyAffected/classBecameFinal", "2.4")
|
||||||
|
|
||||||
|
project.performAndAssertBuildStages()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testSupertypesListChanged() {
|
||||||
|
val project = JpsTestProject(jpsResourcesPath, "classHierarchyAffected/supertypesListChanged", "2.4")
|
||||||
|
|
||||||
|
project.performAndAssertBuildStages()
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user