diff --git a/jps/jps-plugin/jps-tests/test/org/jetbrains/kotlin/jps/incremental/AbstractJvmProtoComparisonTest.kt b/jps/jps-plugin/jps-tests/test/org/jetbrains/kotlin/jps/incremental/AbstractJvmProtoComparisonTest.kt new file mode 100644 index 00000000000..36ce3fc2eba --- /dev/null +++ b/jps/jps-plugin/jps-tests/test/org/jetbrains/kotlin/jps/incremental/AbstractJvmProtoComparisonTest.kt @@ -0,0 +1,65 @@ +/* + * 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.jps.incremental + +import org.jetbrains.kotlin.incremental.Difference +import org.jetbrains.kotlin.incremental.LocalFileKotlinClass +import org.jetbrains.kotlin.incremental.difference +import org.jetbrains.kotlin.incremental.storage.ProtoMapValue +import org.jetbrains.kotlin.incremental.testingUtils.copyTestSources +import org.jetbrains.kotlin.load.kotlin.KotlinJvmBinaryClass +import org.jetbrains.kotlin.load.kotlin.header.KotlinClassHeader +import org.jetbrains.kotlin.name.ClassId +import org.jetbrains.kotlin.serialization.jvm.BitEncoding +import org.jetbrains.kotlin.test.MockLibraryUtil +import java.io.File + +abstract class AbstractJvmProtoComparisonTest : AbstractProtoComparisonTest() { + override fun compileAndGetClasses(sourceDir: File, outputDir: File): Map { + MockLibraryUtil.compileKotlin(sourceDir.path, outputDir) + + val classFiles = outputDir.walkMatching { it.name.endsWith(".class") } + val localClassFiles = classFiles.map { LocalFileKotlinClass.create(it)!! } + return localClassFiles.associateBy { it.classId } + } + + override fun difference(oldData: LocalFileKotlinClass, newData: LocalFileKotlinClass): Difference? { + val oldProto = oldData.readProto() ?: return null + val newProto = newData.readProto() ?: return null + return org.jetbrains.kotlin.incremental.difference(oldProto, newProto) + } + + private fun KotlinJvmBinaryClass.readProto(): ProtoMapValue? { + assert(classHeader.metadataVersion.isCompatible()) { "Incompatible class ($classHeader): $location" } + + val bytes by lazy { BitEncoding.decodeBytes(classHeader.data!!) } + val strings by lazy { classHeader.strings!! } + + return when (classHeader.kind) { + KotlinClassHeader.Kind.CLASS -> { + ProtoMapValue(false, bytes, strings) + } + KotlinClassHeader.Kind.FILE_FACADE, + KotlinClassHeader.Kind.MULTIFILE_CLASS_PART -> { + ProtoMapValue(true, bytes, strings) + } + else -> { + null + } + } + } +} \ No newline at end of file diff --git a/jps/jps-plugin/jps-tests/test/org/jetbrains/kotlin/jps/incremental/AbstractProtoComparisonTest.kt b/jps/jps-plugin/jps-tests/test/org/jetbrains/kotlin/jps/incremental/AbstractProtoComparisonTest.kt index 2266fa2e47c..d76f79845d4 100644 --- a/jps/jps-plugin/jps-tests/test/org/jetbrains/kotlin/jps/incremental/AbstractProtoComparisonTest.kt +++ b/jps/jps-plugin/jps-tests/test/org/jetbrains/kotlin/jps/incremental/AbstractProtoComparisonTest.kt @@ -18,112 +18,77 @@ package org.jetbrains.kotlin.jps.incremental import com.intellij.openapi.util.io.FileUtil import com.intellij.testFramework.UsefulTestCase -import com.intellij.util.SmartList -import org.jetbrains.kotlin.incremental.LocalFileKotlinClass -import org.jetbrains.kotlin.incremental.difference -import org.jetbrains.kotlin.incremental.storage.ProtoMapValue -import org.jetbrains.kotlin.load.kotlin.KotlinJvmBinaryClass -import org.jetbrains.kotlin.load.kotlin.header.KotlinClassHeader -import org.jetbrains.kotlin.serialization.jvm.BitEncoding +import org.jetbrains.kotlin.incremental.Difference +import org.jetbrains.kotlin.name.ClassId import org.jetbrains.kotlin.test.KotlinTestUtils -import org.jetbrains.kotlin.test.MockLibraryUtil import org.jetbrains.kotlin.utils.Printer +import org.jetbrains.kotlin.utils.keysToMap import java.io.File -abstract class AbstractProtoComparisonTest : UsefulTestCase() { +abstract class AbstractProtoComparisonTest : UsefulTestCase() { + protected abstract fun compileAndGetClasses(sourceDir: File, outputDir: File): Map + protected abstract fun difference(oldData: PROTO_DATA, newData: PROTO_DATA): Difference? + fun doTest(testDataPath: String) { - val testDir = KotlinTestUtils.tmpDir("testDirectory") + val testDir = File(testDataPath) + val workingDir = KotlinTestUtils.tmpDir("testDirectory") - val oldClassFiles = compileFileAndGetClasses(testDataPath, testDir, "old") - val newClassFiles = compileFileAndGetClasses(testDataPath, testDir, "new") - - - val oldClassMap = oldClassFiles.map { LocalFileKotlinClass.create(it)!!.let { it.classId to it } }.toMap() - val newClassMap = newClassFiles.map { LocalFileKotlinClass.create(it)!!.let { it.classId to it } }.toMap() + val oldClassMap = classesForPrefixedSources(testDir, workingDir, "old") + val newClassMap = classesForPrefixedSources(testDir, workingDir, "new") val sb = StringBuilder() val p = Printer(sb) - val oldSetOfNames = oldClassFiles.map { it.name }.toSet() - val newSetOfNames = newClassFiles.map { it.name }.toSet() - - val removedNames = (oldClassMap.keys - newClassMap.keys).map { it.toString() }.sorted() - removedNames.forEach { - p.println("REMOVED: class $it") + (oldClassMap.keys - newClassMap.keys).sortedBy { it.toString() }.forEach { classId -> + p.println("REMOVED: class $classId") } - val addedNames = (newClassMap.keys - oldClassMap.keys).map { it.toString() }.sorted() - addedNames.forEach { - p.println("ADDED: class $it") + (newClassMap.keys - oldClassMap.keys).sortedBy { it.toString() }.forEach { classId -> + p.println("ADDED: class $classId") } - val commonNames = oldClassMap.keys.intersect(newClassMap.keys).sortedBy { it.toString() } + (oldClassMap.keys.intersect(newClassMap.keys)).sortedBy { it.toString() }.forEach { classId -> + val diff = difference(oldClassMap[classId]!!, newClassMap[classId]!!) - for(name in commonNames) { - p.printDifference(oldClassMap[name]!!, newClassMap[name]!!) + if (diff == null) { + p.println("skip $classId") + return@forEach + } + + val changes = arrayListOf() + if (diff.isClassAffected) { + changes.add("CLASS_SIGNATURE") + } + if (diff.changedMembersNames.isNotEmpty()) { + changes.add("MEMBERS\n ${diff.changedMembersNames.sorted()}") + } + if (changes.isEmpty()) { + changes.add("NONE") + } + + p.println("changes in $classId: ${changes.joinToString()}") } KotlinTestUtils.assertEqualsToFile(File(testDataPath + File.separator + "result.out"), sb.toString()) } - private fun compileFileAndGetClasses(testPath: String, testDir: File, prefix: String): List { - val files = File(testPath).listFiles { it -> it.name.startsWith(prefix) }!! - val sourcesDirectory = testDir.createSubDirectory("sources") - val classesDirectory = testDir.createSubDirectory("$prefix.src") - - files.forEach { file -> - FileUtil.copy(file, File(sourcesDirectory, file.name.replaceFirst(prefix, "main"))) - } - MockLibraryUtil.compileKotlin(sourcesDirectory.path, classesDirectory) - - return File(classesDirectory, "test").listFiles() { it -> it.name.endsWith(".class") }?.sortedBy { it.name }!! + private fun classesForPrefixedSources(testDir: File, workingDir: File, prefix: String): Map { + val srcDir = workingDir.createSubDirectory("$prefix/src") + val outDir = workingDir.createSubDirectory("$prefix/out") + copySourceFiles(testDir, srcDir, prefix) + return compileAndGetClasses(srcDir, outDir) } - private fun Printer.printDifference(oldClass: LocalFileKotlinClass, newClass: LocalFileKotlinClass) { - fun KotlinJvmBinaryClass.readProto(): ProtoMapValue? { - assert(classHeader.metadataVersion.isCompatible()) { "Incompatible class ($classHeader): $location" } - - val bytes by lazy { BitEncoding.decodeBytes(classHeader.data!!) } - val strings by lazy { classHeader.strings!! } - - return when (classHeader.kind) { - KotlinClassHeader.Kind.CLASS -> { - ProtoMapValue(false, bytes, strings) - } - KotlinClassHeader.Kind.FILE_FACADE, - KotlinClassHeader.Kind.MULTIFILE_CLASS_PART -> { - ProtoMapValue(true, bytes, strings) - } - else -> { - println("skip $classId") - null - } - } + private fun copySourceFiles(sourceDir: File, targetDir: File, prefix: String) { + for (srcFile in sourceDir.walkMatching { it.name.startsWith(prefix) }) { + val targetFile = File(targetDir, srcFile.name.replaceFirst(prefix, "main")) + srcFile.copyTo(targetFile) } - - val diff = difference(oldClass.readProto() ?: return, - newClass.readProto() ?: return) - - val changes = SmartList() - - if (diff.isClassAffected) { - changes.add("CLASS_SIGNATURE") - } - - if (diff.changedMembersNames.isNotEmpty()) { - changes.add("MEMBERS\n ${diff.changedMembersNames.sorted()}") - } - - if (changes.isEmpty()) { - changes.add("NONE") - } - - println("changes in ${oldClass.classId}: ${changes.joinToString()}") } - private fun File.createSubDirectory(relativePath: String): File { - val directory = File(this, relativePath) - FileUtil.createDirectory(directory) - return directory - } + protected fun File.createSubDirectory(relativePath: String): File = + File(this, relativePath).apply { mkdirs() } + + protected fun File.walkMatching(predicate: (File)->Boolean): Sequence = + walk().filter { predicate(it) } } diff --git a/jps/jps-plugin/jps-tests/test/org/jetbrains/kotlin/jps/incremental/ProtoComparisonTestGenerated.java b/jps/jps-plugin/jps-tests/test/org/jetbrains/kotlin/jps/incremental/JvmProtoComparisonTestGenerated.java similarity index 96% rename from jps/jps-plugin/jps-tests/test/org/jetbrains/kotlin/jps/incremental/ProtoComparisonTestGenerated.java rename to jps/jps-plugin/jps-tests/test/org/jetbrains/kotlin/jps/incremental/JvmProtoComparisonTestGenerated.java index 07a71ae9747..e8af27c8bc8 100644 --- a/jps/jps-plugin/jps-tests/test/org/jetbrains/kotlin/jps/incremental/ProtoComparisonTestGenerated.java +++ b/jps/jps-plugin/jps-tests/test/org/jetbrains/kotlin/jps/incremental/JvmProtoComparisonTestGenerated.java @@ -29,11 +29,11 @@ import java.util.regex.Pattern; /** This class is generated by {@link org.jetbrains.kotlin.generators.tests.TestsPackage}. DO NOT MODIFY MANUALLY */ @SuppressWarnings("all") @RunWith(JUnit3RunnerWithInners.class) -public class ProtoComparisonTestGenerated extends AbstractProtoComparisonTest { +public class JvmProtoComparisonTestGenerated extends AbstractJvmProtoComparisonTest { @TestMetadata("jps-plugin/testData/comparison/classSignatureChange") @TestDataPath("$PROJECT_ROOT") @RunWith(JUnit3RunnerWithInners.class) - public static class ClassSignatureChange extends AbstractProtoComparisonTest { + public static class ClassSignatureChange extends AbstractJvmProtoComparisonTest { public void testAllFilesPresentInClassSignatureChange() throws Exception { KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("jps-plugin/testData/comparison/classSignatureChange"), Pattern.compile("^([^\\.]+)$"), TargetBackend.ANY, true); } @@ -72,7 +72,7 @@ public class ProtoComparisonTestGenerated extends AbstractProtoComparisonTest { @TestMetadata("jps-plugin/testData/comparison/classPrivateOnlyChange") @TestDataPath("$PROJECT_ROOT") @RunWith(JUnit3RunnerWithInners.class) - public static class ClassPrivateOnlyChange extends AbstractProtoComparisonTest { + public static class ClassPrivateOnlyChange extends AbstractJvmProtoComparisonTest { public void testAllFilesPresentInClassPrivateOnlyChange() throws Exception { KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("jps-plugin/testData/comparison/classPrivateOnlyChange"), Pattern.compile("^([^\\.]+)$"), TargetBackend.ANY, true); } @@ -111,7 +111,7 @@ public class ProtoComparisonTestGenerated extends AbstractProtoComparisonTest { @TestMetadata("jps-plugin/testData/comparison/classMembersOnlyChanged") @TestDataPath("$PROJECT_ROOT") @RunWith(JUnit3RunnerWithInners.class) - public static class ClassMembersOnlyChanged extends AbstractProtoComparisonTest { + public static class ClassMembersOnlyChanged extends AbstractJvmProtoComparisonTest { public void testAllFilesPresentInClassMembersOnlyChanged() throws Exception { KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("jps-plugin/testData/comparison/classMembersOnlyChanged"), Pattern.compile("^([^\\.]+)$"), TargetBackend.ANY, true); } @@ -168,7 +168,7 @@ public class ProtoComparisonTestGenerated extends AbstractProtoComparisonTest { @TestMetadata("jps-plugin/testData/comparison/packageMembers") @TestDataPath("$PROJECT_ROOT") @RunWith(JUnit3RunnerWithInners.class) - public static class PackageMembers extends AbstractProtoComparisonTest { + public static class PackageMembers extends AbstractJvmProtoComparisonTest { public void testAllFilesPresentInPackageMembers() throws Exception { KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("jps-plugin/testData/comparison/packageMembers"), Pattern.compile("^([^\\.]+)$"), TargetBackend.ANY, true); } @@ -201,7 +201,7 @@ public class ProtoComparisonTestGenerated extends AbstractProtoComparisonTest { @TestMetadata("jps-plugin/testData/comparison/unchanged") @TestDataPath("$PROJECT_ROOT") @RunWith(JUnit3RunnerWithInners.class) - public static class Unchanged extends AbstractProtoComparisonTest { + public static class Unchanged extends AbstractJvmProtoComparisonTest { public void testAllFilesPresentInUnchanged() throws Exception { KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("jps-plugin/testData/comparison/unchanged"), Pattern.compile("^([^\\.]+)$"), TargetBackend.ANY, true); } @@ -222,7 +222,7 @@ public class ProtoComparisonTestGenerated extends AbstractProtoComparisonTest { @TestMetadata("jps-plugin/testData/comparison/jvmOnly") @TestDataPath("$PROJECT_ROOT") @RunWith(JUnit3RunnerWithInners.class) - public static class JvmOnly extends AbstractProtoComparisonTest { + public static class JvmOnly extends AbstractJvmProtoComparisonTest { public void testAllFilesPresentInJvmOnly() throws Exception { KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("jps-plugin/testData/comparison/jvmOnly"), Pattern.compile("^([^\\.]+)$"), TargetBackend.ANY, true); }