Extract JVM specific proto comparison test

Original commit: 1921950a1b
This commit is contained in:
Alexey Tsvetkov
2017-06-16 18:03:05 +03:00
parent 772224fb63
commit 164d133042
3 changed files with 120 additions and 90 deletions
@@ -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
}
}
}
}
@@ -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) }
}
@@ -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);
}