| /* |
| * 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) |
| } |
| } |
| } |