+65
@@ -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<LocalFileKotlinClass>() {
|
||||||
|
override fun compileAndGetClasses(sourceDir: File, outputDir: File): Map<ClassId, LocalFileKotlinClass> {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+48
-83
@@ -18,112 +18,77 @@ package org.jetbrains.kotlin.jps.incremental
|
|||||||
|
|
||||||
import com.intellij.openapi.util.io.FileUtil
|
import com.intellij.openapi.util.io.FileUtil
|
||||||
import com.intellij.testFramework.UsefulTestCase
|
import com.intellij.testFramework.UsefulTestCase
|
||||||
import com.intellij.util.SmartList
|
import org.jetbrains.kotlin.incremental.Difference
|
||||||
import org.jetbrains.kotlin.incremental.LocalFileKotlinClass
|
import org.jetbrains.kotlin.name.ClassId
|
||||||
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.test.KotlinTestUtils
|
import org.jetbrains.kotlin.test.KotlinTestUtils
|
||||||
import org.jetbrains.kotlin.test.MockLibraryUtil
|
|
||||||
import org.jetbrains.kotlin.utils.Printer
|
import org.jetbrains.kotlin.utils.Printer
|
||||||
|
import org.jetbrains.kotlin.utils.keysToMap
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
abstract class AbstractProtoComparisonTest : UsefulTestCase() {
|
abstract class AbstractProtoComparisonTest<PROTO_DATA> : UsefulTestCase() {
|
||||||
|
protected abstract fun compileAndGetClasses(sourceDir: File, outputDir: File): Map<ClassId, PROTO_DATA>
|
||||||
|
protected abstract fun difference(oldData: PROTO_DATA, newData: PROTO_DATA): Difference?
|
||||||
|
|
||||||
fun doTest(testDataPath: String) {
|
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 oldClassMap = classesForPrefixedSources(testDir, workingDir, "old")
|
||||||
val newClassFiles = compileFileAndGetClasses(testDataPath, testDir, "new")
|
val newClassMap = classesForPrefixedSources(testDir, workingDir, "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 sb = StringBuilder()
|
val sb = StringBuilder()
|
||||||
val p = Printer(sb)
|
val p = Printer(sb)
|
||||||
|
|
||||||
val oldSetOfNames = oldClassFiles.map { it.name }.toSet()
|
(oldClassMap.keys - newClassMap.keys).sortedBy { it.toString() }.forEach { classId ->
|
||||||
val newSetOfNames = newClassFiles.map { it.name }.toSet()
|
p.println("REMOVED: class $classId")
|
||||||
|
|
||||||
val removedNames = (oldClassMap.keys - newClassMap.keys).map { it.toString() }.sorted()
|
|
||||||
removedNames.forEach {
|
|
||||||
p.println("REMOVED: class $it")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val addedNames = (newClassMap.keys - oldClassMap.keys).map { it.toString() }.sorted()
|
(newClassMap.keys - oldClassMap.keys).sortedBy { it.toString() }.forEach { classId ->
|
||||||
addedNames.forEach {
|
p.println("ADDED: class $classId")
|
||||||
p.println("ADDED: class $it")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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) {
|
if (diff == null) {
|
||||||
p.printDifference(oldClassMap[name]!!, newClassMap[name]!!)
|
p.println("skip $classId")
|
||||||
|
return@forEach
|
||||||
|
}
|
||||||
|
|
||||||
|
val changes = arrayListOf<String>()
|
||||||
|
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())
|
KotlinTestUtils.assertEqualsToFile(File(testDataPath + File.separator + "result.out"), sb.toString())
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun compileFileAndGetClasses(testPath: String, testDir: File, prefix: String): List<File> {
|
private fun classesForPrefixedSources(testDir: File, workingDir: File, prefix: String): Map<ClassId, PROTO_DATA> {
|
||||||
val files = File(testPath).listFiles { it -> it.name.startsWith(prefix) }!!
|
val srcDir = workingDir.createSubDirectory("$prefix/src")
|
||||||
val sourcesDirectory = testDir.createSubDirectory("sources")
|
val outDir = workingDir.createSubDirectory("$prefix/out")
|
||||||
val classesDirectory = testDir.createSubDirectory("$prefix.src")
|
copySourceFiles(testDir, srcDir, prefix)
|
||||||
|
return compileAndGetClasses(srcDir, outDir)
|
||||||
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 Printer.printDifference(oldClass: LocalFileKotlinClass, newClass: LocalFileKotlinClass) {
|
private fun copySourceFiles(sourceDir: File, targetDir: File, prefix: String) {
|
||||||
fun KotlinJvmBinaryClass.readProto(): ProtoMapValue? {
|
for (srcFile in sourceDir.walkMatching { it.name.startsWith(prefix) }) {
|
||||||
assert(classHeader.metadataVersion.isCompatible()) { "Incompatible class ($classHeader): $location" }
|
val targetFile = File(targetDir, srcFile.name.replaceFirst(prefix, "main"))
|
||||||
|
srcFile.copyTo(targetFile)
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val diff = difference(oldClass.readProto() ?: return,
|
|
||||||
newClass.readProto() ?: return)
|
|
||||||
|
|
||||||
val changes = SmartList<String>()
|
|
||||||
|
|
||||||
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 {
|
protected fun File.createSubDirectory(relativePath: String): File =
|
||||||
val directory = File(this, relativePath)
|
File(this, relativePath).apply { mkdirs() }
|
||||||
FileUtil.createDirectory(directory)
|
|
||||||
return directory
|
protected fun File.walkMatching(predicate: (File)->Boolean): Sequence<File> =
|
||||||
}
|
walk().filter { predicate(it) }
|
||||||
}
|
}
|
||||||
|
|||||||
+7
-7
@@ -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 */
|
/** This class is generated by {@link org.jetbrains.kotlin.generators.tests.TestsPackage}. DO NOT MODIFY MANUALLY */
|
||||||
@SuppressWarnings("all")
|
@SuppressWarnings("all")
|
||||||
@RunWith(JUnit3RunnerWithInners.class)
|
@RunWith(JUnit3RunnerWithInners.class)
|
||||||
public class ProtoComparisonTestGenerated extends AbstractProtoComparisonTest {
|
public class JvmProtoComparisonTestGenerated extends AbstractJvmProtoComparisonTest {
|
||||||
@TestMetadata("jps-plugin/testData/comparison/classSignatureChange")
|
@TestMetadata("jps-plugin/testData/comparison/classSignatureChange")
|
||||||
@TestDataPath("$PROJECT_ROOT")
|
@TestDataPath("$PROJECT_ROOT")
|
||||||
@RunWith(JUnit3RunnerWithInners.class)
|
@RunWith(JUnit3RunnerWithInners.class)
|
||||||
public static class ClassSignatureChange extends AbstractProtoComparisonTest {
|
public static class ClassSignatureChange extends AbstractJvmProtoComparisonTest {
|
||||||
public void testAllFilesPresentInClassSignatureChange() throws Exception {
|
public void testAllFilesPresentInClassSignatureChange() throws Exception {
|
||||||
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("jps-plugin/testData/comparison/classSignatureChange"), Pattern.compile("^([^\\.]+)$"), TargetBackend.ANY, true);
|
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")
|
@TestMetadata("jps-plugin/testData/comparison/classPrivateOnlyChange")
|
||||||
@TestDataPath("$PROJECT_ROOT")
|
@TestDataPath("$PROJECT_ROOT")
|
||||||
@RunWith(JUnit3RunnerWithInners.class)
|
@RunWith(JUnit3RunnerWithInners.class)
|
||||||
public static class ClassPrivateOnlyChange extends AbstractProtoComparisonTest {
|
public static class ClassPrivateOnlyChange extends AbstractJvmProtoComparisonTest {
|
||||||
public void testAllFilesPresentInClassPrivateOnlyChange() throws Exception {
|
public void testAllFilesPresentInClassPrivateOnlyChange() throws Exception {
|
||||||
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("jps-plugin/testData/comparison/classPrivateOnlyChange"), Pattern.compile("^([^\\.]+)$"), TargetBackend.ANY, true);
|
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")
|
@TestMetadata("jps-plugin/testData/comparison/classMembersOnlyChanged")
|
||||||
@TestDataPath("$PROJECT_ROOT")
|
@TestDataPath("$PROJECT_ROOT")
|
||||||
@RunWith(JUnit3RunnerWithInners.class)
|
@RunWith(JUnit3RunnerWithInners.class)
|
||||||
public static class ClassMembersOnlyChanged extends AbstractProtoComparisonTest {
|
public static class ClassMembersOnlyChanged extends AbstractJvmProtoComparisonTest {
|
||||||
public void testAllFilesPresentInClassMembersOnlyChanged() throws Exception {
|
public void testAllFilesPresentInClassMembersOnlyChanged() throws Exception {
|
||||||
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("jps-plugin/testData/comparison/classMembersOnlyChanged"), Pattern.compile("^([^\\.]+)$"), TargetBackend.ANY, true);
|
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")
|
@TestMetadata("jps-plugin/testData/comparison/packageMembers")
|
||||||
@TestDataPath("$PROJECT_ROOT")
|
@TestDataPath("$PROJECT_ROOT")
|
||||||
@RunWith(JUnit3RunnerWithInners.class)
|
@RunWith(JUnit3RunnerWithInners.class)
|
||||||
public static class PackageMembers extends AbstractProtoComparisonTest {
|
public static class PackageMembers extends AbstractJvmProtoComparisonTest {
|
||||||
public void testAllFilesPresentInPackageMembers() throws Exception {
|
public void testAllFilesPresentInPackageMembers() throws Exception {
|
||||||
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("jps-plugin/testData/comparison/packageMembers"), Pattern.compile("^([^\\.]+)$"), TargetBackend.ANY, true);
|
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")
|
@TestMetadata("jps-plugin/testData/comparison/unchanged")
|
||||||
@TestDataPath("$PROJECT_ROOT")
|
@TestDataPath("$PROJECT_ROOT")
|
||||||
@RunWith(JUnit3RunnerWithInners.class)
|
@RunWith(JUnit3RunnerWithInners.class)
|
||||||
public static class Unchanged extends AbstractProtoComparisonTest {
|
public static class Unchanged extends AbstractJvmProtoComparisonTest {
|
||||||
public void testAllFilesPresentInUnchanged() throws Exception {
|
public void testAllFilesPresentInUnchanged() throws Exception {
|
||||||
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("jps-plugin/testData/comparison/unchanged"), Pattern.compile("^([^\\.]+)$"), TargetBackend.ANY, true);
|
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")
|
@TestMetadata("jps-plugin/testData/comparison/jvmOnly")
|
||||||
@TestDataPath("$PROJECT_ROOT")
|
@TestDataPath("$PROJECT_ROOT")
|
||||||
@RunWith(JUnit3RunnerWithInners.class)
|
@RunWith(JUnit3RunnerWithInners.class)
|
||||||
public static class JvmOnly extends AbstractProtoComparisonTest {
|
public static class JvmOnly extends AbstractJvmProtoComparisonTest {
|
||||||
public void testAllFilesPresentInJvmOnly() throws Exception {
|
public void testAllFilesPresentInJvmOnly() throws Exception {
|
||||||
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("jps-plugin/testData/comparison/jvmOnly"), Pattern.compile("^([^\\.]+)$"), TargetBackend.ANY, true);
|
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("jps-plugin/testData/comparison/jvmOnly"), Pattern.compile("^([^\\.]+)$"), TargetBackend.ANY, true);
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user