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 val resourcesRoot = File(resourcesRootFile, "testProject/$projectName")
|
||||
val projectDir = File(workingDir, projectName)
|
||||
val projectDir = File(workingDir.canonicalFile, projectName)
|
||||
|
||||
open fun setupWorkingDir() {
|
||||
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) {
|
||||
val cmd = createBuildCommand(tasks, options)
|
||||
@@ -173,6 +180,35 @@ abstract class BaseGradleIT {
|
||||
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> =
|
||||
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