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:
Ilya Chernikov
2016-01-25 19:10:42 +01:00
committed by Alexey Tsvetkov
parent cde89cb1e8
commit 7b54751092
4 changed files with 361 additions and 2 deletions
@@ -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
}
@@ -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))
@@ -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")
}
@@ -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()
}
}