diff --git a/libraries/tools/kotlin-gradle-plugin/src/test/kotlin/org/jetbrains/kotlin/gradle/BaseIncrementalGradleIT.kt b/libraries/tools/kotlin-gradle-plugin/src/test/kotlin/org/jetbrains/kotlin/gradle/BaseIncrementalGradleIT.kt index dd24f47183e..7891fcdfc3a 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/test/kotlin/org/jetbrains/kotlin/gradle/BaseIncrementalGradleIT.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/test/kotlin/org/jetbrains/kotlin/gradle/BaseIncrementalGradleIT.kt @@ -2,13 +2,14 @@ package org.jetbrains.kotlin.gradle import com.google.common.io.Files import org.gradle.api.logging.LogLevel +import org.jetbrains.kotlin.gradle.incremental.BuildStep +import org.jetbrains.kotlin.gradle.incremental.parseTestBuildLog 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) { @@ -69,37 +70,6 @@ abstract class BaseIncrementalGradleIT : BaseGradleIT() { modificationStage = actualStage + 1 } - class StageResults(val stage: Int, val compiledKotlinFiles: HashSet = hashSetOf(), val compiledJavaFiles: HashSet = hashSetOf(), var compileSucceeded: Boolean = true) - - fun parseTestBuildLog(file: File): List { - class StagedLines(val stage: Int, val line: String) - - return file.readLines() - .map { if (it.startsWith("========== Step")) "" else it } - .fold(arrayListOf()) { 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()) { 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(), weakTesting: Boolean = false) { val checkKnown = testIsKnownJpsTestProject(resourcesRoot) @@ -133,7 +103,7 @@ abstract class BaseIncrementalGradleIT : BaseGradleIT() { } } - fun IncrementalTestProject.buildAndAssertStageResults(expected: StageResults, options: BuildOptions = defaultBuildOptions(), weakTesting: Boolean = false) { + fun IncrementalTestProject.buildAndAssertStageResults(expected: BuildStep, options: BuildOptions = defaultBuildOptions(), weakTesting: Boolean = false) { build("build", options = options) { if (expected.compileSucceeded) { assertSuccessful() diff --git a/libraries/tools/kotlin-gradle-plugin/src/test/kotlin/org/jetbrains/kotlin/gradle/BuildLogParserParametrizedIT.kt b/libraries/tools/kotlin-gradle-plugin/src/test/kotlin/org/jetbrains/kotlin/gradle/BuildLogParserParametrizedIT.kt new file mode 100644 index 00000000000..c6c933fbd47 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/test/kotlin/org/jetbrains/kotlin/gradle/BuildLogParserParametrizedIT.kt @@ -0,0 +1,79 @@ +package org.jetbrains.kotlin.gradle + +import org.jetbrains.kotlin.gradle.incremental.parseTestBuildLog +import org.jetbrains.kotlin.utils.Printer +import org.junit.Assert +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.Parameterized +import java.io.File + +@RunWith(Parameterized::class) +class BuildLogParserParametrizedIT : BaseGradleIT() { + + @Parameterized.Parameter + @JvmField + var testDirName: String = "" + + @Test + fun testParser() { + val testDir = File(TEST_ROOT, testDirName) + val logFile = File(testDir, LOG_FILE_NAME) + assert(logFile.isFile) { "Log file: $logFile does not exist" } + + val parsedStages = parseTestBuildLog(logFile) + val sb = StringBuilder() + val p = Printer(sb) + + for ((i, stage) in parsedStages.withIndex()) { + if (i > 0) { + p.println() + } + + p.println("Step #${i + 1}") + + p.println("Compiled java files:") + p.printlnSorted(stage.compiledJavaFiles) + + p.println("Compiled kotlin files:") + p.printlnSorted(stage.compiledKotlinFiles) + + p.println("Compile errors:") + p.printlnSorted(stage.compileErrors) + } + + val actual = sb.toString() + val expectedFile = File(testDir, EXPECTED_PARSED_LOG_FILE_NAME) + + if (!expectedFile.isFile) { + expectedFile.createNewFile() + expectedFile.writeText(actual) + + throw AssertionError("Expected file log did not exist, created: $expectedFile") + } + + val expectedContent = expectedFile.readText() + Assert.assertEquals("Parsed content was unexpected: ", actual, expectedContent) + } + + companion object { + private val TEST_ROOT = File(resourcesRootFile, "buildLogsParserData") + private val LOG_FILE_NAME = "build.log" + private val EXPECTED_PARSED_LOG_FILE_NAME = "expected.txt" + + @Suppress("unused") + @Parameterized.Parameters(name = "{index}: {0}") + @JvmStatic + fun data(): List> { + val directories = TEST_ROOT.listFiles().filter { it.isDirectory } + return directories.map { arrayOf(it.name) }.toList() + } + } +} + +private fun > Printer.printlnSorted(elements: Iterable) { + pushIndent() + elements.sorted().forEach { this.println(it) } + popIndent() +} + diff --git a/libraries/tools/kotlin-gradle-plugin/src/test/kotlin/org/jetbrains/kotlin/gradle/incremental/testLogsParsingUtil.kt b/libraries/tools/kotlin-gradle-plugin/src/test/kotlin/org/jetbrains/kotlin/gradle/incremental/testLogsParsingUtil.kt new file mode 100644 index 00000000000..829fb0963f1 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/test/kotlin/org/jetbrains/kotlin/gradle/incremental/testLogsParsingUtil.kt @@ -0,0 +1,79 @@ +package org.jetbrains.kotlin.gradle.incremental + +import java.io.File + +class BuildStep( + val compiledKotlinFiles: MutableSet = hashSetOf(), + val compiledJavaFiles: MutableSet = hashSetOf(), + val compileErrors: MutableList = arrayListOf() +) { + val compileSucceeded: Boolean + get() = compileErrors.isEmpty() +} + +fun parseTestBuildLog(file: File): List { + fun splitSteps(lines: List): List> { + val stepsLines = mutableListOf>() + + for (line in lines) { + when { + line.matches("=+ Step #\\d+ =+".toRegex()) -> { + stepsLines.add(mutableListOf()) + } + else -> { + stepsLines.lastOrNull()?.add(line) + } + } + } + + return stepsLines + } + + fun BuildStep.parseStepCompiledFiles(stepLines: List) { + var readFiles = false + + for (line in stepLines) { + if (line.startsWith("Compiling files:")) { + readFiles = true + continue + } + + if (readFiles && line.startsWith("End of files")) { + readFiles = false + continue + } + + if (readFiles) { + val path = line.trim() + + if (path.endsWith(".kt")) { + compiledKotlinFiles.add(path) + } + else if (path.endsWith(".java")) { + compiledJavaFiles.add(path) + } + else { + throw IllegalStateException("Expected .kt or .java file, got: $path") + } + } + } + } + + fun BuildStep.parseErrors(stepLines: List) { + val startIndex = stepLines.indexOfLast { it.startsWith("COMPILATION FAILED") } + + if (startIndex > 0) { + compileErrors.addAll(stepLines.subList(startIndex + 1, stepLines.size)) + } + } + + val stepsLines = splitSteps(file.readLines()) + + + return stepsLines.map { stepLines -> + val buildStep = BuildStep() + buildStep.parseStepCompiledFiles(stepLines) + buildStep.parseErrors(stepLines) + buildStep + } +} diff --git a/libraries/tools/kotlin-gradle-plugin/src/test/resources/buildLogsParserData/twoStep/build.log b/libraries/tools/kotlin-gradle-plugin/src/test/resources/buildLogsParserData/twoStep/build.log new file mode 100644 index 00000000000..1b9129d947f --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/test/resources/buildLogsParserData/twoStep/build.log @@ -0,0 +1,50 @@ +================ Step #1 ================= + +Cleaning output files: + out/production/module/META-INF/module.kotlin_module + out/production/module/inline/InlineGetKt.class +End of files +Compiling files: + src/inlineGet.kt +End of files +Marked as dirty by Kotlin: + src/UsageVal.kt + src/UsageVar.kt +Exit code: ADDITIONAL_PASS_REQUIRED +------------------------------------------ +Cleaning output files: + out/production/module/usage/UsageVal.class + out/production/module/usage/UsageVar.class +End of files +Compiling files: + src/UsageVal.kt + src/UsageVar.kt +End of files +Exit code: ADDITIONAL_PASS_REQUIRED +------------------------------------------ +Exit code: NOTHING_DONE +------------------------------------------ + +================ Step #2 ================= + +Cleaning output files: + out/production/module/META-INF/module.kotlin_module + out/production/module/inline/InlineSetKt.class +End of files +Compiling files: + src/inlineSet.kt +End of files +Marked as dirty by Kotlin: + src/UsageVar.kt +Exit code: ADDITIONAL_PASS_REQUIRED +------------------------------------------ +Cleaning output files: + out/production/module/usage/UsageVar.class +End of files +Compiling files: + src/UsageVar.kt +End of files +Exit code: ADDITIONAL_PASS_REQUIRED +------------------------------------------ +Exit code: NOTHING_DONE +------------------------------------------ \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin/src/test/resources/buildLogsParserData/twoStep/expected.txt b/libraries/tools/kotlin-gradle-plugin/src/test/resources/buildLogsParserData/twoStep/expected.txt new file mode 100644 index 00000000000..b7b33342f8c --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/test/resources/buildLogsParserData/twoStep/expected.txt @@ -0,0 +1,14 @@ +Step #1 +Compiled java files: +Compiled kotlin files: + src/UsageVal.kt + src/UsageVar.kt + src/inlineGet.kt +Compile errors: + +Step #2 +Compiled java files: +Compiled kotlin files: + src/UsageVar.kt + src/inlineSet.kt +Compile errors: diff --git a/libraries/tools/kotlin-gradle-plugin/src/test/resources/buildLogsParserData/withError/build.log b/libraries/tools/kotlin-gradle-plugin/src/test/resources/buildLogsParserData/withError/build.log new file mode 100644 index 00000000000..c105d8ab425 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/test/resources/buildLogsParserData/withError/build.log @@ -0,0 +1,37 @@ +================ Step #1 ================= + +Cleaning output files: + out/production/module1/META-INF/module1.kotlin_module + out/production/module1/a/A.class + out/production/module1/a/ClassAnnotation.class + out/production/module1/a/FileAnnotation.class + out/production/module1/a/Module1_aKt.class +End of files +Compiling files: + module1/src/module1_a.kt +End of files +Marked as dirty by Kotlin: + module2/src/module2_b.kt +Exit code: ADDITIONAL_PASS_REQUIRED +------------------------------------------ +Exit code: NOTHING_DONE +------------------------------------------ +Cleaning output files: + out/production/module2/META-INF/module2.kotlin_module + out/production/module2/b/B.class + out/production/module2/b/Module2_bKt.class +End of files +Compiling files: + module2/src/module2_b.kt +End of files +Exit code: ABORT +------------------------------------------ +COMPILATION FAILED +Cannot access 'FileAnnotation': it is 'internal' in 'a' +Cannot access 'A': it is 'internal' in 'a' +Cannot access 'FileAnnotation': it is 'internal' in 'a' +Cannot access 'ClassAnnotation': it is 'internal' in 'a' +Cannot access 'ClassAnnotation': it is 'internal' in 'a' +Function effective visibility 'public' should be the same or less permissive than its parameter type effective visibility 'internal' +Cannot access 'A': it is 'internal' in 'a' +Cannot access 'a': it is 'internal' in 'a' diff --git a/libraries/tools/kotlin-gradle-plugin/src/test/resources/buildLogsParserData/withError/expected.txt b/libraries/tools/kotlin-gradle-plugin/src/test/resources/buildLogsParserData/withError/expected.txt new file mode 100644 index 00000000000..22bb49fd9f5 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/test/resources/buildLogsParserData/withError/expected.txt @@ -0,0 +1,14 @@ +Step #1 +Compiled java files: +Compiled kotlin files: + module1/src/module1_a.kt + module2/src/module2_b.kt +Compile errors: + Cannot access 'A': it is 'internal' in 'a' + Cannot access 'A': it is 'internal' in 'a' + Cannot access 'ClassAnnotation': it is 'internal' in 'a' + Cannot access 'ClassAnnotation': it is 'internal' in 'a' + Cannot access 'FileAnnotation': it is 'internal' in 'a' + Cannot access 'FileAnnotation': it is 'internal' in 'a' + Cannot access 'a': it is 'internal' in 'a' + Function effective visibility 'public' should be the same or less permissive than its parameter type effective visibility 'internal' diff --git a/libraries/tools/kotlin-gradle-plugin/src/test/resources/buildLogsParserData/withJava/build.log b/libraries/tools/kotlin-gradle-plugin/src/test/resources/buildLogsParserData/withJava/build.log new file mode 100644 index 00000000000..77974661009 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/test/resources/buildLogsParserData/withJava/build.log @@ -0,0 +1,17 @@ +================ Step #1 ================= + +Cleaning output files: + out/production/module/JavaClass.class + out/production/module/META-INF/module.kotlin_module + out/production/module/UsageKt.class +End of files +Compiling files: + src/usage.kt +End of files +Exit code: ADDITIONAL_PASS_REQUIRED +------------------------------------------ +Compiling files: + src/JavaClass.java +End of files +Exit code: NOTHING_DONE +------------------------------------------ diff --git a/libraries/tools/kotlin-gradle-plugin/src/test/resources/buildLogsParserData/withJava/expected.txt b/libraries/tools/kotlin-gradle-plugin/src/test/resources/buildLogsParserData/withJava/expected.txt new file mode 100644 index 00000000000..47f3142795e --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/test/resources/buildLogsParserData/withJava/expected.txt @@ -0,0 +1,6 @@ +Step #1 +Compiled java files: + src/JavaClass.java +Compiled kotlin files: + src/usage.kt +Compile errors: