+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.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<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) {
|
||||
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<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())
|
||||
}
|
||||
|
||||
private fun compileFileAndGetClasses(testPath: String, testDir: File, prefix: String): List<File> {
|
||||
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<ClassId, PROTO_DATA> {
|
||||
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<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 {
|
||||
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<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 */
|
||||
@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);
|
||||
}
|
||||
Reference in New Issue
Block a user