blob: a6257429c7a2180622fee8274693069d44b7961e [file] [log] [blame]
/*
* Copyright 2010-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.incremental
import com.intellij.testFramework.UsefulTestCase
import org.jetbrains.kotlin.TestWithWorkingDir
import org.jetbrains.kotlin.cli.common.ExitCode
import org.jetbrains.kotlin.cli.common.arguments.CommonCompilerArguments
import org.jetbrains.kotlin.incremental.testingUtils.*
import org.jetbrains.kotlin.incremental.utils.TestCompilationResult
import org.junit.Assert
import java.io.File
abstract class AbstractIncrementalCompilerRunnerTestBase<Args : CommonCompilerArguments> : TestWithWorkingDir() {
protected abstract fun createCompilerArguments(destinationDir: File, testDir: File): Args
fun doTest(path: String) {
val testDir = File(path)
fun Iterable<File>.relativePaths() =
map { it.relativeTo(workingDir).path.replace('\\', '/') }
val srcDir = File(workingDir, "src").apply { mkdirs() }
val cacheDir = File(workingDir, "incremental-data").apply { mkdirs() }
val outDir = File(workingDir, "out").apply { mkdirs() }
val mapWorkingToOriginalFile = HashMap(copyTestSources(testDir, srcDir, filePrefix = ""))
val sourceRoots = listOf(srcDir)
val args = createCompilerArguments(outDir, testDir)
// initial build
val (_, _, errors) = make(cacheDir, sourceRoots, args)
if (errors.isNotEmpty()) {
throw IllegalStateException("Initial build failed: \n${errors.joinToString("\n")}")
}
// modifications
val buildLogFile = buildLogFinder.findBuildLog(testDir) ?: throw IllegalStateException("build log file not found in $workingDir")
val buildLogSteps = parseTestBuildLog(buildLogFile)
val modifications = getModificationsToPerform(
testDir,
moduleNames = null,
allowNoFilesWithSuffixInTestData = false,
touchPolicy = TouchPolicy.CHECKSUM
)
assert(modifications.size == buildLogSteps.size) {
"Modifications count (${modifications.size}) != expected build log steps count (${buildLogSteps.size})"
}
// Sometimes error messages differ.
// This needs to be fixed, but it does not really matter much (e.g extra lines),
// The workaround is to compare logs without errors, then logs with errors.
// (if logs without errors differ then either compiled files differ or exit codes differ)
val expectedSB = StringBuilder()
val actualSB = StringBuilder()
val expectedSBWithoutErrors = StringBuilder()
val actualSBWithoutErrors = StringBuilder()
var step = 1
for ((modificationStep, buildLogStep) in modifications.zip(buildLogSteps)) {
modificationStep.forEach { it.perform(workingDir, mapWorkingToOriginalFile) }
val (_, compiledSources, compileErrors) = make(cacheDir, sourceRoots, args)
expectedSB.appendLine(stepLogAsString(step, buildLogStep.compiledKotlinFiles, buildLogStep.compileErrors))
expectedSBWithoutErrors.appendLine(
stepLogAsString(
step,
buildLogStep.compiledKotlinFiles,
buildLogStep.compileErrors,
includeErrors = false
)
)
actualSB.appendLine(stepLogAsString(step, compiledSources.relativePaths(), compileErrors))
actualSBWithoutErrors.appendLine(stepLogAsString(step, compiledSources.relativePaths(), compileErrors, includeErrors = false))
step++
}
if (expectedSBWithoutErrors.toString() != actualSBWithoutErrors.toString()) {
if (BuildLogFinder.isJpsLogFile(buildLogFile)) {
// JPS logs should be updated carefully, because standalone logs are a bit different (no removed classes, iterations, etc)
Assert.assertEquals(expectedSB.toString(), actualSB.toString())
} else {
UsefulTestCase.assertSameLinesWithFile(buildLogFile.canonicalPath, actualSB.toString(), false)
}
}
// todo: also compare caches
run rebuildAndCompareOutput@{
val rebuildOutDir = File(workingDir, "rebuild-out").apply { mkdirs() }
val rebuildCacheDir = File(workingDir, "rebuild-cache").apply { mkdirs() }
val rebuildResult = make(rebuildCacheDir, sourceRoots, createCompilerArguments(rebuildOutDir, testDir))
val rebuildExpectedToSucceed = buildLogSteps.last().compileSucceeded
val rebuildSucceeded = rebuildResult.exitCode == ExitCode.OK
Assert.assertEquals("Rebuild exit code differs from incremental exit code", rebuildExpectedToSucceed, rebuildSucceeded)
if (rebuildSucceeded) {
assertEqualDirectories(rebuildOutDir, outDir, forgiveExtraFiles = false)
}
}
}
protected open val buildLogFinder: BuildLogFinder
get() = BuildLogFinder(isGradleEnabled = true)
protected abstract fun make(cacheDir: File, sourceRoots: Iterable<File>, args: Args): TestCompilationResult
private fun stepLogAsString(step: Int, ktSources: Iterable<String>, errors: Collection<String>, includeErrors: Boolean = true): String {
val sb = StringBuilder()
sb.appendLine("================ Step #$step =================")
sb.appendLine()
sb.appendLine("Compiling files:")
ktSources.toSet().toTypedArray().sortedArray().forEach { sb.appendLine(" $it") }
sb.appendLine("End of files")
sb.appendLine("Exit code: ${if (errors.isEmpty()) "OK" else "ABORT"}")
if (errors.isNotEmpty() && includeErrors) {
sb.appendLine("------------------------------------------")
sb.appendLine("COMPILATION FAILED")
errors.filter(String::isNotEmpty).forEach { sb.appendLine(it) }
}
return sb.toString()
}
private fun StringBuilder.appendLine(line: String = "") {
append(line)
append('\n')
}
companion object {
@JvmStatic
private val distKotlincLib: File = File("dist/kotlinc/lib")
@JvmStatic
protected val kotlinStdlibJvm: File = File(distKotlincLib, "kotlin-stdlib.jar").also {
UsefulTestCase.assertExists(it)
}
}
}