KT-45777: Snapshot Java classes using ASM analysis directly
This is faster than the current approach which creates `JavaClassDescriptor`s, converts them to protos, and then snapshots these protos. - Refactor unit tests to faciliate further changes - moves test data to a directory that matches the tests' package name - moves expected snapshots to a separate directory - adds public and private fields/properties to sample class - Compute changes between ASM-based Java class snapshots - Don't collect members of an added Java class as changes as it's enough to report the name of the added Java class as changed (we also do that for added Kotlin classes and Kotlin/Java removed classes). - Add unit tests for impact analysis in advance - Compute impacted symbols of changed symbols Also do not collect added classes/class members as they don't impact recompilation. -Use ClassId when computing Java class changes It is more precise than JvmClassName, which can be ambiguous around the `$` character (e.g., ClassId "com/example/A$B.C" and "com/example/A.B$C" both have the same JvmClassName "com/example/A$B$C"). - Compute impacted set of changed symbols across Kotlin and Java - Add unit tests for impact analysis across Kotlin and Java - Compute supertypes of Kotlin classes during snapshotting - Handle inner classes when computing list of changed symbols. For the reported symbols, always check all options: class member, inner class, top level class, top level member. Test: IncrementalJavaChangeClasspathSnapshotIT.testAddingInnerClass
This commit is contained in:
committed by
nataliya.valtman
parent
271368ac53
commit
bd7c2ae6d7
Generated
+8
@@ -0,0 +1,8 @@
|
||||
<component name="ProjectDictionaryState">
|
||||
<dictionary name="hungnv">
|
||||
<words>
|
||||
<w>snapshotter</w>
|
||||
<w>snapshotter's</w>
|
||||
</words>
|
||||
</dictionary>
|
||||
</component>
|
||||
@@ -7,7 +7,7 @@ package org.jetbrains.kotlin.incremental
|
||||
|
||||
import com.intellij.util.io.DataExternalizer
|
||||
import org.jetbrains.kotlin.incremental.storage.FqNameExternalizer
|
||||
import org.jetbrains.kotlin.incremental.storage.LinkedHashSetExternalizer
|
||||
import org.jetbrains.kotlin.incremental.storage.SetExternalizer
|
||||
import org.jetbrains.kotlin.incremental.storage.LookupSymbolExternalizer
|
||||
import org.jetbrains.kotlin.name.FqName
|
||||
import java.io.*
|
||||
@@ -24,13 +24,13 @@ sealed class ClasspathChanges : Serializable {
|
||||
private const val serialVersionUID = 0L
|
||||
}
|
||||
|
||||
lateinit var lookupSymbols: LinkedHashSet<LookupSymbol>
|
||||
lateinit var lookupSymbols: Set<LookupSymbol> // Preferably ordered but not required
|
||||
private set
|
||||
|
||||
lateinit var fqNames: LinkedHashSet<FqName>
|
||||
lateinit var fqNames: Set<FqName> // Preferably ordered but not required
|
||||
private set
|
||||
|
||||
constructor(lookupSymbols: LinkedHashSet<LookupSymbol>, fqNames: LinkedHashSet<FqName>) : this() {
|
||||
constructor(lookupSymbols: Set<LookupSymbol>, fqNames: Set<FqName>) : this() {
|
||||
this.lookupSymbols = lookupSymbols
|
||||
this.fqNames = fqNames
|
||||
}
|
||||
@@ -61,14 +61,14 @@ sealed class ClasspathChanges : Serializable {
|
||||
private object ClasspathChangesAvailableExternalizer : DataExternalizer<ClasspathChanges.Available> {
|
||||
|
||||
override fun save(output: DataOutput, classpathChanges: ClasspathChanges.Available) {
|
||||
LinkedHashSetExternalizer(LookupSymbolExternalizer).save(output, classpathChanges.lookupSymbols)
|
||||
LinkedHashSetExternalizer(FqNameExternalizer).save(output, classpathChanges.fqNames)
|
||||
SetExternalizer(LookupSymbolExternalizer).save(output, classpathChanges.lookupSymbols)
|
||||
SetExternalizer(FqNameExternalizer).save(output, classpathChanges.fqNames)
|
||||
}
|
||||
|
||||
override fun read(input: DataInput): ClasspathChanges.Available {
|
||||
return ClasspathChanges.Available(
|
||||
lookupSymbols = LinkedHashSetExternalizer(LookupSymbolExternalizer).read(input),
|
||||
fqNames = LinkedHashSetExternalizer(FqNameExternalizer).read(input)
|
||||
lookupSymbols = SetExternalizer(LookupSymbolExternalizer).read(input),
|
||||
fqNames = SetExternalizer(FqNameExternalizer).read(input)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,10 +27,10 @@ import org.jetbrains.kotlin.cli.common.toBooleanLenient
|
||||
import org.jetbrains.kotlin.incremental.LookupSymbol
|
||||
import org.jetbrains.kotlin.name.ClassId
|
||||
import org.jetbrains.kotlin.name.FqName
|
||||
import org.jetbrains.kotlin.resolve.jvm.JvmClassName
|
||||
import java.io.DataInput
|
||||
import java.io.DataInputStream
|
||||
import java.io.DataOutput
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
* Storage versioning:
|
||||
@@ -114,6 +114,17 @@ object ClassIdExternalizer : DataExternalizer<ClassId> {
|
||||
}
|
||||
}
|
||||
|
||||
object JvmClassNameExternalizer : DataExternalizer<JvmClassName> {
|
||||
|
||||
override fun save(output: DataOutput, jvmClassName: JvmClassName) {
|
||||
output.writeString(jvmClassName.internalName)
|
||||
}
|
||||
|
||||
override fun read(input: DataInput): JvmClassName {
|
||||
return JvmClassName.byInternalName(input.readString())
|
||||
}
|
||||
}
|
||||
|
||||
object ProtoMapValueExternalizer : DataExternalizer<ProtoMapValue> {
|
||||
override fun save(output: DataOutput, value: ProtoMapValue) {
|
||||
output.writeBoolean(value.isPackageFacade)
|
||||
@@ -349,8 +360,8 @@ open class GenericCollectionExternalizer<T, C : Collection<T>>(
|
||||
class ListExternalizer<T>(elementExternalizer: DataExternalizer<T>) :
|
||||
GenericCollectionExternalizer<T, List<T>>(elementExternalizer, { size -> ArrayList(size) })
|
||||
|
||||
class LinkedHashSetExternalizer<T>(elementExternalizer: DataExternalizer<T>) :
|
||||
GenericCollectionExternalizer<T, LinkedHashSet<T>>(elementExternalizer, { size -> LinkedHashSet(size) })
|
||||
class SetExternalizer<T>(elementExternalizer: DataExternalizer<T>) :
|
||||
GenericCollectionExternalizer<T, Set<T>>(elementExternalizer, { size -> LinkedHashSet(size) })
|
||||
|
||||
class LinkedHashMapExternalizer<K, V>(
|
||||
private val keyExternalizer: DataExternalizer<K>,
|
||||
|
||||
@@ -7,13 +7,12 @@ package org.jetbrains.kotlin.incremental
|
||||
|
||||
import org.jetbrains.kotlin.name.ClassId
|
||||
import org.jetbrains.kotlin.name.FqName
|
||||
import org.jetbrains.kotlin.test.KotlinTestUtils
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.rules.TemporaryFolder
|
||||
import java.io.File
|
||||
import javax.tools.ToolProvider
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
class JavaClassNameTest {
|
||||
|
||||
@@ -73,15 +72,7 @@ class JavaClassNameTest {
|
||||
}
|
||||
val classesDir = tmpDir.newFolder()
|
||||
|
||||
val compiler = ToolProvider.getSystemJavaCompiler()
|
||||
compiler.getStandardFileManager(null, null, null).use { fileManager ->
|
||||
val compilationTask = compiler.getTask(
|
||||
null, fileManager, null,
|
||||
listOf("-d", classesDir.path), null,
|
||||
fileManager.getJavaFileObjectsFromFiles(listOf(sourceFile))
|
||||
)
|
||||
assertTrue(compilationTask.call(), "Failed to compile '$className'")
|
||||
}
|
||||
KotlinTestUtils.compileJavaFiles(listOf(sourceFile), listOf("-d", classesDir.path))
|
||||
|
||||
return classesDir.walk().filter { it.isFile }
|
||||
.sortedBy { it.path.substringBefore(".class") }
|
||||
|
||||
+12
@@ -55,6 +55,18 @@ class IncrementalJavaChangeClasspathSnapshotIT : IncrementalJavaChangeDefaultIT(
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testAddingInnerClass() {
|
||||
doTest(
|
||||
"A.kt",
|
||||
{ content: String -> content.substringBeforeLast("}") + " class InnerClass }" },
|
||||
assertResults = {
|
||||
assertTasksExecuted(":lib:compileKotlin", ":app:compileKotlin")
|
||||
assertCompiledKotlinFiles(project.projectDir.getFilesByNames("AAA.kt", "AA.kt", "BB.kt", "A.kt", "B.kt"))
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
class IncrementalJavaChangePreciseIT : IncrementalCompilationJavaChangesBase(usePreciseJavaTracking = true) {
|
||||
|
||||
+103
@@ -0,0 +1,103 @@
|
||||
/*
|
||||
* Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.gradle.incremental
|
||||
|
||||
import org.jetbrains.kotlin.incremental.ClasspathChanges
|
||||
import org.jetbrains.kotlin.incremental.LookupSymbol
|
||||
import org.jetbrains.kotlin.name.ClassId
|
||||
import org.jetbrains.kotlin.name.FqName
|
||||
|
||||
/** Intermediate data to compute [ClasspathChanges] (see [toClasspathChanges]). */
|
||||
class ChangeSet(
|
||||
|
||||
/** Set of changed classes, preferably ordered by not required. */
|
||||
val changedClasses: Set<ClassId>,
|
||||
|
||||
/**
|
||||
* Map from a [ClassId] to the names of changed Java fields/methods or Kotlin properties/functions within that class.
|
||||
*
|
||||
* The map and sets are preferably ordered but not required.
|
||||
*
|
||||
* The [ClassId]s must not appear in [changedClasses] to avoid redundancy.
|
||||
*/
|
||||
val changedClassMembers: Map<ClassId, Set<String>>,
|
||||
|
||||
/** Map from a package name to the names of changed Kotlin top-level properties/functions within that package. */
|
||||
val changedTopLevelMembers: Map<FqName, Set<String>>
|
||||
) {
|
||||
init {
|
||||
check(changedClassMembers.keys.none { it in changedClasses })
|
||||
}
|
||||
|
||||
class Collector {
|
||||
private val changedClasses = mutableSetOf<ClassId>()
|
||||
private val changedClassMembers = mutableMapOf<ClassId, MutableSet<String>>()
|
||||
private val changedTopLevelMembers = mutableMapOf<FqName, MutableSet<String>>()
|
||||
|
||||
fun addChangedClasses(classNames: Collection<ClassId>) = changedClasses.addAll(classNames)
|
||||
|
||||
fun addChangedClass(className: ClassId) = changedClasses.add(className)
|
||||
|
||||
fun addChangedClassMembers(className: ClassId, memberNames: Collection<String>) {
|
||||
if (memberNames.isNotEmpty()) {
|
||||
changedClassMembers.computeIfAbsent(className) { mutableSetOf() }.addAll(memberNames)
|
||||
}
|
||||
}
|
||||
|
||||
fun addChangedClassMember(className: ClassId, memberName: String) = addChangedClassMembers(className, listOf(memberName))
|
||||
|
||||
fun addChangedTopLevelMembers(packageName: FqName, topLevelMembers: Collection<String>) {
|
||||
if (topLevelMembers.isNotEmpty()) {
|
||||
changedTopLevelMembers.computeIfAbsent(packageName) { mutableSetOf() }.addAll(topLevelMembers)
|
||||
}
|
||||
}
|
||||
|
||||
fun addChangedTopLevelMember(packageName: FqName, topLevelMember: String) =
|
||||
addChangedTopLevelMembers(packageName, listOf(topLevelMember))
|
||||
|
||||
fun getChanges(): ChangeSet {
|
||||
// Remove redundancy in the change set first
|
||||
changedClassMembers.keys.intersect(changedClasses).forEach { changedClassMembers.remove(it) }
|
||||
return ChangeSet(changedClasses.toSet(), changedClassMembers.toMap(), changedTopLevelMembers.toMap())
|
||||
}
|
||||
}
|
||||
|
||||
fun isEmpty() = changedClasses.isEmpty() && changedClassMembers.isEmpty() && changedTopLevelMembers.isEmpty()
|
||||
|
||||
operator fun plus(other: ChangeSet) = ChangeSet(
|
||||
changedClasses + other.changedClasses,
|
||||
changedClassMembers + other.changedClassMembers,
|
||||
changedTopLevelMembers + other.changedTopLevelMembers
|
||||
)
|
||||
|
||||
fun toClasspathChanges(): ClasspathChanges.Available {
|
||||
val lookupSymbols = mutableSetOf<LookupSymbol>()
|
||||
val fqNames = mutableSetOf<FqName>()
|
||||
|
||||
changedClasses.forEach {
|
||||
val classFqName = it.asSingleFqName()
|
||||
lookupSymbols.add(LookupSymbol(name = classFqName.shortName().asString(), scope = classFqName.parent().asString()))
|
||||
fqNames.add(classFqName)
|
||||
}
|
||||
|
||||
for ((changedClass, changedClassMembers) in changedClassMembers) {
|
||||
val classFqName = changedClass.asSingleFqName()
|
||||
changedClassMembers.forEach {
|
||||
lookupSymbols.add(LookupSymbol(name = it, scope = classFqName.asString()))
|
||||
}
|
||||
fqNames.add(classFqName)
|
||||
}
|
||||
|
||||
for ((changedPackage, changedTopLevelMembers) in changedTopLevelMembers) {
|
||||
changedTopLevelMembers.forEach {
|
||||
lookupSymbols.add(LookupSymbol(name = it, scope = changedPackage.asString()))
|
||||
}
|
||||
fqNames.add(changedPackage)
|
||||
}
|
||||
|
||||
return ClasspathChanges.Available(lookupSymbols, fqNames)
|
||||
}
|
||||
}
|
||||
+195
-29
@@ -6,12 +6,17 @@
|
||||
package org.jetbrains.kotlin.gradle.incremental
|
||||
|
||||
import com.intellij.openapi.util.io.FileUtil
|
||||
import org.jetbrains.kotlin.gradle.incremental.ImpactAnalysis.computeImpactedSet
|
||||
import org.jetbrains.kotlin.incremental.*
|
||||
import org.jetbrains.kotlin.incremental.storage.FileToCanonicalPathConverter
|
||||
import org.jetbrains.kotlin.metadata.deserialization.TypeTable
|
||||
import org.jetbrains.kotlin.metadata.deserialization.supertypes
|
||||
import org.jetbrains.kotlin.name.ClassId
|
||||
import org.jetbrains.kotlin.name.FqName
|
||||
import org.jetbrains.kotlin.resolve.jvm.JvmClassName
|
||||
import org.jetbrains.kotlin.serialization.deserialization.getClassId
|
||||
import java.io.File
|
||||
import java.util.*
|
||||
import kotlin.collections.LinkedHashMap
|
||||
|
||||
/** Computes [ClasspathChanges] between two [ClasspathSnapshot]s .*/
|
||||
object ClasspathChangesComputer {
|
||||
@@ -101,9 +106,7 @@ object ClasspathChangesComputer {
|
||||
/** Returns `true` if this snapshot file contains a duplicate class with another snapshot file in the given list. */
|
||||
@Suppress("unused", "UNUSED_PARAMETER")
|
||||
private fun File.containsDuplicatesWith(otherSnapshotFiles: List<File>): Boolean {
|
||||
// TODO: Implement and optimize this method
|
||||
// Existing approach (with `kotlin.incremental.useClasspathSnapshot=false`) doesn't seem to handle duplicate classes, so it is
|
||||
// probably not a regression that we are not handling duplicate classes here yet.
|
||||
// FIXME: Implement and optimize this method
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -111,7 +114,22 @@ object ClasspathChangesComputer {
|
||||
val currentClassSnapshots = currentClasspathSnapshot.getNonDuplicateClassSnapshots()
|
||||
val previousClassSnapshots = previousClasspathSnapshot.getNonDuplicateClassSnapshots()
|
||||
|
||||
return compute(currentClassSnapshots, previousClassSnapshots)
|
||||
return computeClassChanges(currentClassSnapshots, previousClassSnapshots)
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all [ClassSnapshot]s in this [ClasspathSnapshot].
|
||||
*
|
||||
* If there are duplicate classes on the classpath, retain only the first one to match the compiler's behavior.
|
||||
*/
|
||||
private fun ClasspathSnapshot.getNonDuplicateClassSnapshots(): List<ClassSnapshot> {
|
||||
val classSnapshots = LinkedHashMap<String, ClassSnapshot>(classpathEntrySnapshots.sumOf { it.classSnapshots.size })
|
||||
for (classpathEntrySnapshot in classpathEntrySnapshots) {
|
||||
for ((unixStyleRelativePath, classSnapshot) in classpathEntrySnapshot.classSnapshots) {
|
||||
classSnapshots.putIfAbsent(unixStyleRelativePath, classSnapshot)
|
||||
}
|
||||
}
|
||||
return classSnapshots.values.toList()
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -119,12 +137,43 @@ object ClasspathChangesComputer {
|
||||
*
|
||||
* Each list must not contain duplicate classes.
|
||||
*/
|
||||
fun compute(currentClassSnapshots: List<ClassSnapshot>, previousClassSnapshots: List<ClassSnapshot>): ClasspathChanges {
|
||||
fun computeClassChanges(currentClassSnapshots: List<ClassSnapshot>, previousClassSnapshots: List<ClassSnapshot>): ClasspathChanges {
|
||||
if (currentClassSnapshots.any { it is ContentHashJavaClassSnapshot }
|
||||
|| previousClassSnapshots.any { it is ContentHashJavaClassSnapshot }) {
|
||||
return ClasspathChanges.NotAvailable.UnableToCompute
|
||||
}
|
||||
|
||||
// Ignore `EmptyJavaClassSnapshot`s as they don't impact the result
|
||||
val currentNonEmptyClassSnapshots = currentClassSnapshots.filter { it !is EmptyJavaClassSnapshot }
|
||||
val previousNonEmptyClassSnapshots = previousClassSnapshots.filter { it !is EmptyJavaClassSnapshot }
|
||||
|
||||
val (currentAsmBasedSnapshots, currentProtoBasedSnapshots) =
|
||||
currentNonEmptyClassSnapshots.partition { it is RegularJavaClassSnapshot }
|
||||
val (previousAsmBasedSnapshots, previousProtoBasedSnapshots) =
|
||||
previousNonEmptyClassSnapshots.partition { it is RegularJavaClassSnapshot }
|
||||
|
||||
val changeSet1 = computeChangesForProtoBasedSnapshots(currentProtoBasedSnapshots, previousProtoBasedSnapshots)
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
val changeSet2 = JavaClassChangesComputer.compute(
|
||||
currentAsmBasedSnapshots as List<RegularJavaClassSnapshot>,
|
||||
previousAsmBasedSnapshots as List<RegularJavaClassSnapshot>
|
||||
)
|
||||
|
||||
val allChanges = changeSet1 + changeSet2
|
||||
if (allChanges.isEmpty()) {
|
||||
return allChanges.toClasspathChanges()
|
||||
}
|
||||
|
||||
val impactedSet = computeImpactedSet(allChanges, previousNonEmptyClassSnapshots)
|
||||
|
||||
return impactedSet.toClasspathChanges()
|
||||
}
|
||||
|
||||
private fun computeChangesForProtoBasedSnapshots(
|
||||
currentClassSnapshots: List<ClassSnapshot>,
|
||||
previousClassSnapshots: List<ClassSnapshot>
|
||||
): ChangeSet {
|
||||
val workingDir =
|
||||
FileUtil.createTempDirectory(this::class.java.simpleName, "_WorkingDir_${UUID.randomUUID()}", /* deleteOnExit */ true)
|
||||
val incrementalJvmCache = IncrementalJvmCache(workingDir, /* targetOutputDir */ null, FileToCanonicalPathConverter)
|
||||
@@ -146,7 +195,7 @@ object ClasspathChangesComputer {
|
||||
)
|
||||
incrementalJvmCache.markDirty(previousSnapshot.classInfo.className)
|
||||
}
|
||||
is RegularJavaClassSnapshot -> {
|
||||
is ProtoBasedJavaClassSnapshot -> {
|
||||
incrementalJvmCache.saveJavaClassProto(
|
||||
source = null,
|
||||
serializedJavaClass = previousSnapshot.serializedJavaClass,
|
||||
@@ -154,10 +203,7 @@ object ClasspathChangesComputer {
|
||||
)
|
||||
incrementalJvmCache.markDirty(JvmClassName.byClassId(previousSnapshot.serializedJavaClass.classId))
|
||||
}
|
||||
is EmptyJavaClassSnapshot -> {
|
||||
// Nothing to process as these classes don't impact the result.
|
||||
}
|
||||
is ContentHashJavaClassSnapshot -> {
|
||||
is RegularJavaClassSnapshot, is ContentHashJavaClassSnapshot, is EmptyJavaClassSnapshot -> {
|
||||
error("Unexpected type (it should have been handled earlier): ${previousSnapshot.javaClass.name}")
|
||||
}
|
||||
}
|
||||
@@ -180,17 +226,14 @@ object ClasspathChangesComputer {
|
||||
changesCollector = changesCollector
|
||||
)
|
||||
}
|
||||
is RegularJavaClassSnapshot -> {
|
||||
is ProtoBasedJavaClassSnapshot -> {
|
||||
incrementalJvmCache.saveJavaClassProto(
|
||||
source = null,
|
||||
serializedJavaClass = currentSnapshot.serializedJavaClass,
|
||||
collector = changesCollector
|
||||
)
|
||||
}
|
||||
is EmptyJavaClassSnapshot -> {
|
||||
// Nothing to process as these classes don't impact the result.
|
||||
}
|
||||
is ContentHashJavaClassSnapshot -> {
|
||||
is RegularJavaClassSnapshot, is ContentHashJavaClassSnapshot, is EmptyJavaClassSnapshot -> {
|
||||
error("Unexpected type (it should have been handled earlier): ${currentSnapshot.javaClass.name}")
|
||||
}
|
||||
}
|
||||
@@ -207,24 +250,147 @@ object ClasspathChangesComputer {
|
||||
val dirtyData = changesCollector.getDirtyData(listOf(incrementalJvmCache), EmptyICReporter)
|
||||
workingDir.deleteRecursively()
|
||||
|
||||
return ClasspathChanges.Available(
|
||||
lookupSymbols = LinkedHashSet(dirtyData.dirtyLookupSymbols),
|
||||
fqNames = LinkedHashSet(dirtyData.dirtyClassesFqNames)
|
||||
)
|
||||
return dirtyData.normalize(currentClassSnapshots, previousClassSnapshots)
|
||||
}
|
||||
|
||||
private fun DirtyData.normalize(currentClassSnapshots: List<ClassSnapshot>, previousClassSnapshots: List<ClassSnapshot>): ChangeSet {
|
||||
val allClassIds = currentClassSnapshots.map { it.getClassId() }.toSet() + previousClassSnapshots.map { it.getClassId() }
|
||||
val fqNameToClassId = LinkedHashMap<FqName, ClassId>(allClassIds.size)
|
||||
allClassIds.forEach { classId ->
|
||||
val fqName = classId.asSingleFqName()
|
||||
check(!fqNameToClassId.contains(fqName)) {
|
||||
"Ambiguous FqName $fqName corresponds to two different `ClassId`s: ${fqNameToClassId[fqName]} and $classId"
|
||||
}
|
||||
fqNameToClassId[fqName] = classId
|
||||
}
|
||||
|
||||
return ChangeSet.Collector().run {
|
||||
dirtyLookupSymbols.forEach {
|
||||
fqNameToClassId[FqName(it.scope)]?.let { classIdOfScope ->
|
||||
// If scope is a class, lookup symbol is a class member and maybe inner class
|
||||
fqNameToClassId[FqName("${it.scope}.${it.name}")]?.let { innerClass ->
|
||||
addChangedClass(innerClass)
|
||||
} ?: addChangedClassMember(classIdOfScope, it.name)
|
||||
return@forEach
|
||||
}
|
||||
|
||||
// scope is a package, so changed symbol is a top-level member and maybe a class
|
||||
val potentialClassFqName = if (it.scope.isEmpty()) FqName(it.name) else FqName("${it.scope}.${it.name}")
|
||||
fqNameToClassId[potentialClassFqName]?.let { classId ->
|
||||
// Lookup symbol is a class
|
||||
addChangedClass(classId)
|
||||
} ?: addChangedTopLevelMember(FqName(it.scope), it.name)
|
||||
}
|
||||
val changes = getChanges()
|
||||
|
||||
// dirtyClassesFqNames should be derived from dirtyLookupSymbols. Double-check that this is the case.
|
||||
val changedFqNames: Set<FqName> =
|
||||
changes.changedClasses.map { it.asSingleFqName() }.toSet() +
|
||||
changes.changedClassMembers.keys.map { it.asSingleFqName() } +
|
||||
changes.changedTopLevelMembers.keys
|
||||
check(dirtyClassesFqNames.toSet() == changedFqNames) {
|
||||
"Two sets differ:\n" +
|
||||
"dirtyClassesFqNames: $dirtyClassesFqNames\n" +
|
||||
"changedFqNames: $changedFqNames"
|
||||
}
|
||||
changes
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun ClassSnapshot.getClassId(): ClassId {
|
||||
return when (this) {
|
||||
is KotlinClassSnapshot -> classInfo.classId
|
||||
is RegularJavaClassSnapshot -> classId
|
||||
is ProtoBasedJavaClassSnapshot -> serializedJavaClass.classId
|
||||
is EmptyJavaClassSnapshot, is ContentHashJavaClassSnapshot -> {
|
||||
error("Unexpected type (it should have been handled earlier): ${javaClass.name}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private object ImpactAnalysis {
|
||||
|
||||
/**
|
||||
* Computes the set of classes/class members that are impacted by the given changes.
|
||||
*
|
||||
* For example, if a superclass has changed, any of its subclasses will be impacted even if it has not changed, and unchanged source
|
||||
* files in the previous compilation that depended on the subclasses will need to be recompiled.
|
||||
*
|
||||
* The returned set is also a [ChangeSet], which includes the given changes plus the impacted ones.
|
||||
*/
|
||||
fun computeImpactedSet(changes: ChangeSet, previousClassSnapshots: List<ClassSnapshot>): ChangeSet {
|
||||
val classIdToSubclasses = getClassIdToSubclassesMap(previousClassSnapshots)
|
||||
|
||||
return ChangeSet.Collector().run {
|
||||
addChangedClasses(findSubclassesInclusive(changes.changedClasses, classIdToSubclasses))
|
||||
for ((changedClass, changedClassMembers) in changes.changedClassMembers) {
|
||||
findSubclassesInclusive(setOf(changedClass), classIdToSubclasses).forEach {
|
||||
addChangedClassMembers(it, changedClassMembers)
|
||||
}
|
||||
}
|
||||
for ((changedPackage, changedClassMembers) in changes.changedTopLevelMembers) {
|
||||
addChangedTopLevelMembers(changedPackage, changedClassMembers)
|
||||
}
|
||||
getChanges()
|
||||
}
|
||||
}
|
||||
|
||||
private fun getClassIdToSubclassesMap(classSnapshots: List<ClassSnapshot>): Map<ClassId, Set<ClassId>> {
|
||||
val classIds = classSnapshots.map { it.getClassId() }
|
||||
val classNameToClassId = classIds.associateBy { JvmClassName.byClassId(it) }
|
||||
val classNameToClassIdResolver = { className: JvmClassName -> classNameToClassId[className] }
|
||||
|
||||
val classIdToSubclasses = mutableMapOf<ClassId, MutableSet<ClassId>>()
|
||||
classSnapshots.forEach { classSnapshot ->
|
||||
val classId = classSnapshot.getClassId()
|
||||
classSnapshot.getSupertypes(classNameToClassIdResolver).forEach { supertype ->
|
||||
// No need to collect supertypes outside the considered class snapshots (e.g., "java/lang/Object")
|
||||
if (supertype in classIds) {
|
||||
classIdToSubclasses.computeIfAbsent(supertype) { mutableSetOf() }.add(classId)
|
||||
}
|
||||
}
|
||||
}
|
||||
return classIdToSubclasses
|
||||
}
|
||||
|
||||
private fun ClassSnapshot.getSupertypes(classIdResolver: (JvmClassName) -> ClassId?): List<ClassId> {
|
||||
return when (this) {
|
||||
is RegularJavaClassSnapshot -> supertypes.mapNotNull {
|
||||
// The following call returns null if supertype is outside the considered class snapshots (e.g., "java/lang/Object").
|
||||
// Use `mapNotNull` as we don't need to collect those supertypes (see getClassIdToSubclassesMap).
|
||||
classIdResolver.invoke(it)
|
||||
}
|
||||
is KotlinClassSnapshot -> supertypes.mapNotNull {
|
||||
// Same as above
|
||||
classIdResolver.invoke(it)
|
||||
}
|
||||
is ProtoBasedJavaClassSnapshot -> {
|
||||
val (proto, nameResolver) = serializedJavaClass.toProtoData()
|
||||
proto.supertypes(TypeTable(proto.typeTable)).map { nameResolver.getClassId(it.className) }
|
||||
}
|
||||
is EmptyJavaClassSnapshot, is ContentHashJavaClassSnapshot -> {
|
||||
error("Unexpected type (it should have been handled earlier): ${javaClass.name}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all [ClassSnapshot]s in this [ClasspathSnapshot].
|
||||
*
|
||||
* If there are duplicate classes on the classpath, retain only the first one to match the compiler's behavior.
|
||||
* Finds direct and indirect subclasses of the given classes. The return set includes both the given classes and their direct and
|
||||
* indirect subclasses.
|
||||
*/
|
||||
private fun ClasspathSnapshot.getNonDuplicateClassSnapshots(): List<ClassSnapshot> {
|
||||
val classSnapshots = LinkedHashMap<String, ClassSnapshot>(classpathEntrySnapshots.sumOf { it.classSnapshots.size })
|
||||
for (classpathEntrySnapshot in classpathEntrySnapshots) {
|
||||
for ((unixStyleRelativePath, classSnapshot) in classpathEntrySnapshot.classSnapshots) {
|
||||
classSnapshots.putIfAbsent(unixStyleRelativePath, classSnapshot)
|
||||
private fun findSubclassesInclusive(classIds: Set<ClassId>, classIdsToSubclasses: Map<ClassId, Set<ClassId>>): Set<ClassId> {
|
||||
val visitedClasses = mutableSetOf<ClassId>()
|
||||
val toVisitClasses = classIds.toMutableSet()
|
||||
while (toVisitClasses.isNotEmpty()) {
|
||||
val nextToVisit = mutableSetOf<ClassId>()
|
||||
toVisitClasses.forEach {
|
||||
nextToVisit.addAll(classIdsToSubclasses[it] ?: emptyList())
|
||||
}
|
||||
visitedClasses.addAll(toVisitClasses)
|
||||
toVisitClasses.clear()
|
||||
toVisitClasses.addAll(nextToVisit - visitedClasses)
|
||||
}
|
||||
return classSnapshots.values.toList()
|
||||
return visitedClasses
|
||||
}
|
||||
}
|
||||
|
||||
+55
-3
@@ -7,6 +7,8 @@ package org.jetbrains.kotlin.gradle.incremental
|
||||
|
||||
import org.jetbrains.kotlin.incremental.KotlinClassInfo
|
||||
import org.jetbrains.kotlin.incremental.SerializedJavaClass
|
||||
import org.jetbrains.kotlin.name.ClassId
|
||||
import org.jetbrains.kotlin.resolve.jvm.JvmClassName
|
||||
|
||||
/** Snapshot of a classpath. It consists of a list of [ClasspathEntrySnapshot]s. */
|
||||
class ClasspathSnapshot(val classpathEntrySnapshots: List<ClasspathEntrySnapshot>)
|
||||
@@ -37,13 +39,63 @@ class ClasspathEntrySnapshot(
|
||||
sealed class ClassSnapshot
|
||||
|
||||
/** [ClassSnapshot] of a Kotlin class. */
|
||||
class KotlinClassSnapshot(val classInfo: KotlinClassInfo) : ClassSnapshot()
|
||||
class KotlinClassSnapshot(
|
||||
val classInfo: KotlinClassInfo,
|
||||
val supertypes: List<JvmClassName>
|
||||
) : ClassSnapshot()
|
||||
|
||||
/** [ClassSnapshot] of a Java class. */
|
||||
sealed class JavaClassSnapshot : ClassSnapshot()
|
||||
|
||||
/** [JavaClassSnapshot] of a typical Java class. */
|
||||
class RegularJavaClassSnapshot(val serializedJavaClass: SerializedJavaClass) : JavaClassSnapshot()
|
||||
class RegularJavaClassSnapshot(
|
||||
|
||||
/** [ClassId] of the class. It is part of the class's ABI ([classAbiExcludingMembers]). */
|
||||
val classId: ClassId,
|
||||
|
||||
/** The superclass and interfaces of the class. It is part of the class's ABI ([classAbiExcludingMembers]). */
|
||||
val supertypes: List<JvmClassName>,
|
||||
|
||||
/** [AbiSnapshot] of the class excluding its fields and methods. */
|
||||
val classAbiExcludingMembers: AbiSnapshot,
|
||||
|
||||
/** [AbiSnapshot]s of the class's fields. */
|
||||
val fieldsAbi: List<AbiSnapshot>,
|
||||
|
||||
/** [AbiSnapshot]s of the class's methods. */
|
||||
val methodsAbi: List<AbiSnapshot>
|
||||
|
||||
) : JavaClassSnapshot() {
|
||||
|
||||
val className by lazy {
|
||||
JvmClassName.byClassId(classId).also {
|
||||
check(it == JvmClassName.byInternalName(classAbiExcludingMembers.name))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** The ABI snapshot of a Java element (e.g., class, field, or method). */
|
||||
open class AbiSnapshot(
|
||||
|
||||
/** The name of the Java element. It is part of the Java element's ABI. */
|
||||
val name: String,
|
||||
|
||||
/** The hash of the Java element's ABI. */
|
||||
val abiHash: Long
|
||||
)
|
||||
|
||||
/** TEST-ONLY: An [AbiSnapshot] that is used for testing only and must not be used in production code. */
|
||||
class AbiSnapshotForTests(
|
||||
name: String,
|
||||
abiHash: Long,
|
||||
|
||||
/** The Java element's ABI, captured in a [String]. */
|
||||
@Suppress("unused") val abiValue: String
|
||||
|
||||
) : AbiSnapshot(name, abiHash)
|
||||
|
||||
/** [JavaClassSnapshot] of a typical Java class which uses protos internally. */
|
||||
class ProtoBasedJavaClassSnapshot(val serializedJavaClass: SerializedJavaClass) : JavaClassSnapshot()
|
||||
|
||||
/**
|
||||
* [JavaClassSnapshot] of a Java class where there is nothing to capture.
|
||||
@@ -58,4 +110,4 @@ object EmptyJavaClassSnapshot : JavaClassSnapshot()
|
||||
* the snapshot instead, so that at least it's still correct when used as an input of the `KotlinCompile` task (when the class contents have
|
||||
* changed, this snapshot will also change).
|
||||
*/
|
||||
class ContentHashJavaClassSnapshot(val contentHash: ByteArray) : JavaClassSnapshot()
|
||||
class ContentHashJavaClassSnapshot(val contentHash: Long) : JavaClassSnapshot()
|
||||
|
||||
+44
-5
@@ -59,10 +59,14 @@ object KotlinClassSnapshotExternalizer : DataExternalizer<KotlinClassSnapshot> {
|
||||
|
||||
override fun save(output: DataOutput, snapshot: KotlinClassSnapshot) {
|
||||
KotlinClassInfoExternalizer.save(output, snapshot.classInfo)
|
||||
ListExternalizer(JvmClassNameExternalizer).save(output, snapshot.supertypes)
|
||||
}
|
||||
|
||||
override fun read(input: DataInput): KotlinClassSnapshot {
|
||||
return KotlinClassSnapshot(classInfo = KotlinClassInfoExternalizer.read(input))
|
||||
return KotlinClassSnapshot(
|
||||
classInfo = KotlinClassInfoExternalizer.read(input),
|
||||
supertypes = ListExternalizer(JvmClassNameExternalizer).read(input)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -97,6 +101,7 @@ object JavaClassSnapshotExternalizer : DataExternalizer<JavaClassSnapshot> {
|
||||
output.writeString(snapshot.javaClass.name)
|
||||
when (snapshot) {
|
||||
is RegularJavaClassSnapshot -> RegularJavaClassSnapshotExternalizer.save(output, snapshot)
|
||||
is ProtoBasedJavaClassSnapshot -> ProtoBasedJavaClassSnapshotExternalizer.save(output, snapshot)
|
||||
is EmptyJavaClassSnapshot -> EmptyJavaClassSnapshotExternalizer.save(output, snapshot)
|
||||
is ContentHashJavaClassSnapshot -> ContentHashJavaClassSnapshotExternalizer.save(output, snapshot)
|
||||
}
|
||||
@@ -105,6 +110,7 @@ object JavaClassSnapshotExternalizer : DataExternalizer<JavaClassSnapshot> {
|
||||
override fun read(input: DataInput): JavaClassSnapshot {
|
||||
return when (val className = input.readString()) {
|
||||
RegularJavaClassSnapshot::class.java.name -> RegularJavaClassSnapshotExternalizer.read(input)
|
||||
ProtoBasedJavaClassSnapshot::class.java.name -> ProtoBasedJavaClassSnapshotExternalizer.read(input)
|
||||
EmptyJavaClassSnapshot::class.java.name -> EmptyJavaClassSnapshotExternalizer.read(input)
|
||||
ContentHashJavaClassSnapshot::class.java.name -> ContentHashJavaClassSnapshotExternalizer.read(input)
|
||||
else -> error("Unrecognized class name: $className")
|
||||
@@ -115,11 +121,44 @@ object JavaClassSnapshotExternalizer : DataExternalizer<JavaClassSnapshot> {
|
||||
object RegularJavaClassSnapshotExternalizer : DataExternalizer<RegularJavaClassSnapshot> {
|
||||
|
||||
override fun save(output: DataOutput, snapshot: RegularJavaClassSnapshot) {
|
||||
JavaClassProtoMapValueExternalizer.save(output, snapshot.serializedJavaClass)
|
||||
ClassIdExternalizer.save(output, snapshot.classId)
|
||||
ListExternalizer(JvmClassNameExternalizer).save(output, snapshot.supertypes)
|
||||
AbiSnapshotExternalizer.save(output, snapshot.classAbiExcludingMembers)
|
||||
ListExternalizer(AbiSnapshotExternalizer).save(output, snapshot.fieldsAbi)
|
||||
ListExternalizer(AbiSnapshotExternalizer).save(output, snapshot.methodsAbi)
|
||||
}
|
||||
|
||||
override fun read(input: DataInput): RegularJavaClassSnapshot {
|
||||
return RegularJavaClassSnapshot(serializedJavaClass = JavaClassProtoMapValueExternalizer.read(input))
|
||||
return RegularJavaClassSnapshot(
|
||||
classId = ClassIdExternalizer.read(input),
|
||||
supertypes = ListExternalizer(JvmClassNameExternalizer).read(input),
|
||||
classAbiExcludingMembers = AbiSnapshotExternalizer.read(input),
|
||||
fieldsAbi = ListExternalizer(AbiSnapshotExternalizer).read(input),
|
||||
methodsAbi = ListExternalizer(AbiSnapshotExternalizer).read(input)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
object AbiSnapshotExternalizer : DataExternalizer<AbiSnapshot> {
|
||||
|
||||
override fun save(output: DataOutput, value: AbiSnapshot) {
|
||||
output.writeString(value.name)
|
||||
LongExternalizer.save(output, value.abiHash)
|
||||
}
|
||||
|
||||
override fun read(input: DataInput): AbiSnapshot {
|
||||
return AbiSnapshot(name = input.readString(), abiHash = LongExternalizer.read(input))
|
||||
}
|
||||
}
|
||||
|
||||
object ProtoBasedJavaClassSnapshotExternalizer : DataExternalizer<ProtoBasedJavaClassSnapshot> {
|
||||
|
||||
override fun save(output: DataOutput, snapshot: ProtoBasedJavaClassSnapshot) {
|
||||
JavaClassProtoMapValueExternalizer.save(output, snapshot.serializedJavaClass)
|
||||
}
|
||||
|
||||
override fun read(input: DataInput): ProtoBasedJavaClassSnapshot {
|
||||
return ProtoBasedJavaClassSnapshot(serializedJavaClass = JavaClassProtoMapValueExternalizer.read(input))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -137,11 +176,11 @@ object EmptyJavaClassSnapshotExternalizer : DataExternalizer<EmptyJavaClassSnaps
|
||||
object ContentHashJavaClassSnapshotExternalizer : DataExternalizer<ContentHashJavaClassSnapshot> {
|
||||
|
||||
override fun save(output: DataOutput, snapshot: ContentHashJavaClassSnapshot) {
|
||||
ByteArrayExternalizer.save(output, snapshot.contentHash)
|
||||
LongExternalizer.save(output, snapshot.contentHash)
|
||||
}
|
||||
|
||||
override fun read(input: DataInput): ContentHashJavaClassSnapshot {
|
||||
return ContentHashJavaClassSnapshot(contentHash = ByteArrayExternalizer.read(input))
|
||||
return ContentHashJavaClassSnapshot(contentHash = LongExternalizer.read(input))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+74
-20
@@ -7,14 +7,20 @@ package org.jetbrains.kotlin.gradle.incremental
|
||||
|
||||
import org.jetbrains.kotlin.incremental.*
|
||||
import org.jetbrains.kotlin.load.java.descriptors.JavaClassDescriptor
|
||||
import org.jetbrains.kotlin.metadata.deserialization.TypeTable
|
||||
import org.jetbrains.kotlin.metadata.deserialization.supertypes
|
||||
import org.jetbrains.kotlin.metadata.jvm.deserialization.JvmProtoBufUtil
|
||||
import org.jetbrains.kotlin.name.ClassId
|
||||
import org.jetbrains.kotlin.resolve.jvm.JvmClassName
|
||||
import org.jetbrains.kotlin.serialization.deserialization.getClassId
|
||||
import org.jetbrains.kotlin.utils.DFS
|
||||
import org.jetbrains.org.objectweb.asm.ClassReader
|
||||
import org.jetbrains.org.objectweb.asm.ClassVisitor
|
||||
import org.jetbrains.org.objectweb.asm.Opcodes
|
||||
import java.io.File
|
||||
import java.security.MessageDigest
|
||||
import java.util.zip.ZipInputStream
|
||||
|
||||
/** Computes a [ClasspathEntrySnapshot] of a classpath entry (directory or jar). */
|
||||
@Suppress("SpellCheckingInspection")
|
||||
object ClasspathEntrySnapshotter {
|
||||
|
||||
private val DEFAULT_CLASS_FILTER = { unixStyleRelativePath: String, isDirectory: Boolean ->
|
||||
@@ -24,7 +30,7 @@ object ClasspathEntrySnapshotter {
|
||||
&& !unixStyleRelativePath.startsWith("meta-inf", ignoreCase = true)
|
||||
}
|
||||
|
||||
fun snapshot(classpathEntry: File): ClasspathEntrySnapshot {
|
||||
fun snapshot(classpathEntry: File, protoBased: Boolean? = null): ClasspathEntrySnapshot {
|
||||
val classes =
|
||||
DirectoryOrJarContentsReader.read(classpathEntry, DEFAULT_CLASS_FILTER)
|
||||
.map { (unixStyleRelativePath, contents) ->
|
||||
@@ -32,7 +38,7 @@ object ClasspathEntrySnapshotter {
|
||||
}
|
||||
|
||||
val snapshots = try {
|
||||
ClassSnapshotter.snapshot(classes)
|
||||
ClassSnapshotter.snapshot(classes, protoBased)
|
||||
} catch (e: Throwable) {
|
||||
if (isKnownProblematicClasspathEntry(classpathEntry)) {
|
||||
classes.map { ContentHashJavaClassSnapshot(it.contents.md5()) }
|
||||
@@ -66,7 +72,6 @@ object ClasspathEntrySnapshotter {
|
||||
}
|
||||
|
||||
/** Creates [ClassSnapshot]s of classes. */
|
||||
@Suppress("SpellCheckingInspection")
|
||||
object ClassSnapshotter {
|
||||
|
||||
/**
|
||||
@@ -75,7 +80,11 @@ object ClassSnapshotter {
|
||||
* Note that for Java (non-Kotlin) classes, creating a [ClassSnapshot] for a nested class will require accessing the outer class (and
|
||||
* possibly vice versa). Therefore, outer classes and nested classes must be passed together in one invocation of this method.
|
||||
*/
|
||||
fun snapshot(classes: List<ClassFileWithContents>): List<ClassSnapshot> {
|
||||
fun snapshot(
|
||||
classes: List<ClassFileWithContents>,
|
||||
protoBased: Boolean? = null,
|
||||
includeDebugInfoInSnapshot: Boolean? = null
|
||||
): List<ClassSnapshot> {
|
||||
// Snapshot Kotlin classes first
|
||||
val kotlinClassSnapshots: Map<ClassFileWithContents, KotlinClassSnapshot?> = classes.associateWith {
|
||||
trySnapshotKotlinClass(it)
|
||||
@@ -83,7 +92,7 @@ object ClassSnapshotter {
|
||||
|
||||
// Snapshot the remaining Java classes in one invocation
|
||||
val javaClasses: List<ClassFileWithContents> = classes.filter { kotlinClassSnapshots[it] == null }
|
||||
val snapshots: List<JavaClassSnapshot> = snapshotJavaClasses(javaClasses)
|
||||
val snapshots: List<JavaClassSnapshot> = snapshotJavaClasses(javaClasses, protoBased, includeDebugInfoInSnapshot)
|
||||
val javaClassSnapshots: Map<ClassFileWithContents, JavaClassSnapshot> = javaClasses.zipToMap(snapshots)
|
||||
|
||||
return classes.map { kotlinClassSnapshots[it] ?: javaClassSnapshots[it]!! }
|
||||
@@ -92,7 +101,33 @@ object ClassSnapshotter {
|
||||
/** Creates [KotlinClassSnapshot] of the given class, or returns `null` if the class is not a Kotlin class. */
|
||||
private fun trySnapshotKotlinClass(clazz: ClassFileWithContents): KotlinClassSnapshot? {
|
||||
return KotlinClassInfo.tryCreateFrom(clazz.contents)?.let {
|
||||
KotlinClassSnapshot(it)
|
||||
KotlinClassSnapshot(it, computeSupertypes(it, clazz.contents))
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Find a faster way to get supertypes without loading protos (e.g., attach to an existing ASM visitor)
|
||||
private fun computeSupertypes(classInfo: KotlinClassInfo, classContents: ByteArray): List<JvmClassName> {
|
||||
return try {
|
||||
val (nameResolver, proto) = JvmProtoBufUtil.readClassDataFrom(classInfo.classHeaderData, classInfo.classHeaderStrings)
|
||||
val supertypeClassIds = proto.supertypes(TypeTable(proto.typeTable)).map { nameResolver.getClassId(it.className) }
|
||||
supertypeClassIds.map { JvmClassName.byClassId(it) }
|
||||
} catch (e: Exception) {
|
||||
// The above method call currently fails on a few classes for some reason:
|
||||
// - org.jetbrains.kotlin.protobuf.InvalidProtocolBufferException: Message was missing required fields.
|
||||
// (Lite runtime could not determine which fields were missing) for SomeClassKt.class
|
||||
// - java.lang.NullPointerException: parseDelimitedFrom(this, EXTENSION_REGISTRY) must not be null for
|
||||
// kotlin-stdlib-1.6.255-SNAPSHOT.jar
|
||||
// Fall back to ASM visitor to get the supertypes.
|
||||
val supertypeClassNames = mutableListOf<String>()
|
||||
ClassReader(classContents).accept(object : ClassVisitor(Opcodes.API_VERSION) {
|
||||
override fun visit(
|
||||
version: Int, access: Int, name: String, signature: String?, superName: String?, interfaces: Array<String>?
|
||||
) {
|
||||
superName?.let { supertypeClassNames.add(it) }
|
||||
interfaces?.let { supertypeClassNames.addAll(it) }
|
||||
}
|
||||
}, ClassReader.SKIP_CODE or ClassReader.SKIP_DEBUG)
|
||||
supertypeClassNames.map { JvmClassName.byInternalName(it) }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -102,9 +137,13 @@ object ClassSnapshotter {
|
||||
* Note that creating a [JavaClassSnapshot] for a nested class will require accessing the outer class (and possibly vice versa).
|
||||
* Therefore, outer classes and nested classes must be passed together in one invocation of this method.
|
||||
*/
|
||||
private fun snapshotJavaClasses(classes: List<ClassFileWithContents>): List<JavaClassSnapshot> {
|
||||
private fun snapshotJavaClasses(
|
||||
classes: List<ClassFileWithContents>,
|
||||
protoBased: Boolean? = null,
|
||||
includeDebugInfoInSnapshot: Boolean? = null
|
||||
): List<JavaClassSnapshot> {
|
||||
val classNames: List<JavaClassName> = classes.map { JavaClassName.compute(it.contents) }
|
||||
val classNameToClassFile: LinkedHashMap<JavaClassName, ClassFileWithContents> = classNames.zipToMap(classes)
|
||||
val classNameToClassFile: Map<JavaClassName, ClassFileWithContents> = classNames.zipToMap(classes)
|
||||
|
||||
// We divide classes into 2 categories:
|
||||
// - Special classes, which includes local, anonymous, or synthetic classes, and their nested classes. These classes can't be
|
||||
@@ -120,17 +159,34 @@ object ClassSnapshotter {
|
||||
} else null
|
||||
}
|
||||
|
||||
// Snapshot the remaining regular classes in one invocation
|
||||
// Snapshot the remaining regular classes
|
||||
val regularClasses: List<JavaClassName> = classNames.filter { specialClassSnapshots[it] == null }
|
||||
val regularClassIds: List<ClassId> = computeJavaClassIds(regularClasses)
|
||||
val regularClassesContents: List<ByteArray> = regularClasses.map { classNameToClassFile[it]!!.contents }
|
||||
val regularClassIds = computeJavaClassIds(regularClasses)
|
||||
val regularClassFiles: List<ClassFileWithContents> = regularClasses.map { classNameToClassFile[it]!! }
|
||||
|
||||
val descriptors: List<JavaClassDescriptor?> = JavaClassDescriptorCreator.create(regularClassIds, regularClassesContents)
|
||||
val snapshots: List<JavaClassSnapshot> = if (protoBased ?: protoBasedDefaultValue) {
|
||||
snapshotJavaClassesProtoBased(regularClassIds, regularClassFiles)
|
||||
} else {
|
||||
regularClassIds.mapIndexed { index, classId ->
|
||||
JavaClassSnapshotter.snapshot(classId, regularClassFiles[index].contents, includeDebugInfoInSnapshot)
|
||||
}
|
||||
}
|
||||
val regularClassSnapshots: Map<JavaClassName, JavaClassSnapshot> = regularClasses.zipToMap(snapshots)
|
||||
|
||||
return classNames.map { specialClassSnapshots[it] ?: regularClassSnapshots[it]!! }
|
||||
}
|
||||
|
||||
private fun snapshotJavaClassesProtoBased(
|
||||
classIds: List<ClassId>,
|
||||
classFilesWithContents: List<ClassFileWithContents>
|
||||
): List<JavaClassSnapshot> {
|
||||
val classesContents = classFilesWithContents.map { it.contents }
|
||||
val descriptors: List<JavaClassDescriptor?> = JavaClassDescriptorCreator.create(classIds, classesContents)
|
||||
val snapshots: List<JavaClassSnapshot> = descriptors.mapIndexed { index, descriptor ->
|
||||
val classFileWithContents = classNameToClassFile[regularClasses[index]]!!
|
||||
val classFileWithContents = classFilesWithContents[index]
|
||||
if (descriptor != null) {
|
||||
try {
|
||||
RegularJavaClassSnapshot(descriptor.toSerializedJavaClass())
|
||||
ProtoBasedJavaClassSnapshot(descriptor.toSerializedJavaClass())
|
||||
} catch (e: Throwable) {
|
||||
if (isKnownExceptionWhenReadingDescriptor(e)) {
|
||||
ContentHashJavaClassSnapshot(classFileWithContents.contents.md5())
|
||||
@@ -147,9 +203,7 @@ object ClassSnapshotter {
|
||||
}
|
||||
}
|
||||
}
|
||||
val regularClassSnapshots: LinkedHashMap<JavaClassName, JavaClassSnapshot> = regularClasses.zipToMap(snapshots)
|
||||
|
||||
return classNames.map { specialClassSnapshots[it] ?: regularClassSnapshots[it]!! }
|
||||
return snapshots
|
||||
}
|
||||
|
||||
/** Returns local, anonymous, or synthetic classes, and their nested classes. */
|
||||
@@ -300,4 +354,4 @@ private fun <K, V> List<K>.zipToMap(other: List<V>): LinkedHashMap<K, V> {
|
||||
return map
|
||||
}
|
||||
|
||||
private fun ByteArray.md5(): ByteArray = MessageDigest.getInstance("MD5").digest(this)
|
||||
private const val protoBasedDefaultValue = false
|
||||
+86
@@ -0,0 +1,86 @@
|
||||
/*
|
||||
* Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.gradle.incremental
|
||||
|
||||
import org.jetbrains.kotlin.name.ClassId
|
||||
import org.jetbrains.kotlin.resolve.jvm.JvmClassName
|
||||
import org.jetbrains.kotlin.resolve.sam.SAM_LOOKUP_NAME
|
||||
|
||||
/** Computes [ChangeSet] between two lists of [JavaClassSnapshot]s .*/
|
||||
object JavaClassChangesComputer {
|
||||
|
||||
/**
|
||||
* Computes [ChangeSet] between two lists of [JavaClassSnapshot]s.
|
||||
*
|
||||
* Each list must not contain duplicate classes (having the same [JvmClassName]/[ClassId]).
|
||||
*/
|
||||
fun compute(
|
||||
currentJavaClassSnapshots: List<RegularJavaClassSnapshot>,
|
||||
previousJavaClassSnapshots: List<RegularJavaClassSnapshot>
|
||||
): ChangeSet {
|
||||
val currentClasses: Map<ClassId, RegularJavaClassSnapshot> = currentJavaClassSnapshots.associateBy { it.classId }
|
||||
val previousClasses: Map<ClassId, RegularJavaClassSnapshot> = previousJavaClassSnapshots.associateBy { it.classId }
|
||||
|
||||
// No need to collect added classes as they don't impact recompilation
|
||||
val removedClasses = previousClasses.keys - currentClasses.keys
|
||||
val unchangedOrModifiedClasses = previousClasses.keys - removedClasses
|
||||
|
||||
return ChangeSet.Collector().run {
|
||||
addChangedClasses(removedClasses)
|
||||
unchangedOrModifiedClasses.forEach {
|
||||
collectClassChanges(currentClasses[it]!!, previousClasses[it]!!, this)
|
||||
}
|
||||
getChanges()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Collects changes between two [JavaClassSnapshot]s.
|
||||
*
|
||||
* The two classes must have the same [ClassId].
|
||||
*/
|
||||
private fun collectClassChanges(
|
||||
currentClassSnapshot: RegularJavaClassSnapshot,
|
||||
previousClassSnapshot: RegularJavaClassSnapshot,
|
||||
changes: ChangeSet.Collector
|
||||
) {
|
||||
val classId = currentClassSnapshot.classId.also { check(it == previousClassSnapshot.classId) }
|
||||
if (currentClassSnapshot.classAbiExcludingMembers.abiHash != previousClassSnapshot.classAbiExcludingMembers.abiHash) {
|
||||
changes.addChangedClass(classId)
|
||||
} else {
|
||||
collectClassMemberChanges(classId, currentClassSnapshot.fieldsAbi, previousClassSnapshot.fieldsAbi, changes)
|
||||
collectClassMemberChanges(classId, currentClassSnapshot.methodsAbi, previousClassSnapshot.methodsAbi, changes)
|
||||
}
|
||||
}
|
||||
|
||||
/** Collects changes between two lists of fields/methods within a class. */
|
||||
private fun collectClassMemberChanges(
|
||||
classId: ClassId,
|
||||
currentMemberSnapshots: List<AbiSnapshot>,
|
||||
previousMemberSnapshots: List<AbiSnapshot>,
|
||||
changes: ChangeSet.Collector
|
||||
) {
|
||||
val currentMemberHashes: Set<Long> = currentMemberSnapshots.map { it.abiHash }.toSet()
|
||||
val previousMemberHashes: Map<Long, AbiSnapshot> = previousMemberSnapshots.associateBy { it.abiHash }
|
||||
|
||||
val addedMembers = currentMemberHashes - previousMemberHashes.keys
|
||||
val removedMembers = previousMemberHashes.keys - currentMemberHashes
|
||||
|
||||
// Note:
|
||||
// - No need to collect added members as they don't impact recompilation.
|
||||
// - Modified members have a current version and a previous version. The current version will appear in addedMembers (which will
|
||||
// not be collected), and the previous version will appear in removedMembers (which will be collected).
|
||||
// - Multiple members may have the same name (but never the same signature (name + desc) or ABI hash). It's okay to report the
|
||||
// same name multiple times.
|
||||
changes.addChangedClassMembers(classId, removedMembers.map { previousMemberHashes[it]!!.name })
|
||||
|
||||
// TODO: Check whether the condition to add SAM_LOOKUP_NAME below is too broad, and correct it if necessary.
|
||||
// Currently, it matches the logic in ChangesCollector.getDirtyData in buildUtil.kt.
|
||||
if (addedMembers.isNotEmpty() || removedMembers.isNotEmpty()) {
|
||||
changes.addChangedClassMember(classId, SAM_LOOKUP_NAME.asString())
|
||||
}
|
||||
}
|
||||
}
|
||||
+87
@@ -0,0 +1,87 @@
|
||||
/*
|
||||
* Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.gradle.incremental
|
||||
|
||||
import com.google.gson.GsonBuilder
|
||||
import org.jetbrains.kotlin.incremental.md5
|
||||
import org.jetbrains.kotlin.name.ClassId
|
||||
import org.jetbrains.kotlin.resolve.jvm.JvmClassName
|
||||
import org.jetbrains.org.objectweb.asm.ClassReader
|
||||
import org.jetbrains.org.objectweb.asm.Opcodes
|
||||
import org.jetbrains.org.objectweb.asm.tree.ClassNode
|
||||
|
||||
/** Computes a [JavaClassSnapshot] of a Java class. */
|
||||
object JavaClassSnapshotter {
|
||||
|
||||
fun snapshot(classId: ClassId, classContents: ByteArray, includeDebugInfoInSnapshot: Boolean? = null): JavaClassSnapshot {
|
||||
// We will extract ABI information from the given class and store it into the `abiClass` variable.
|
||||
// It is acceptable to collect more info than required, but it is incorrect to collect less info than required.
|
||||
// There are 2 approaches:
|
||||
// 1. Collect ABI info directly. The collected info must be exhaustive (now and in the future when there are updates to Java/ASM).
|
||||
// 2. Collect all info and remove non-ABI info. The removed info should be exhaustive, but even if it's not, it is still
|
||||
// acceptable.
|
||||
// In the following, we will use the second approach as it is safer.
|
||||
val abiClass = ClassNode()
|
||||
|
||||
// First, collect all info.
|
||||
// Note the parsing options passed to ClassReader:
|
||||
// - SKIP_CODE is set as method bodies will not be part of the ABI of the class.
|
||||
// - SKIP_DEBUG is not set as it would skip method parameters, which may be used by annotation processors like Room.
|
||||
// - SKIP_FRAMES and EXPAND_FRAMES are not relevant when SKIP_CODE is set.
|
||||
val classReader = ClassReader(classContents)
|
||||
classReader.accept(abiClass, ClassReader.SKIP_CODE)
|
||||
|
||||
// Then, remove non-ABI info:
|
||||
// - Method bodies have already been removed (see SKIP_CODE above).
|
||||
// - If the class is private, its snapshot will be empty. Otherwise, remove its private fields and methods.
|
||||
if (abiClass.access.isPrivate()) {
|
||||
return EmptyJavaClassSnapshot
|
||||
}
|
||||
abiClass.fields.removeIf { it.access.isPrivate() }
|
||||
abiClass.methods.removeIf { it.access.isPrivate() }
|
||||
|
||||
// Sort fields and methods as their order is not important (we still use List instead of Set as we want the serialized snapshot to
|
||||
// be deterministic).
|
||||
abiClass.fields.sortWith(compareBy({ it.name }, { it.desc }))
|
||||
abiClass.methods.sortWith(compareBy({ it.name }, { it.desc }))
|
||||
|
||||
val supertypes = (listOf(abiClass.superName) + abiClass.interfaces.toList()).map { JvmClassName.byInternalName(it) }
|
||||
|
||||
val fieldsAbi = abiClass.fields.map { snapshotJavaElement(it, it.name, includeDebugInfoInSnapshot) }
|
||||
val methodsAbi = abiClass.methods.map { snapshotJavaElement(it, it.name, includeDebugInfoInSnapshot) }
|
||||
|
||||
abiClass.fields.clear()
|
||||
abiClass.methods.clear()
|
||||
val classAbiExcludingMembers = abiClass.let { snapshotJavaElement(it, it.name, includeDebugInfoInSnapshot) }
|
||||
|
||||
return RegularJavaClassSnapshot(classId, supertypes, classAbiExcludingMembers, fieldsAbi, methodsAbi)
|
||||
}
|
||||
|
||||
private fun Int.isPrivate() = (this and Opcodes.ACC_PRIVATE) != 0
|
||||
|
||||
private val gson by lazy {
|
||||
// Use serializeSpecialFloatingPointValues() to avoid
|
||||
// "java.lang.IllegalArgumentException: NaN is not a valid double value as per JSON specification. To override this behavior, use
|
||||
// GsonBuilder.serializeSpecialFloatingPointValues() method."
|
||||
// on jars such as ~/.gradle/kotlin-build-dependencies/repo/kotlin.build/ideaIC/203.8084.24/artifacts/lib/rhino-1.7.12.jar.
|
||||
GsonBuilder()
|
||||
.serializeSpecialFloatingPointValues()
|
||||
.setPrettyPrinting()
|
||||
.create()
|
||||
}
|
||||
|
||||
private fun snapshotJavaElement(javaElement: Any, javaElementName: String, includeDebugInfoInSnapshot: Boolean? = null): AbiSnapshot {
|
||||
// TODO: Optimize this method later if necessary. Currently we focus on correctness first.
|
||||
val abiValue = gson.toJson(javaElement)
|
||||
val abiHash = abiValue.toByteArray().md5()
|
||||
|
||||
return if (includeDebugInfoInSnapshot == true) {
|
||||
AbiSnapshotForTests(javaElementName, abiHash, abiValue)
|
||||
} else {
|
||||
AbiSnapshot(javaElementName, abiHash)
|
||||
}
|
||||
}
|
||||
}
|
||||
+3
-4
@@ -8,9 +8,9 @@ package org.jetbrains.kotlin.gradle.tasks
|
||||
import org.gradle.api.GradleException
|
||||
import org.gradle.api.Project
|
||||
import org.gradle.api.Task
|
||||
import org.gradle.api.attributes.Attribute
|
||||
import org.gradle.api.file.*
|
||||
import org.gradle.api.invocation.Gradle
|
||||
import org.gradle.api.attributes.Attribute
|
||||
import org.gradle.api.logging.Logger
|
||||
import org.gradle.api.model.ObjectFactory
|
||||
import org.gradle.api.model.ReplacedBy
|
||||
@@ -53,15 +53,14 @@ import org.jetbrains.kotlin.gradle.plugin.statistics.KotlinBuildStatsService
|
||||
import org.jetbrains.kotlin.gradle.report.ReportingSettings
|
||||
import org.jetbrains.kotlin.gradle.targets.js.ir.isProduceUnzippedKlib
|
||||
import org.jetbrains.kotlin.gradle.utils.*
|
||||
import org.jetbrains.kotlin.incremental.ClasspathChanges
|
||||
import org.jetbrains.kotlin.incremental.ChangedFiles
|
||||
import org.jetbrains.kotlin.incremental.ClasspathChanges
|
||||
import org.jetbrains.kotlin.incremental.IncrementalCompilerRunner
|
||||
import org.jetbrains.kotlin.library.impl.isKotlinLibrary
|
||||
import org.jetbrains.kotlin.statistics.metrics.BooleanMetrics
|
||||
import org.jetbrains.kotlin.utils.JsLibraryUtils
|
||||
import org.jetbrains.kotlin.utils.addToStdlib.cast
|
||||
import java.io.File
|
||||
import java.util.LinkedHashSet
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
import javax.inject.Inject
|
||||
|
||||
@@ -844,7 +843,7 @@ abstract class KotlinCompile @Inject constructor(
|
||||
private fun getClasspathChanges(inputChanges: InputChanges): ClasspathChanges {
|
||||
val fileChanges = inputChanges.getFileChanges(classpathSnapshotProperties.classpathSnapshot).toList()
|
||||
return if (fileChanges.isEmpty()) {
|
||||
ClasspathChanges.Available(LinkedHashSet(), LinkedHashSet())
|
||||
ClasspathChanges.Available(emptySet(), emptySet())
|
||||
} else {
|
||||
val previousClasspathEntrySnapshotFiles = getPreviousClasspathEntrySnapshotFiles()
|
||||
if (previousClasspathEntrySnapshotFiles.isEmpty()) {
|
||||
|
||||
+260
-131
@@ -5,16 +5,18 @@
|
||||
|
||||
package org.jetbrains.kotlin.gradle.incremental
|
||||
|
||||
import org.jetbrains.kotlin.gradle.incremental.ClasspathSnapshotTestCommon.*
|
||||
import org.jetbrains.kotlin.gradle.incremental.ClasspathSnapshotTestCommon.Util.compileAll
|
||||
import org.jetbrains.kotlin.gradle.incremental.ClasspathSnapshotTestCommon.Util.snapshot
|
||||
import org.jetbrains.kotlin.gradle.incremental.ClasspathSnapshotTestCommon.Util.snapshotAll
|
||||
import org.jetbrains.kotlin.incremental.ClasspathChanges
|
||||
import org.jetbrains.kotlin.incremental.LookupSymbol
|
||||
import org.jetbrains.kotlin.name.FqName
|
||||
import org.jetbrains.kotlin.resolve.sam.SAM_LOOKUP_NAME
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Test
|
||||
import org.junit.rules.TemporaryFolder
|
||||
import org.junit.runner.RunWith
|
||||
import org.junit.runners.Parameterized
|
||||
import java.io.File
|
||||
import kotlin.test.fail
|
||||
|
||||
abstract class ClasspathChangesComputerTest : ClasspathSnapshotTestCommon() {
|
||||
|
||||
@@ -25,184 +27,311 @@ abstract class ClasspathChangesComputerTest : ClasspathSnapshotTestCommon() {
|
||||
// - adding an annotation
|
||||
|
||||
@Test
|
||||
abstract fun testSingleClass_changePublicMethodSignature()
|
||||
abstract fun testAbiChange_changePublicMethodSignature()
|
||||
|
||||
@Test
|
||||
abstract fun testSingleClass_changeMethodImplementation()
|
||||
abstract fun testNonAbiChange_changeMethodImplementation()
|
||||
|
||||
@Test
|
||||
abstract fun testMultipleClasses()
|
||||
abstract fun testVariousAbiChanges()
|
||||
|
||||
@Test
|
||||
abstract fun testImpactAnalysis()
|
||||
}
|
||||
|
||||
class KotlinClassesClasspathChangesComputerTest : ClasspathChangesComputerTest() {
|
||||
class KotlinOnlyClasspathChangesComputerTest : ClasspathChangesComputerTest() {
|
||||
|
||||
@Test
|
||||
override fun testSingleClass_changePublicMethodSignature() {
|
||||
override fun testAbiChange_changePublicMethodSignature() {
|
||||
val sourceFile = SimpleKotlinClass(tmpDir)
|
||||
val previousSnapshot = sourceFile.compileAndSnapshot()
|
||||
val currentSnapshot = sourceFile.changePublicMethodSignature().compileAndSnapshot()
|
||||
val changes = ClasspathChangesComputer.compute(listOf(currentSnapshot), listOf(previousSnapshot)).normalize()
|
||||
val changes = ClasspathChangesComputer.computeClassChanges(listOf(currentSnapshot), listOf(previousSnapshot)).normalize()
|
||||
|
||||
assertEquals(
|
||||
Changes(
|
||||
lookupSymbols = setOf(
|
||||
LookupSymbol(name = SAM_LOOKUP_NAME.asString(), scope = "com.example.SimpleKotlinClass"),
|
||||
LookupSymbol(name = "changedPublicMethod", scope = "com.example.SimpleKotlinClass"),
|
||||
LookupSymbol(name = "publicMethod", scope = "com.example.SimpleKotlinClass")
|
||||
),
|
||||
fqNames = setOf(
|
||||
FqName("com.example.SimpleKotlinClass")
|
||||
),
|
||||
Changes(
|
||||
lookupSymbols = setOf(
|
||||
LookupSymbol(name = "publicFunction", scope = "com.example.SimpleKotlinClass"),
|
||||
LookupSymbol(name = SAM_LOOKUP_NAME.asString(), scope = "com.example.SimpleKotlinClass")
|
||||
),
|
||||
changes
|
||||
)
|
||||
fqNames = setOf("com.example.SimpleKotlinClass")
|
||||
).assertEquals(changes)
|
||||
}
|
||||
|
||||
@Test
|
||||
override fun testSingleClass_changeMethodImplementation() {
|
||||
override fun testNonAbiChange_changeMethodImplementation() {
|
||||
val sourceFile = SimpleKotlinClass(tmpDir)
|
||||
val previousSnapshot = sourceFile.compileAndSnapshot()
|
||||
val currentSnapshot = sourceFile.changeMethodImplementation().compileAndSnapshot()
|
||||
val changes = ClasspathChangesComputer.compute(listOf(currentSnapshot), listOf(previousSnapshot)).normalize()
|
||||
val changes = ClasspathChangesComputer.computeClassChanges(listOf(currentSnapshot), listOf(previousSnapshot)).normalize()
|
||||
|
||||
assertEquals(Changes(emptySet(), emptySet()), changes)
|
||||
Changes(emptySet(), emptySet()).assertEquals(changes)
|
||||
}
|
||||
|
||||
@Test
|
||||
override fun testMultipleClasses() {
|
||||
val classpathSourceDir = File(testDataDir, "../ClasspathChangesComputerTest/testMultipleClasses/src/kotlin").canonicalFile
|
||||
override fun testVariousAbiChanges() {
|
||||
val classpathSourceDir = File(testDataDir, "../ClasspathChangesComputerTest/testVariousAbiChanges/src/kotlin").canonicalFile
|
||||
val currentSnapshot = snapshotClasspath(File(classpathSourceDir, "current-classpath"), tmpDir)
|
||||
val previousSnapshot = snapshotClasspath(File(classpathSourceDir, "previous-classpath"), tmpDir)
|
||||
val changes = ClasspathChangesComputer.compute(currentSnapshot, previousSnapshot).normalize()
|
||||
|
||||
assertEquals(
|
||||
Changes(
|
||||
lookupSymbols = setOf(
|
||||
LookupSymbol(name = SAM_LOOKUP_NAME.asString(), scope = "com.example.B"),
|
||||
LookupSymbol(name = "b2", scope = "com.example.B"),
|
||||
LookupSymbol(name = "b3", scope = "com.example.B"),
|
||||
LookupSymbol(name = "b4", scope = "com.example.B"),
|
||||
LookupSymbol(name = "C", scope = "com.example"),
|
||||
LookupSymbol(name = "D", scope = "com.example"),
|
||||
LookupSymbol(name = SAM_LOOKUP_NAME.asString(), scope = "com.example"),
|
||||
LookupSymbol(name = "topLevelFuncB", scope = "com.example"),
|
||||
LookupSymbol(name = "topLevelFuncC", scope = "com.example"),
|
||||
LookupSymbol(name = "topLevelFuncD", scope = "com.example"),
|
||||
LookupSymbol(name = "topLevelFuncInCKtMovedToDKt", scope = "com.example"),
|
||||
LookupSymbol(name = "CKt", scope = "com.example"),
|
||||
),
|
||||
fqNames = setOf(
|
||||
FqName("com.example.B"),
|
||||
FqName("com.example.C"),
|
||||
FqName("com.example.D"),
|
||||
FqName("com.example"),
|
||||
FqName("com.example.CKt"),
|
||||
)
|
||||
Changes(
|
||||
lookupSymbols = setOf(
|
||||
// ModifiedClassUnchangedMembers
|
||||
LookupSymbol(name = "ModifiedClassUnchangedMembers", scope = "com.example"),
|
||||
|
||||
// ModifiedClassChangedMembers
|
||||
LookupSymbol(name = "modifiedProperty", scope = "com.example.ModifiedClassChangedMembers"),
|
||||
LookupSymbol(name = "addedProperty", scope = "com.example.ModifiedClassChangedMembers"),
|
||||
LookupSymbol(name = "removedProperty", scope = "com.example.ModifiedClassChangedMembers"),
|
||||
LookupSymbol(name = "modifiedFunction", scope = "com.example.ModifiedClassChangedMembers"),
|
||||
LookupSymbol(name = "addedFunction", scope = "com.example.ModifiedClassChangedMembers"),
|
||||
LookupSymbol(name = "removedFunction", scope = "com.example.ModifiedClassChangedMembers"),
|
||||
LookupSymbol(name = SAM_LOOKUP_NAME.asString(), scope = "com.example.ModifiedClassChangedMembers"),
|
||||
|
||||
// AddedClass
|
||||
LookupSymbol(name = "AddedClass", scope = "com.example"),
|
||||
|
||||
// RemovedClass
|
||||
LookupSymbol(name = "RemovedClass", scope = "com.example"),
|
||||
|
||||
// Top-level properties and functions
|
||||
LookupSymbol(name = "modifiedTopLevelProperty", scope = "com.example"),
|
||||
LookupSymbol(name = "addedTopLevelProperty", scope = "com.example"),
|
||||
LookupSymbol(name = "removedTopLevelProperty", scope = "com.example"),
|
||||
LookupSymbol(name = "movedTopLevelProperty", scope = "com.example"),
|
||||
LookupSymbol(name = "modifiedTopLevelFunction", scope = "com.example"),
|
||||
LookupSymbol(name = "addedTopLevelFunction", scope = "com.example"),
|
||||
LookupSymbol(name = "removedTopLevelFunction", scope = "com.example"),
|
||||
LookupSymbol(name = "movedTopLevelFunction", scope = "com.example"),
|
||||
LookupSymbol(name = SAM_LOOKUP_NAME.asString(), scope = "com.example")
|
||||
),
|
||||
changes
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
class JavaClassesClasspathChangesComputerTest : ClasspathChangesComputerTest() {
|
||||
|
||||
@Test
|
||||
override fun testSingleClass_changePublicMethodSignature() {
|
||||
val sourceFile = SimpleJavaClass(tmpDir)
|
||||
val previousSnapshot = sourceFile.compileAndSnapshot()
|
||||
val currentSnapshot = sourceFile.changePublicMethodSignature().compileAndSnapshot()
|
||||
val changes = ClasspathChangesComputer.compute(listOf(currentSnapshot), listOf(previousSnapshot)).normalize()
|
||||
|
||||
assertEquals(
|
||||
Changes(
|
||||
lookupSymbols = setOf(
|
||||
LookupSymbol(name = SAM_LOOKUP_NAME.asString(), scope = "com.example.SimpleJavaClass"),
|
||||
LookupSymbol(name = "changedPublicMethod", scope = "com.example.SimpleJavaClass"),
|
||||
LookupSymbol(name = "publicMethod", scope = "com.example.SimpleJavaClass")
|
||||
),
|
||||
fqNames = setOf(
|
||||
FqName("com.example.SimpleJavaClass")
|
||||
),
|
||||
),
|
||||
changes
|
||||
)
|
||||
fqNames = setOf(
|
||||
"com.example.ModifiedClassUnchangedMembers",
|
||||
"com.example.ModifiedClassChangedMembers",
|
||||
"com.example.AddedClass",
|
||||
"com.example.RemovedClass",
|
||||
"com.example"
|
||||
)
|
||||
).assertEquals(changes)
|
||||
}
|
||||
|
||||
@Test
|
||||
override fun testSingleClass_changeMethodImplementation() {
|
||||
val sourceFile = SimpleJavaClass(tmpDir)
|
||||
val previousSnapshot = sourceFile.compileAndSnapshot()
|
||||
val currentSnapshot = sourceFile.changeMethodImplementation().compileAndSnapshot()
|
||||
val changes = ClasspathChangesComputer.compute(listOf(currentSnapshot), listOf(previousSnapshot)).normalize()
|
||||
|
||||
assertEquals(Changes(emptySet(), emptySet()), changes)
|
||||
}
|
||||
|
||||
@Test
|
||||
override fun testMultipleClasses() {
|
||||
val classpathSourceDir = File(testDataDir, "../ClasspathChangesComputerTest/testMultipleClasses/src/java").canonicalFile
|
||||
override fun testImpactAnalysis() {
|
||||
val classpathSourceDir =
|
||||
File(testDataDir, "../ClasspathChangesComputerTest/testImpactAnalysis_KotlinOrJava/src/kotlin").canonicalFile
|
||||
val currentSnapshot = snapshotClasspath(File(classpathSourceDir, "current-classpath"), tmpDir)
|
||||
val previousSnapshot = snapshotClasspath(File(classpathSourceDir, "previous-classpath"), tmpDir)
|
||||
val changes = ClasspathChangesComputer.compute(currentSnapshot, previousSnapshot).normalize()
|
||||
|
||||
assertEquals(
|
||||
Changes(
|
||||
lookupSymbols = setOf(
|
||||
LookupSymbol(name = SAM_LOOKUP_NAME.asString(), scope = "com.example.B"),
|
||||
LookupSymbol(name = "b2", scope = "com.example.B"),
|
||||
LookupSymbol(name = "b3", scope = "com.example.B"),
|
||||
LookupSymbol(name = "b4", scope = "com.example.B"),
|
||||
LookupSymbol(name = "C", scope = "com.example"),
|
||||
LookupSymbol(name = "D", scope = "com.example"),
|
||||
LookupSymbol(name = SAM_LOOKUP_NAME.asString(), scope = "com.example.D"),
|
||||
LookupSymbol(name = "<init>", scope = "com.example.D"),
|
||||
LookupSymbol(name = "d", scope = "com.example.D")
|
||||
),
|
||||
fqNames = setOf(
|
||||
FqName("com.example.B"),
|
||||
FqName("com.example.C"),
|
||||
FqName("com.example.D")
|
||||
)
|
||||
Changes(
|
||||
lookupSymbols = setOf(
|
||||
LookupSymbol(name = "changedProperty", scope = "com.example.ChangedSuperClass"),
|
||||
LookupSymbol(name = "changedProperty", scope = "com.example.SubClass"),
|
||||
LookupSymbol(name = "changedProperty", scope = "com.example.SubSubClass"),
|
||||
LookupSymbol(name = "changedFunction", scope = "com.example.ChangedSuperClass"),
|
||||
LookupSymbol(name = "changedFunction", scope = "com.example.SubClass"),
|
||||
LookupSymbol(name = "changedFunction", scope = "com.example.SubSubClass"),
|
||||
LookupSymbol(name = SAM_LOOKUP_NAME.asString(), scope = "com.example.ChangedSuperClass"),
|
||||
LookupSymbol(name = SAM_LOOKUP_NAME.asString(), scope = "com.example.SubClass"),
|
||||
LookupSymbol(name = SAM_LOOKUP_NAME.asString(), scope = "com.example.SubSubClass")
|
||||
),
|
||||
changes
|
||||
)
|
||||
fqNames = setOf(
|
||||
"com.example.ChangedSuperClass",
|
||||
"com.example.SubClass",
|
||||
"com.example.SubSubClass"
|
||||
)
|
||||
).assertEquals(changes)
|
||||
}
|
||||
}
|
||||
|
||||
private fun snapshotClasspath(classpathSourceDir: File, tmpDir: TemporaryFolder): ClasspathSnapshot {
|
||||
val classpathEntrySnapshots = classpathSourceDir.listFiles()!!.map { classpathEntrySourceDir ->
|
||||
val relativePathsInDir = classpathEntrySourceDir.walk()
|
||||
.filter { it.extension == "kt" || it.extension == "java" }
|
||||
.map { file -> file.toRelativeString(classpathEntrySourceDir) }
|
||||
.sortedBy { it }
|
||||
val sourceFiles = relativePathsInDir.map { relativePath ->
|
||||
if (relativePath.endsWith(".kt")) {
|
||||
val preCompiledClassFilesRoot = classpathEntrySourceDir.path.let {
|
||||
File(it.substringBeforeLast("src") + "classes" + it.substringAfterLast("src"))
|
||||
}.also { check(it.exists()) }
|
||||
KotlinSourceFile(
|
||||
classpathEntrySourceDir, relativePath,
|
||||
preCompiledClassFiles = listOf(
|
||||
ClassFile(preCompiledClassFilesRoot, relativePath.replace(".kt", ".class")),
|
||||
ClassFile(preCompiledClassFilesRoot, relativePath.replace(".kt", "Kt.class"))
|
||||
).filter { File(it.classRoot, it.unixStyleRelativePath).exists() }
|
||||
@RunWith(Parameterized::class)
|
||||
class JavaOnlyClasspathChangesComputerTest(private val protoBased: Boolean) : ClasspathChangesComputerTest() {
|
||||
|
||||
companion object {
|
||||
@Parameterized.Parameters(name = "protoBased={0}")
|
||||
@JvmStatic
|
||||
fun parameters() = listOf(true, false)
|
||||
}
|
||||
|
||||
@Test
|
||||
override fun testAbiChange_changePublicMethodSignature() {
|
||||
val sourceFile = SimpleJavaClass(tmpDir)
|
||||
val previousSnapshot = sourceFile.compile().snapshot(protoBased)
|
||||
val currentSnapshot = sourceFile.changePublicMethodSignature().compile().snapshot(protoBased)
|
||||
val changes = ClasspathChangesComputer.computeClassChanges(listOf(currentSnapshot), listOf(previousSnapshot)).normalize()
|
||||
|
||||
Changes(
|
||||
lookupSymbols = setOf(
|
||||
LookupSymbol(name = "publicMethod", scope = "com.example.SimpleJavaClass"),
|
||||
LookupSymbol(name = SAM_LOOKUP_NAME.asString(), scope = "com.example.SimpleJavaClass")
|
||||
),
|
||||
fqNames = setOf("com.example.SimpleJavaClass")
|
||||
).assertEquals(changes)
|
||||
}
|
||||
|
||||
@Test
|
||||
override fun testNonAbiChange_changeMethodImplementation() {
|
||||
val sourceFile = SimpleJavaClass(tmpDir)
|
||||
val previousSnapshot = sourceFile.compile().snapshot(protoBased)
|
||||
val currentSnapshot = sourceFile.changeMethodImplementation().compile().snapshot(protoBased)
|
||||
val changes = ClasspathChangesComputer.computeClassChanges(listOf(currentSnapshot), listOf(previousSnapshot)).normalize()
|
||||
|
||||
Changes(emptySet(), emptySet()).assertEquals(changes)
|
||||
}
|
||||
|
||||
@Test
|
||||
override fun testVariousAbiChanges() {
|
||||
val classpathSourceDir = File(testDataDir, "../ClasspathChangesComputerTest/testVariousAbiChanges/src/java").canonicalFile
|
||||
val currentSnapshot = snapshotClasspath(File(classpathSourceDir, "current-classpath"), tmpDir, protoBased)
|
||||
val previousSnapshot = snapshotClasspath(File(classpathSourceDir, "previous-classpath"), tmpDir, protoBased)
|
||||
val changes = ClasspathChangesComputer.compute(currentSnapshot, previousSnapshot).normalize()
|
||||
|
||||
Changes(
|
||||
lookupSymbols = setOf(
|
||||
// ModifiedClassUnchangedMembers
|
||||
LookupSymbol(name = "ModifiedClassUnchangedMembers", scope = "com.example"),
|
||||
|
||||
// ModifiedClassChangedMembers
|
||||
LookupSymbol(name = "modifiedField", scope = "com.example.ModifiedClassChangedMembers"),
|
||||
LookupSymbol(name = "removedField", scope = "com.example.ModifiedClassChangedMembers"),
|
||||
LookupSymbol(name = "modifiedMethod", scope = "com.example.ModifiedClassChangedMembers"),
|
||||
LookupSymbol(name = "removedMethod", scope = "com.example.ModifiedClassChangedMembers"),
|
||||
LookupSymbol(name = SAM_LOOKUP_NAME.asString(), scope = "com.example.ModifiedClassChangedMembers"),
|
||||
|
||||
// RemovedClass
|
||||
LookupSymbol(name = "RemovedClass", scope = "com.example")
|
||||
) + if (protoBased) {
|
||||
setOf(
|
||||
// ModifiedClassChangedMembers
|
||||
LookupSymbol(name = "addedField", scope = "com.example.ModifiedClassChangedMembers"),
|
||||
LookupSymbol(name = "addedMethod", scope = "com.example.ModifiedClassChangedMembers"),
|
||||
|
||||
// AddedClass
|
||||
LookupSymbol(name = "AddedClass", scope = "com.example"),
|
||||
)
|
||||
} else {
|
||||
SourceFile(classpathEntrySourceDir, relativePath)
|
||||
emptySet()
|
||||
},
|
||||
fqNames = setOf(
|
||||
"com.example.ModifiedClassUnchangedMembers",
|
||||
"com.example.ModifiedClassChangedMembers",
|
||||
"com.example.RemovedClass"
|
||||
) + if (protoBased) {
|
||||
setOf("com.example.AddedClass")
|
||||
} else {
|
||||
emptySet()
|
||||
}
|
||||
}
|
||||
val classFiles = sourceFiles.flatMap { TestSourceFile(it, tmpDir).compileAll() }
|
||||
).assertEquals(changes)
|
||||
}
|
||||
|
||||
@Test
|
||||
override fun testImpactAnalysis() {
|
||||
val classpathSourceDir = File(testDataDir, "../ClasspathChangesComputerTest/testImpactAnalysis_KotlinOrJava/src/java").canonicalFile
|
||||
val currentSnapshot = snapshotClasspath(File(classpathSourceDir, "current-classpath"), tmpDir, protoBased)
|
||||
val previousSnapshot = snapshotClasspath(File(classpathSourceDir, "previous-classpath"), tmpDir, protoBased)
|
||||
val changes = ClasspathChangesComputer.compute(currentSnapshot, previousSnapshot).normalize()
|
||||
|
||||
Changes(
|
||||
lookupSymbols = setOf(
|
||||
LookupSymbol(name = "changedField", scope = "com.example.ChangedSuperClass"),
|
||||
LookupSymbol(name = "changedField", scope = "com.example.SubClass"),
|
||||
LookupSymbol(name = "changedField", scope = "com.example.SubSubClass"),
|
||||
LookupSymbol(name = "changedMethod", scope = "com.example.ChangedSuperClass"),
|
||||
LookupSymbol(name = "changedMethod", scope = "com.example.SubClass"),
|
||||
LookupSymbol(name = "changedMethod", scope = "com.example.SubSubClass"),
|
||||
LookupSymbol(name = SAM_LOOKUP_NAME.asString(), scope = "com.example.ChangedSuperClass"),
|
||||
LookupSymbol(name = SAM_LOOKUP_NAME.asString(), scope = "com.example.SubClass"),
|
||||
LookupSymbol(name = SAM_LOOKUP_NAME.asString(), scope = "com.example.SubSubClass")
|
||||
),
|
||||
fqNames = setOf(
|
||||
"com.example.ChangedSuperClass",
|
||||
"com.example.SubClass",
|
||||
"com.example.SubSubClass"
|
||||
)
|
||||
).assertEquals(changes)
|
||||
}
|
||||
}
|
||||
|
||||
class KotlinAndJavaClasspathChangesComputerTest : ClasspathSnapshotTestCommon() {
|
||||
|
||||
@Test
|
||||
fun testImpactAnalysis() {
|
||||
val classpathSourceDir = File(testDataDir, "../ClasspathChangesComputerTest/testImpactAnalysis_KotlinAndJava/src").canonicalFile
|
||||
val currentSnapshot = snapshotClasspath(File(classpathSourceDir, "current-classpath"), tmpDir)
|
||||
val previousSnapshot = snapshotClasspath(File(classpathSourceDir, "previous-classpath"), tmpDir)
|
||||
val changes = ClasspathChangesComputer.compute(currentSnapshot, previousSnapshot).normalize()
|
||||
|
||||
Changes(
|
||||
lookupSymbols = setOf(
|
||||
LookupSymbol(name = "changedProperty", scope = "com.example.ChangedKotlinSuperClass"),
|
||||
LookupSymbol(name = "changedProperty", scope = "com.example.KotlinSubClassOfKotlinSuperClass"),
|
||||
LookupSymbol(name = "changedProperty", scope = "com.example.JavaSubClassOfKotlinSuperClass"),
|
||||
LookupSymbol(name = "changedFunction", scope = "com.example.ChangedKotlinSuperClass"),
|
||||
LookupSymbol(name = "changedFunction", scope = "com.example.KotlinSubClassOfKotlinSuperClass"),
|
||||
LookupSymbol(name = "changedFunction", scope = "com.example.JavaSubClassOfKotlinSuperClass"),
|
||||
LookupSymbol(name = SAM_LOOKUP_NAME.asString(), scope = "com.example.ChangedKotlinSuperClass"),
|
||||
LookupSymbol(name = SAM_LOOKUP_NAME.asString(), scope = "com.example.KotlinSubClassOfKotlinSuperClass"),
|
||||
LookupSymbol(name = SAM_LOOKUP_NAME.asString(), scope = "com.example.JavaSubClassOfKotlinSuperClass"),
|
||||
LookupSymbol(name = "changedField", scope = "com.example.ChangedJavaSuperClass"),
|
||||
LookupSymbol(name = "changedField", scope = "com.example.KotlinSubClassOfJavaSuperClass"),
|
||||
LookupSymbol(name = "changedField", scope = "com.example.JavaSubClassOfJavaSuperClass"),
|
||||
LookupSymbol(name = "changedMethod", scope = "com.example.ChangedJavaSuperClass"),
|
||||
LookupSymbol(name = "changedMethod", scope = "com.example.KotlinSubClassOfJavaSuperClass"),
|
||||
LookupSymbol(name = "changedMethod", scope = "com.example.JavaSubClassOfJavaSuperClass"),
|
||||
LookupSymbol(name = SAM_LOOKUP_NAME.asString(), scope = "com.example.ChangedJavaSuperClass"),
|
||||
LookupSymbol(name = SAM_LOOKUP_NAME.asString(), scope = "com.example.KotlinSubClassOfJavaSuperClass"),
|
||||
LookupSymbol(name = SAM_LOOKUP_NAME.asString(), scope = "com.example.JavaSubClassOfJavaSuperClass")
|
||||
),
|
||||
fqNames = setOf(
|
||||
"com.example.ChangedKotlinSuperClass",
|
||||
"com.example.KotlinSubClassOfKotlinSuperClass",
|
||||
"com.example.JavaSubClassOfKotlinSuperClass",
|
||||
"com.example.ChangedJavaSuperClass",
|
||||
"com.example.KotlinSubClassOfJavaSuperClass",
|
||||
"com.example.JavaSubClassOfJavaSuperClass"
|
||||
)
|
||||
).assertEquals(changes)
|
||||
}
|
||||
}
|
||||
|
||||
private fun snapshotClasspath(classpathSourceDir: File, tmpDir: TemporaryFolder, protoBased: Boolean = true): ClasspathSnapshot {
|
||||
val classpath = mutableListOf<File>()
|
||||
val classpathEntrySnapshots = classpathSourceDir.listFiles()!!.sortedBy { it.name }.map { classpathEntrySourceDir ->
|
||||
val classFiles = compileAll(classpathEntrySourceDir, classpath, tmpDir)
|
||||
classpath.addAll(listOfNotNull(classFiles.firstOrNull()?.classRoot))
|
||||
|
||||
val relativePaths = classFiles.map { it.unixStyleRelativePath }
|
||||
val classSnapshots = classFiles.snapshotAll(protoBased)
|
||||
ClasspathEntrySnapshot(
|
||||
classSnapshots = classFiles.map { it.unixStyleRelativePath to it.snapshot() }.toMap(LinkedHashMap())
|
||||
classSnapshots = relativePaths.zip(classSnapshots).toMap(LinkedHashMap())
|
||||
)
|
||||
}
|
||||
return ClasspathSnapshot(classpathEntrySnapshots)
|
||||
}
|
||||
|
||||
/** Adapted version of [ClasspathChanges.Available] for readability in this test. */
|
||||
private data class Changes(private val lookupSymbols: Set<LookupSymbol>, private val fqNames: Set<FqName>)
|
||||
private data class Changes(val lookupSymbols: Set<LookupSymbol>, val fqNames: Set<String>)
|
||||
|
||||
private fun ClasspathChanges.normalize(): Changes {
|
||||
this as ClasspathChanges.Available
|
||||
return Changes(lookupSymbols, fqNames)
|
||||
return Changes(lookupSymbols, fqNames.map { it.asString() }.toSet())
|
||||
}
|
||||
|
||||
private fun Changes.assertEquals(actual: Changes) {
|
||||
listOfNotNull(
|
||||
compare(expected = this.lookupSymbols, actual = actual.lookupSymbols),
|
||||
compare(expected = this.fqNames, actual = actual.fqNames)
|
||||
).also {
|
||||
if (it.isNotEmpty()) {
|
||||
fail(it.joinToString("\n"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun compare(expected: Set<*>, actual: Set<*>): String? {
|
||||
return if (expected != actual) {
|
||||
"Two sets differ:\n" +
|
||||
"Elements in expected set but not in actual set: ${expected - actual}\n" +
|
||||
"Elements in actual set but not in expected set: ${actual - expected}"
|
||||
} else null
|
||||
}
|
||||
|
||||
+6
-16
@@ -5,7 +5,8 @@
|
||||
|
||||
package org.jetbrains.kotlin.gradle.incremental
|
||||
|
||||
import org.junit.Assert.*
|
||||
import org.jetbrains.kotlin.gradle.incremental.ClasspathSnapshotTestCommon.Util.readBytes
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Test
|
||||
|
||||
abstract class ClasspathSnapshotSerializerTest : ClasspathSnapshotTestCommon() {
|
||||
@@ -32,23 +33,12 @@ class JavaClassesClasspathSnapshotSerializerTest : ClasspathSnapshotSerializerTe
|
||||
|
||||
@Test
|
||||
override fun `test ClassSnapshotDataSerializer`() {
|
||||
val originalSnapshot = testSourceFile.compileAndSnapshot()
|
||||
val originalSnapshot = testSourceFile.compile().let {
|
||||
ClassSnapshotter.snapshot(listOf(ClassFileWithContents(it, it.readBytes())), includeDebugInfoInSnapshot = false)
|
||||
}.single()
|
||||
val serializedSnapshot = ClassSnapshotDataSerializer.toByteArray(originalSnapshot)
|
||||
val deserializedSnapshot = ClassSnapshotDataSerializer.fromByteArray(serializedSnapshot)
|
||||
|
||||
// The deserialized object does not exactly match the original object as they contain fields called `memoizedSerializedSize` which
|
||||
// are not part of the serialized data and their values seem to be generated separately. Therefore, we remove these fields from the
|
||||
// check below.
|
||||
assertNotEquals(originalSnapshot.toGson(), deserializedSnapshot.toGson())
|
||||
assertEquals(
|
||||
originalSnapshot.toGson().stripLinesContainingText("memoizedSerializedSize"),
|
||||
deserializedSnapshot.toGson().stripLinesContainingText("memoizedSerializedSize")
|
||||
)
|
||||
// Add another check to confirm that those fields are not part of the serialized data.
|
||||
assertArrayEquals(serializedSnapshot, ClassSnapshotDataSerializer.toByteArray(deserializedSnapshot))
|
||||
}
|
||||
|
||||
private fun String.stripLinesContainingText(text: String): String {
|
||||
return lines().filterNot { it.contains(text, ignoreCase = true) }.joinToString("\n")
|
||||
assertEquals(originalSnapshot.toGson(), deserializedSnapshot.toGson())
|
||||
}
|
||||
}
|
||||
|
||||
+142
-76
@@ -6,9 +6,13 @@
|
||||
package org.jetbrains.kotlin.gradle.incremental
|
||||
|
||||
import com.google.gson.GsonBuilder
|
||||
import org.jetbrains.kotlin.gradle.incremental.ClasspathSnapshotTestCommon.Util.readBytes
|
||||
import org.jetbrains.kotlin.gradle.incremental.ClasspathSnapshotTestCommon.SourceFile.JavaSourceFile
|
||||
import org.jetbrains.kotlin.gradle.incremental.ClasspathSnapshotTestCommon.SourceFile.KotlinSourceFile
|
||||
import org.jetbrains.kotlin.gradle.incremental.ClasspathSnapshotTestCommon.Util.compile
|
||||
import org.jetbrains.kotlin.gradle.incremental.ClasspathSnapshotTestCommon.Util.compileAll
|
||||
import org.jetbrains.kotlin.gradle.incremental.ClasspathSnapshotTestCommon.Util.snapshot
|
||||
import org.jetbrains.kotlin.gradle.util.compileSources
|
||||
import org.jetbrains.kotlin.gradle.incremental.ClasspathSnapshotTestCommon.Util.snapshotAll
|
||||
import org.jetbrains.kotlin.test.KotlinTestUtils
|
||||
import org.junit.Rule
|
||||
import org.junit.rules.TemporaryFolder
|
||||
import java.io.File
|
||||
@@ -16,7 +20,8 @@ import java.io.File
|
||||
abstract class ClasspathSnapshotTestCommon {
|
||||
|
||||
companion object {
|
||||
val testDataDir = File("libraries/tools/kotlin-gradle-plugin/src/testData/kotlin.incremental.useClasspathSnapshot")
|
||||
val testDataDir =
|
||||
File("libraries/tools/kotlin-gradle-plugin/src/testData/org/jetbrains/kotlin/gradle/incremental/ClasspathSnapshotTestCommon")
|
||||
}
|
||||
|
||||
@get:Rule
|
||||
@@ -26,7 +31,7 @@ abstract class ClasspathSnapshotTestCommon {
|
||||
private val gson by lazy { GsonBuilder().setPrettyPrinting().create() }
|
||||
protected fun Any.toGson(): String = gson.toJson(this)
|
||||
|
||||
open class SourceFile(val baseDir: File, relativePath: String) {
|
||||
sealed class SourceFile(val baseDir: File, relativePath: String) {
|
||||
val unixStyleRelativePath: String
|
||||
|
||||
init {
|
||||
@@ -34,13 +39,15 @@ abstract class ClasspathSnapshotTestCommon {
|
||||
}
|
||||
|
||||
fun asFile() = File(baseDir, unixStyleRelativePath)
|
||||
}
|
||||
|
||||
class KotlinSourceFile(baseDir: File, relativePath: String, val preCompiledClassFiles: List<ClassFile>) :
|
||||
SourceFile(baseDir, relativePath) {
|
||||
class KotlinSourceFile(baseDir: File, relativePath: String, val preCompiledClassFiles: List<ClassFile>) :
|
||||
SourceFile(baseDir, relativePath) {
|
||||
|
||||
constructor(baseDir: File, relativePath: String, preCompiledClassFile: ClassFile) :
|
||||
this(baseDir, relativePath, listOf(preCompiledClassFile))
|
||||
constructor(baseDir: File, relativePath: String, preCompiledClassFile: ClassFile) :
|
||||
this(baseDir, relativePath, listOf(preCompiledClassFile))
|
||||
}
|
||||
|
||||
class JavaSourceFile(baseDir: File, relativePath: String) : SourceFile(baseDir, relativePath)
|
||||
}
|
||||
|
||||
/** Same as [SourceFile] but with a [TemporaryFolder] to store the results of operations on the [SourceFile]. */
|
||||
@@ -50,9 +57,10 @@ abstract class ClasspathSnapshotTestCommon {
|
||||
val fileContents = sourceFile.asFile().readText()
|
||||
check(fileContents.contains(oldValue)) { "String '$oldValue' not found in file '${sourceFile.asFile().path}'" }
|
||||
|
||||
val newSourceFile =
|
||||
preCompiledKotlinClassFile?.let { KotlinSourceFile(tmpDir.newFolder(), sourceFile.unixStyleRelativePath, it) }
|
||||
?: SourceFile(tmpDir.newFolder(), sourceFile.unixStyleRelativePath)
|
||||
val newSourceFile = when (sourceFile) {
|
||||
is KotlinSourceFile -> KotlinSourceFile(tmpDir.newFolder(), sourceFile.unixStyleRelativePath, preCompiledKotlinClassFile!!)
|
||||
is JavaSourceFile -> JavaSourceFile(tmpDir.newFolder(), sourceFile.unixStyleRelativePath)
|
||||
}
|
||||
newSourceFile.asFile().parentFile.mkdirs()
|
||||
newSourceFile.asFile().writeText(fileContents.replace(oldValue, newValue))
|
||||
return TestSourceFile(newSourceFile, tmpDir)
|
||||
@@ -68,45 +76,7 @@ abstract class ClasspathSnapshotTestCommon {
|
||||
|
||||
/** Compiles this source file and returns all generated .class files. */
|
||||
@Suppress("MemberVisibilityCanBePrivate")
|
||||
fun compileAll(): List<ClassFile> {
|
||||
val filePath = sourceFile.asFile().path
|
||||
return when {
|
||||
filePath.endsWith(".kt") -> compileKotlin()
|
||||
filePath.endsWith(".java") -> compileJava()
|
||||
else -> error("Unexpected file name extension: $filePath")
|
||||
}
|
||||
}
|
||||
|
||||
private fun compileKotlin(): List<ClassFile> {
|
||||
sourceFile as KotlinSourceFile
|
||||
|
||||
// TODO: Call Kotlin compiler to generate classes.
|
||||
// If <kotlin-repo>/dist/kotlinc/lib/kotlin-compiler.jar is available (e.g., by running ./gradlew dist), we will be able to
|
||||
// call the Kotlin compiler to generate classes from here. For example:
|
||||
// val classesDir = tmpDir.newFolder()
|
||||
// org.jetbrains.kotlin.test.MockLibraryUtil.compileKotlin(sourceFile.asFile().path, classesDir)
|
||||
// val classFiles = classesDir.walk()
|
||||
// .filter { it.isFile && !it.toRelativeString(classesDir).startsWith("META-INF/") }
|
||||
// .map { ClassFile(classesDir, it.toRelativeString(classesDir)) }
|
||||
// .sortedBy { it.unixStyleRelativePath.substringBefore(".class") }
|
||||
// .toList()
|
||||
// kotlin.test.assertEquals(classFiles.size, sourceFile.preCompiledClassFiles.size)
|
||||
// sourceFile.preCompiledClassFiles.forEach {
|
||||
// File(classesDir, it.unixStyleRelativePath).copyTo(File(it.classRoot, it.unixStyleRelativePath), overwrite = true)
|
||||
// }
|
||||
// However, kotlin-compiler.jar is currently not available in CI builds, so we need to pre-compile the classes, put them in the
|
||||
// test data, and use them here instead.
|
||||
return sourceFile.preCompiledClassFiles
|
||||
}
|
||||
|
||||
private fun compileJava(): List<ClassFile> {
|
||||
val classesDir = tmpDir.newFolder()
|
||||
compileSources(listOf(sourceFile.asFile()), classesDir)
|
||||
return classesDir.walk().filter { it.isFile }
|
||||
.map { ClassFile(classesDir, it.toRelativeString(classesDir)) }
|
||||
.sortedBy { it.unixStyleRelativePath.substringBefore(".class") }
|
||||
.toList()
|
||||
}
|
||||
fun compileAll(): List<ClassFile> = sourceFile.compile(tmpDir)
|
||||
|
||||
/**
|
||||
* Compiles this source file and returns the snapshot of a single generated .class file, or fails if zero or more than one .class
|
||||
@@ -117,23 +87,120 @@ abstract class ClasspathSnapshotTestCommon {
|
||||
fun compileAndSnapshot() = compile().snapshot()
|
||||
|
||||
/** Compiles this source file and returns the snapshots of all generated .class files. */
|
||||
fun compileAndSnapshotAll(): List<ClassSnapshot> {
|
||||
val classes = compileAll().map {
|
||||
ClassFileWithContents(it, it.readBytes())
|
||||
}
|
||||
return ClassSnapshotter.snapshot(classes)
|
||||
}
|
||||
fun compileAndSnapshotAll(): List<ClassSnapshot> = compileAll().snapshotAll()
|
||||
}
|
||||
|
||||
object Util {
|
||||
|
||||
fun ClassFile.readBytes(): ByteArray {
|
||||
// The class files in tests are currently in a directory, so we don't need to handle jars
|
||||
return File(classRoot, unixStyleRelativePath).readBytes()
|
||||
/** Compiles the source files in the given directory and returns all generated .class files. */
|
||||
fun compileAll(srcDir: File, classpath: List<File>, tmpDir: TemporaryFolder): List<ClassFile> {
|
||||
val kotlinClasses = compileKotlin(srcDir, classpath, tmpDir)
|
||||
|
||||
val javaClasspath = classpath + listOfNotNull(kotlinClasses.firstOrNull()?.classRoot)
|
||||
val javaClasses = compileJava(srcDir, javaClasspath, tmpDir)
|
||||
|
||||
return kotlinClasses + javaClasses
|
||||
}
|
||||
|
||||
fun ClassFile.snapshot(): ClassSnapshot {
|
||||
return ClassSnapshotter.snapshot(listOf(ClassFileWithContents(this, readBytes()))).single()
|
||||
private fun compileKotlin(srcDir: File, classpath: List<File>, tmpDir: TemporaryFolder): List<ClassFile> {
|
||||
val preCompiledKotlinClassesDir = srcDir.path.let {
|
||||
File(it.substringBeforeLast("src") + "classes" + it.substringAfterLast("src"))
|
||||
}
|
||||
preCompileKotlinFilesIfNecessary(srcDir, preCompiledKotlinClassesDir, classpath, tmpDir)
|
||||
return getClassFilesInDir(preCompiledKotlinClassesDir)
|
||||
}
|
||||
|
||||
private val preCompiledKotlinClassesDirs = mutableSetOf<File>()
|
||||
|
||||
/**
|
||||
* If <kotlin-repo>/dist/kotlinc/lib/kotlin-compiler.jar is available (e.g., by running ./gradlew dist), we will be able to call the
|
||||
* Kotlin compiler to generate classes. However, kotlin-compiler.jar is currently not available in CI builds, so we need to
|
||||
* pre-compile the classes locally and put them in the test data to check in.
|
||||
*/
|
||||
@Synchronized // To safe-guard shared variable preCompiledKotlinClassesDirs
|
||||
private fun preCompileKotlinFilesIfNecessary(
|
||||
srcDir: File,
|
||||
preCompiledKotlinClassesDir: File,
|
||||
classpath: List<File>,
|
||||
tmpDir: TemporaryFolder,
|
||||
preCompile: Boolean = false // Set to `true` to pre-compile Kotlin class files locally (DO NOT check in with preCompile = true)
|
||||
) {
|
||||
if (preCompile) {
|
||||
if (!preCompiledKotlinClassesDirs.contains(preCompiledKotlinClassesDir)) {
|
||||
val classFiles = doCompileKotlin(srcDir, classpath, tmpDir)
|
||||
preCompiledKotlinClassesDir.deleteRecursively()
|
||||
for (classFile in classFiles) {
|
||||
File(preCompiledKotlinClassesDir, classFile.unixStyleRelativePath).apply {
|
||||
parentFile.mkdirs()
|
||||
classFile.asFile().copyTo(this)
|
||||
}
|
||||
}
|
||||
preCompiledKotlinClassesDirs.add(preCompiledKotlinClassesDir)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun SourceFile.compile(tmpDir: TemporaryFolder): List<ClassFile> {
|
||||
return if (this is KotlinSourceFile) {
|
||||
preCompiledClassFiles.forEach {
|
||||
preCompileKotlinFilesIfNecessary(baseDir, it.classRoot, classpath = emptyList(), tmpDir)
|
||||
}
|
||||
preCompiledClassFiles
|
||||
} else {
|
||||
val srcDir = tmpDir.newFolder()
|
||||
asFile().copyTo(File(srcDir, unixStyleRelativePath))
|
||||
compileAll(srcDir, classpath = emptyList(), tmpDir)
|
||||
}
|
||||
}
|
||||
|
||||
private fun doCompileKotlin(srcDir: File, classpath: List<File>, tmpDir: TemporaryFolder): List<ClassFile> {
|
||||
if (srcDir.walk().none { it.path.endsWith(".kt") }) {
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
val classesDir = tmpDir.newFolder()
|
||||
org.jetbrains.kotlin.test.MockLibraryUtil.compileKotlin(
|
||||
srcDir.path,
|
||||
classesDir,
|
||||
extraClasspath = classpath.map { it.path }.toTypedArray()
|
||||
)
|
||||
return getClassFilesInDir(classesDir)
|
||||
}
|
||||
|
||||
private fun compileJava(srcDir: File, classpath: List<File>, tmpDir: TemporaryFolder): List<ClassFile> {
|
||||
val javaFiles = srcDir.walk().toList().filter { it.path.endsWith(".java") }
|
||||
if (javaFiles.isEmpty()) {
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
val classesDir = tmpDir.newFolder()
|
||||
val classpathOption =
|
||||
if (classpath.isNotEmpty()) listOf("-classpath", classpath.joinToString(File.pathSeparator)) else emptyList()
|
||||
|
||||
KotlinTestUtils.compileJavaFiles(javaFiles, listOf("-d", classesDir.path) + classpathOption)
|
||||
return getClassFilesInDir(classesDir)
|
||||
}
|
||||
|
||||
private fun getClassFilesInDir(classesDir: File): List<ClassFile> {
|
||||
return classesDir.walk().toList()
|
||||
.filter { it.isFile && it.path.endsWith(".class") }
|
||||
.map { ClassFile(classesDir, it.toRelativeString(classesDir)) }
|
||||
.sortedBy { it.unixStyleRelativePath.substringBefore(".class") }
|
||||
}
|
||||
|
||||
// `ClassFile`s in production code could be in a jar, but the `ClassFile`s in tests are currently in a directory, so converting it
|
||||
// to a File is possible.
|
||||
@Suppress("MemberVisibilityCanBePrivate")
|
||||
fun ClassFile.asFile() = File(classRoot, unixStyleRelativePath)
|
||||
|
||||
@Suppress("MemberVisibilityCanBePrivate")
|
||||
fun ClassFile.readBytes() = asFile().readBytes()
|
||||
|
||||
fun ClassFile.snapshot(protoBased: Boolean? = null): ClassSnapshot = listOf(this).snapshotAll(protoBased).single()
|
||||
|
||||
fun List<ClassFile>.snapshotAll(protoBased: Boolean? = null): List<ClassSnapshot> {
|
||||
val classFilesWithContents = this.map { ClassFileWithContents(it, it.readBytes()) }
|
||||
return ClassSnapshotter.snapshot(classFilesWithContents, protoBased, includeDebugInfoInSnapshot = true)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -146,45 +213,44 @@ abstract class ClasspathSnapshotTestCommon {
|
||||
|
||||
class SimpleKotlinClass(tmpDir: TemporaryFolder) : ChangeableTestSourceFile(
|
||||
KotlinSourceFile(
|
||||
baseDir = File(testDataDir, "src/original"),
|
||||
relativePath = "com/example/SimpleKotlinClass.kt",
|
||||
preCompiledClassFile = ClassFile(File(testDataDir, "classes/original"), "com/example/SimpleKotlinClass.class")
|
||||
baseDir = File(testDataDir, "src/kotlin"), relativePath = "com/example/SimpleKotlinClass.kt",
|
||||
preCompiledClassFile = ClassFile(File(testDataDir, "classes/kotlin/original"), "com/example/SimpleKotlinClass.class")
|
||||
), tmpDir
|
||||
) {
|
||||
|
||||
override fun changePublicMethodSignature() = replace(
|
||||
"publicMethod()", "changedPublicMethod()",
|
||||
"publicFunction()", "publicFunction(newParam: Int)",
|
||||
preCompiledKotlinClassFile = ClassFile(
|
||||
File(testDataDir, "classes/changedPublicMethodSignature"), "com/example/SimpleKotlinClass.class"
|
||||
File(testDataDir, "classes/kotlin/changedPublicMethodSignature"), "com/example/SimpleKotlinClass.class"
|
||||
)
|
||||
)
|
||||
|
||||
override fun changeMethodImplementation() = replace(
|
||||
"I'm in a public method", "This method implementation has changed!",
|
||||
"I'm in a public function", "This function's implementation has changed!",
|
||||
preCompiledKotlinClassFile = ClassFile(
|
||||
File(testDataDir, "classes/changedMethodImplementation"), "com/example/SimpleKotlinClass.class"
|
||||
File(testDataDir, "classes/kotlin/changedMethodImplementation"), "com/example/SimpleKotlinClass.class"
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
class SimpleJavaClass(tmpDir: TemporaryFolder) : ChangeableTestSourceFile(
|
||||
SourceFile(File(testDataDir, "src/original"), "com/example/SimpleJavaClass.java"), tmpDir
|
||||
JavaSourceFile(File(testDataDir, "src/java"), "com/example/SimpleJavaClass.java"), tmpDir
|
||||
) {
|
||||
|
||||
override fun changePublicMethodSignature() = replace("publicMethod()", "changedPublicMethod()")
|
||||
override fun changePublicMethodSignature() = replace("publicMethod()", "publicMethod(int newParam)")
|
||||
|
||||
override fun changeMethodImplementation() = replace("I'm in a public method", "This method implementation has changed!")
|
||||
override fun changeMethodImplementation() = replace("I'm in a public method", "This method's implementation has changed!")
|
||||
}
|
||||
|
||||
class JavaClassWithNestedClasses(tmpDir: TemporaryFolder) : ChangeableTestSourceFile(
|
||||
SourceFile(File(testDataDir, "src/original"), "com/example/JavaClassWithNestedClasses.java"), tmpDir
|
||||
JavaSourceFile(File(testDataDir, "src/java"), "com/example/JavaClassWithNestedClasses.java"), tmpDir
|
||||
) {
|
||||
|
||||
/** The source file contains multiple classes, select the one we are mostly interested in. */
|
||||
/** The source file contains multiple classes, select the one that we want to test. */
|
||||
val nestedClassToTest = "com/example/JavaClassWithNestedClasses\$InnerClass"
|
||||
|
||||
override fun changePublicMethodSignature() = replace("publicMethod()", "changedPublicMethod()")
|
||||
override fun changePublicMethodSignature() = replace("publicMethod()", "publicMethod(int newParam)")
|
||||
|
||||
override fun changeMethodImplementation() = replace("I'm in a public method", "This method implementation has changed!")
|
||||
override fun changeMethodImplementation() = replace("I'm in a public method", "This method's implementation has changed!")
|
||||
}
|
||||
}
|
||||
|
||||
+61
-14
@@ -3,14 +3,15 @@
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
@file:Suppress("SpellCheckingInspection")
|
||||
|
||||
package org.jetbrains.kotlin.gradle.incremental
|
||||
|
||||
import org.jetbrains.kotlin.gradle.incremental.ClasspathSnapshotTestCommon.Util.snapshot
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertNotEquals
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.junit.runners.Parameterized
|
||||
import java.io.File
|
||||
|
||||
abstract class ClasspathSnapshotterTest : ClasspathSnapshotTestCommon() {
|
||||
@@ -26,8 +27,11 @@ abstract class ClasspathSnapshotterTest : ClasspathSnapshotTestCommon() {
|
||||
|
||||
@Test
|
||||
fun `test ClassSnapshotter's result against expected snapshot`() {
|
||||
val expectedSnapshot = File("${testSourceFile.sourceFile.asFile().path.substringBeforeLast('.')}-expected-snapshot.json").readText()
|
||||
assertEquals(expectedSnapshot, testClassSnapshot.toGson())
|
||||
assertEquals(getExpectedSnapshotFile().readText(), testClassSnapshot.toGson())
|
||||
}
|
||||
|
||||
private fun getExpectedSnapshotFile() = testSourceFile.sourceFile.asFile().path.let {
|
||||
File(it.substringBeforeLast("src") + "expected-snapshot" + it.substringAfterLast("src").substringBeforeLast('.') + ".json")
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -53,8 +57,53 @@ class KotlinClassesClasspathSnapshotterTest : ClasspathSnapshotterTest() {
|
||||
override val testSourceFile = SimpleKotlinClass(tmpDir)
|
||||
}
|
||||
|
||||
class JavaClassesClasspathSnapshotterTest : ClasspathSnapshotterTest() {
|
||||
override val testSourceFile = SimpleJavaClass(tmpDir)
|
||||
@RunWith(Parameterized::class)
|
||||
class JavaClassesClasspathSnapshotterTest(private val protoBased: Boolean) : ClasspathSnapshotTestCommon() {
|
||||
|
||||
companion object {
|
||||
@Parameterized.Parameters(name = "protoBased={0}")
|
||||
@JvmStatic
|
||||
fun parameters() = listOf(true, false)
|
||||
}
|
||||
|
||||
private val testSourceFile = SimpleJavaClass(tmpDir)
|
||||
|
||||
private lateinit var testClassSnapshot: ClassSnapshot
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
testClassSnapshot = testSourceFile.compile().snapshot(protoBased)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test ClassSnapshotter's result against expected snapshot`() {
|
||||
assertEquals(getExpectedSnapshotFile().readText(), testClassSnapshot.toGson())
|
||||
}
|
||||
|
||||
private fun getExpectedSnapshotFile() = testSourceFile.sourceFile.asFile().path.let {
|
||||
File(
|
||||
it.substringBeforeLast("src") + "expected-snapshot" + it.substringAfterLast("src")
|
||||
.substringBeforeLast('.') + "-protoBased=$protoBased.json"
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test ClassSnapshotter extracts ABI info from a class`() {
|
||||
// Change public method signature
|
||||
val updatedSnapshot = testSourceFile.changePublicMethodSignature().compile().snapshot(protoBased)
|
||||
|
||||
// The snapshot must change
|
||||
assertNotEquals(testClassSnapshot.toGson(), updatedSnapshot.toGson())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test ClassSnapshotter does not extract non-ABI info from a class`() {
|
||||
// Change method implementation
|
||||
val updatedSnapshot = testSourceFile.changeMethodImplementation().compile().snapshot(protoBased)
|
||||
|
||||
// The snapshot must not change
|
||||
assertEquals(testClassSnapshot.toGson(), updatedSnapshot.toGson())
|
||||
}
|
||||
}
|
||||
|
||||
class JavaClassWithNestedClassesClasspathSnapshotterTest : ClasspathSnapshotTestCommon() {
|
||||
@@ -69,19 +118,17 @@ class JavaClassWithNestedClassesClasspathSnapshotterTest : ClasspathSnapshotTest
|
||||
}
|
||||
|
||||
private fun TestSourceFile.compileAndSnapshotNestedClass(): ClassSnapshot {
|
||||
return compileAndSnapshotAll()[5].also {
|
||||
assertEquals(
|
||||
testSourceFile.nestedClassToTest,
|
||||
(it as RegularJavaClassSnapshot).serializedJavaClass.classId.asString().replace('.', '$')
|
||||
)
|
||||
return compileAndSnapshotAll().single {
|
||||
if (it is RegularJavaClassSnapshot) {
|
||||
it.classAbiExcludingMembers.name == testSourceFile.nestedClassToTest
|
||||
} else false
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test ClassSnapshotter's result against expected snapshot`() {
|
||||
val expectedSnapshot =
|
||||
File("${testDataDir.path}/src/original/${testSourceFile.nestedClassToTest}-expected-snapshot.json").readText()
|
||||
assertEquals(expectedSnapshot, testClassSnapshot.toGson())
|
||||
val expectedSnapshotFile = File("${testDataDir.path}/expected-snapshot/java/${testSourceFile.nestedClassToTest}.json")
|
||||
assertEquals(expectedSnapshotFile.readText(), testClassSnapshot.toGson())
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
-6
@@ -1,6 +0,0 @@
|
||||
package com.example;
|
||||
|
||||
// Unchanged class
|
||||
public class A {
|
||||
public int a = 0;
|
||||
}
|
||||
-9
@@ -1,9 +0,0 @@
|
||||
package com.example;
|
||||
|
||||
// Modified class
|
||||
public class B {
|
||||
public int b1 = 0; // Unchanged field
|
||||
public String b2 = ""; // Modified field
|
||||
//public int b3 = 0; // Removed field
|
||||
public int b4 = 0; // Added field
|
||||
}
|
||||
-6
@@ -1,6 +0,0 @@
|
||||
package com.example;
|
||||
|
||||
// Added class
|
||||
public class D {
|
||||
public int d = 0;
|
||||
}
|
||||
-5
@@ -1,5 +0,0 @@
|
||||
package com.example;
|
||||
|
||||
public class A {
|
||||
public int a = 0;
|
||||
}
|
||||
-7
@@ -1,7 +0,0 @@
|
||||
package com.example;
|
||||
|
||||
public class B {
|
||||
public int b1 = 0;
|
||||
public int b2 = 0;
|
||||
public int b3 = 0;
|
||||
}
|
||||
-6
@@ -1,6 +0,0 @@
|
||||
package com.example;
|
||||
|
||||
// To-be-removed class
|
||||
public class C {
|
||||
public int c = 0;
|
||||
}
|
||||
-9
@@ -1,9 +0,0 @@
|
||||
package com.example
|
||||
|
||||
// Unchanged class
|
||||
class A {
|
||||
val a: Int = 0
|
||||
}
|
||||
|
||||
// Unchanged top-level function
|
||||
fun topLevelFuncA() {}
|
||||
-12
@@ -1,12 +0,0 @@
|
||||
package com.example
|
||||
|
||||
// Modified class
|
||||
public class B {
|
||||
val b1: Int = 0 // Unchanged field
|
||||
val b2: String = "" // Modified field
|
||||
//val b3: Int = 0 // Removed field
|
||||
val b4: Int = 0 // Added field
|
||||
}
|
||||
|
||||
// Modified top-level function
|
||||
fun topLevelFuncB(): Int = 0
|
||||
-12
@@ -1,12 +0,0 @@
|
||||
package com.example
|
||||
|
||||
// Added class
|
||||
public class D {
|
||||
val d: Int = 0
|
||||
}
|
||||
|
||||
// Added top-level function
|
||||
fun topLevelFuncD() {}
|
||||
|
||||
// Moved top-level function
|
||||
fun topLevelFuncInCKtMovedToDKt() {}
|
||||
-7
@@ -1,7 +0,0 @@
|
||||
package com.example
|
||||
|
||||
class A {
|
||||
val a: Int = 0
|
||||
}
|
||||
|
||||
fun topLevelFuncA() {}
|
||||
-9
@@ -1,9 +0,0 @@
|
||||
package com.example
|
||||
|
||||
public class B {
|
||||
val b1: Int = 0
|
||||
val b2: Int = 0
|
||||
val b3: Int = 0
|
||||
}
|
||||
|
||||
fun topLevelFuncB() {}
|
||||
-12
@@ -1,12 +0,0 @@
|
||||
package com.example
|
||||
|
||||
// To-be-removed class
|
||||
public class C {
|
||||
val c: Int = 0
|
||||
}
|
||||
|
||||
// To-be-removed top-level function
|
||||
fun topLevelFuncC() {}
|
||||
|
||||
// To-be-moved top-level function
|
||||
fun topLevelFuncInCKtMovedToDKt() {}
|
||||
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
-697
@@ -1,697 +0,0 @@
|
||||
{
|
||||
"serializedJavaClass": {
|
||||
"proto": {
|
||||
"unknownFields": {
|
||||
"bytes": [],
|
||||
"hash": 0
|
||||
},
|
||||
"bitField0_": 3,
|
||||
"flags_": 22,
|
||||
"fqName_": 2,
|
||||
"companionObjectName_": 0,
|
||||
"typeParameter_": [],
|
||||
"supertype_": [
|
||||
{
|
||||
"unknownFields": {
|
||||
"bytes": [],
|
||||
"hash": 0
|
||||
},
|
||||
"bitField0_": 16,
|
||||
"argument_": [],
|
||||
"nullable_": false,
|
||||
"flexibleTypeCapabilitiesId_": 0,
|
||||
"flexibleUpperBound_": {
|
||||
"unknownFields": {
|
||||
"bytes": [],
|
||||
"hash": 0
|
||||
},
|
||||
"bitField0_": 0,
|
||||
"argument_": [],
|
||||
"nullable_": false,
|
||||
"flexibleTypeCapabilitiesId_": 0,
|
||||
"flexibleUpperBoundId_": 0,
|
||||
"className_": 0,
|
||||
"typeParameter_": 0,
|
||||
"typeParameterName_": 0,
|
||||
"typeAliasName_": 0,
|
||||
"outerTypeId_": 0,
|
||||
"abbreviatedTypeId_": 0,
|
||||
"flags_": 0,
|
||||
"memoizedIsInitialized": -1,
|
||||
"memoizedSerializedSize": -1,
|
||||
"extensions": {
|
||||
"fields": {},
|
||||
"isImmutable": false,
|
||||
"hasLazyField": false
|
||||
},
|
||||
"memoizedHashCode": 0
|
||||
},
|
||||
"flexibleUpperBoundId_": 0,
|
||||
"className_": 5,
|
||||
"typeParameter_": 0,
|
||||
"typeParameterName_": 0,
|
||||
"typeAliasName_": 0,
|
||||
"outerType_": {
|
||||
"unknownFields": {
|
||||
"bytes": [],
|
||||
"hash": 0
|
||||
},
|
||||
"bitField0_": 0,
|
||||
"argument_": [],
|
||||
"nullable_": false,
|
||||
"flexibleTypeCapabilitiesId_": 0,
|
||||
"flexibleUpperBoundId_": 0,
|
||||
"className_": 0,
|
||||
"typeParameter_": 0,
|
||||
"typeParameterName_": 0,
|
||||
"typeAliasName_": 0,
|
||||
"outerTypeId_": 0,
|
||||
"abbreviatedTypeId_": 0,
|
||||
"flags_": 0,
|
||||
"memoizedIsInitialized": -1,
|
||||
"memoizedSerializedSize": -1,
|
||||
"extensions": {
|
||||
"fields": {},
|
||||
"isImmutable": false,
|
||||
"hasLazyField": false
|
||||
},
|
||||
"memoizedHashCode": 0
|
||||
},
|
||||
"outerTypeId_": 0,
|
||||
"abbreviatedType_": {
|
||||
"unknownFields": {
|
||||
"bytes": [],
|
||||
"hash": 0
|
||||
},
|
||||
"bitField0_": 0,
|
||||
"argument_": [],
|
||||
"nullable_": false,
|
||||
"flexibleTypeCapabilitiesId_": 0,
|
||||
"flexibleUpperBoundId_": 0,
|
||||
"className_": 0,
|
||||
"typeParameter_": 0,
|
||||
"typeParameterName_": 0,
|
||||
"typeAliasName_": 0,
|
||||
"outerTypeId_": 0,
|
||||
"abbreviatedTypeId_": 0,
|
||||
"flags_": 0,
|
||||
"memoizedIsInitialized": -1,
|
||||
"memoizedSerializedSize": -1,
|
||||
"extensions": {
|
||||
"fields": {},
|
||||
"isImmutable": false,
|
||||
"hasLazyField": false
|
||||
},
|
||||
"memoizedHashCode": 0
|
||||
},
|
||||
"abbreviatedTypeId_": 0,
|
||||
"flags_": 0,
|
||||
"memoizedIsInitialized": 1,
|
||||
"memoizedSerializedSize": -1,
|
||||
"extensions": {
|
||||
"fields": {},
|
||||
"isImmutable": true,
|
||||
"hasLazyField": false
|
||||
},
|
||||
"memoizedHashCode": 0
|
||||
}
|
||||
],
|
||||
"supertypeId_": [],
|
||||
"supertypeIdMemoizedSerializedSize": -1,
|
||||
"nestedClassName_": [],
|
||||
"nestedClassNameMemoizedSerializedSize": -1,
|
||||
"constructor_": [
|
||||
{
|
||||
"unknownFields": {
|
||||
"bytes": [],
|
||||
"hash": 0
|
||||
},
|
||||
"bitField0_": 1,
|
||||
"flags_": 54,
|
||||
"valueParameter_": [],
|
||||
"versionRequirement_": [],
|
||||
"memoizedIsInitialized": 1,
|
||||
"memoizedSerializedSize": -1,
|
||||
"extensions": {
|
||||
"fields": {},
|
||||
"isImmutable": true,
|
||||
"hasLazyField": false
|
||||
},
|
||||
"memoizedHashCode": 0
|
||||
}
|
||||
],
|
||||
"function_": [
|
||||
{
|
||||
"unknownFields": {
|
||||
"bytes": [],
|
||||
"hash": 0
|
||||
},
|
||||
"bitField0_": 13,
|
||||
"flags_": 32786,
|
||||
"oldFlags_": 6,
|
||||
"name_": 6,
|
||||
"returnType_": {
|
||||
"unknownFields": {
|
||||
"bytes": [],
|
||||
"hash": 0
|
||||
},
|
||||
"bitField0_": 16,
|
||||
"argument_": [],
|
||||
"nullable_": false,
|
||||
"flexibleTypeCapabilitiesId_": 0,
|
||||
"flexibleUpperBound_": {
|
||||
"unknownFields": {
|
||||
"bytes": [],
|
||||
"hash": 0
|
||||
},
|
||||
"bitField0_": 0,
|
||||
"argument_": [],
|
||||
"nullable_": false,
|
||||
"flexibleTypeCapabilitiesId_": 0,
|
||||
"flexibleUpperBoundId_": 0,
|
||||
"className_": 0,
|
||||
"typeParameter_": 0,
|
||||
"typeParameterName_": 0,
|
||||
"typeAliasName_": 0,
|
||||
"outerTypeId_": 0,
|
||||
"abbreviatedTypeId_": 0,
|
||||
"flags_": 0,
|
||||
"memoizedIsInitialized": -1,
|
||||
"memoizedSerializedSize": -1,
|
||||
"extensions": {
|
||||
"fields": {},
|
||||
"isImmutable": false,
|
||||
"hasLazyField": false
|
||||
},
|
||||
"memoizedHashCode": 0
|
||||
},
|
||||
"flexibleUpperBoundId_": 0,
|
||||
"className_": 7,
|
||||
"typeParameter_": 0,
|
||||
"typeParameterName_": 0,
|
||||
"typeAliasName_": 0,
|
||||
"outerType_": {
|
||||
"unknownFields": {
|
||||
"bytes": [],
|
||||
"hash": 0
|
||||
},
|
||||
"bitField0_": 0,
|
||||
"argument_": [],
|
||||
"nullable_": false,
|
||||
"flexibleTypeCapabilitiesId_": 0,
|
||||
"flexibleUpperBoundId_": 0,
|
||||
"className_": 0,
|
||||
"typeParameter_": 0,
|
||||
"typeParameterName_": 0,
|
||||
"typeAliasName_": 0,
|
||||
"outerTypeId_": 0,
|
||||
"abbreviatedTypeId_": 0,
|
||||
"flags_": 0,
|
||||
"memoizedIsInitialized": -1,
|
||||
"memoizedSerializedSize": -1,
|
||||
"extensions": {
|
||||
"fields": {},
|
||||
"isImmutable": false,
|
||||
"hasLazyField": false
|
||||
},
|
||||
"memoizedHashCode": 0
|
||||
},
|
||||
"outerTypeId_": 0,
|
||||
"abbreviatedType_": {
|
||||
"unknownFields": {
|
||||
"bytes": [],
|
||||
"hash": 0
|
||||
},
|
||||
"bitField0_": 0,
|
||||
"argument_": [],
|
||||
"nullable_": false,
|
||||
"flexibleTypeCapabilitiesId_": 0,
|
||||
"flexibleUpperBoundId_": 0,
|
||||
"className_": 0,
|
||||
"typeParameter_": 0,
|
||||
"typeParameterName_": 0,
|
||||
"typeAliasName_": 0,
|
||||
"outerTypeId_": 0,
|
||||
"abbreviatedTypeId_": 0,
|
||||
"flags_": 0,
|
||||
"memoizedIsInitialized": -1,
|
||||
"memoizedSerializedSize": -1,
|
||||
"extensions": {
|
||||
"fields": {},
|
||||
"isImmutable": false,
|
||||
"hasLazyField": false
|
||||
},
|
||||
"memoizedHashCode": 0
|
||||
},
|
||||
"abbreviatedTypeId_": 0,
|
||||
"flags_": 0,
|
||||
"memoizedIsInitialized": 1,
|
||||
"memoizedSerializedSize": -1,
|
||||
"extensions": {
|
||||
"fields": {},
|
||||
"isImmutable": true,
|
||||
"hasLazyField": false
|
||||
},
|
||||
"memoizedHashCode": 0
|
||||
},
|
||||
"returnTypeId_": 0,
|
||||
"typeParameter_": [],
|
||||
"receiverType_": {
|
||||
"unknownFields": {
|
||||
"bytes": [],
|
||||
"hash": 0
|
||||
},
|
||||
"bitField0_": 0,
|
||||
"argument_": [],
|
||||
"nullable_": false,
|
||||
"flexibleTypeCapabilitiesId_": 0,
|
||||
"flexibleUpperBoundId_": 0,
|
||||
"className_": 0,
|
||||
"typeParameter_": 0,
|
||||
"typeParameterName_": 0,
|
||||
"typeAliasName_": 0,
|
||||
"outerTypeId_": 0,
|
||||
"abbreviatedTypeId_": 0,
|
||||
"flags_": 0,
|
||||
"memoizedIsInitialized": -1,
|
||||
"memoizedSerializedSize": -1,
|
||||
"extensions": {
|
||||
"fields": {},
|
||||
"isImmutable": false,
|
||||
"hasLazyField": false
|
||||
},
|
||||
"memoizedHashCode": 0
|
||||
},
|
||||
"receiverTypeId_": 0,
|
||||
"valueParameter_": [],
|
||||
"typeTable_": {
|
||||
"unknownFields": {
|
||||
"bytes": [],
|
||||
"hash": 0
|
||||
},
|
||||
"bitField0_": 0,
|
||||
"type_": [],
|
||||
"firstNullable_": -1,
|
||||
"memoizedIsInitialized": -1,
|
||||
"memoizedSerializedSize": -1,
|
||||
"memoizedHashCode": 0
|
||||
},
|
||||
"versionRequirement_": [],
|
||||
"contract_": {
|
||||
"unknownFields": {
|
||||
"bytes": [],
|
||||
"hash": 0
|
||||
},
|
||||
"effect_": [],
|
||||
"memoizedIsInitialized": -1,
|
||||
"memoizedSerializedSize": -1,
|
||||
"memoizedHashCode": 0
|
||||
},
|
||||
"memoizedIsInitialized": 1,
|
||||
"memoizedSerializedSize": -1,
|
||||
"extensions": {
|
||||
"fields": {},
|
||||
"isImmutable": true,
|
||||
"hasLazyField": false
|
||||
},
|
||||
"memoizedHashCode": 0
|
||||
},
|
||||
{
|
||||
"unknownFields": {
|
||||
"bytes": [],
|
||||
"hash": 0
|
||||
},
|
||||
"bitField0_": 13,
|
||||
"flags_": 32790,
|
||||
"oldFlags_": 6,
|
||||
"name_": 9,
|
||||
"returnType_": {
|
||||
"unknownFields": {
|
||||
"bytes": [],
|
||||
"hash": 0
|
||||
},
|
||||
"bitField0_": 16,
|
||||
"argument_": [],
|
||||
"nullable_": false,
|
||||
"flexibleTypeCapabilitiesId_": 0,
|
||||
"flexibleUpperBound_": {
|
||||
"unknownFields": {
|
||||
"bytes": [],
|
||||
"hash": 0
|
||||
},
|
||||
"bitField0_": 0,
|
||||
"argument_": [],
|
||||
"nullable_": false,
|
||||
"flexibleTypeCapabilitiesId_": 0,
|
||||
"flexibleUpperBoundId_": 0,
|
||||
"className_": 0,
|
||||
"typeParameter_": 0,
|
||||
"typeParameterName_": 0,
|
||||
"typeAliasName_": 0,
|
||||
"outerTypeId_": 0,
|
||||
"abbreviatedTypeId_": 0,
|
||||
"flags_": 0,
|
||||
"memoizedIsInitialized": -1,
|
||||
"memoizedSerializedSize": -1,
|
||||
"extensions": {
|
||||
"fields": {},
|
||||
"isImmutable": false,
|
||||
"hasLazyField": false
|
||||
},
|
||||
"memoizedHashCode": 0
|
||||
},
|
||||
"flexibleUpperBoundId_": 0,
|
||||
"className_": 7,
|
||||
"typeParameter_": 0,
|
||||
"typeParameterName_": 0,
|
||||
"typeAliasName_": 0,
|
||||
"outerType_": {
|
||||
"unknownFields": {
|
||||
"bytes": [],
|
||||
"hash": 0
|
||||
},
|
||||
"bitField0_": 0,
|
||||
"argument_": [],
|
||||
"nullable_": false,
|
||||
"flexibleTypeCapabilitiesId_": 0,
|
||||
"flexibleUpperBoundId_": 0,
|
||||
"className_": 0,
|
||||
"typeParameter_": 0,
|
||||
"typeParameterName_": 0,
|
||||
"typeAliasName_": 0,
|
||||
"outerTypeId_": 0,
|
||||
"abbreviatedTypeId_": 0,
|
||||
"flags_": 0,
|
||||
"memoizedIsInitialized": -1,
|
||||
"memoizedSerializedSize": -1,
|
||||
"extensions": {
|
||||
"fields": {},
|
||||
"isImmutable": false,
|
||||
"hasLazyField": false
|
||||
},
|
||||
"memoizedHashCode": 0
|
||||
},
|
||||
"outerTypeId_": 0,
|
||||
"abbreviatedType_": {
|
||||
"unknownFields": {
|
||||
"bytes": [],
|
||||
"hash": 0
|
||||
},
|
||||
"bitField0_": 0,
|
||||
"argument_": [],
|
||||
"nullable_": false,
|
||||
"flexibleTypeCapabilitiesId_": 0,
|
||||
"flexibleUpperBoundId_": 0,
|
||||
"className_": 0,
|
||||
"typeParameter_": 0,
|
||||
"typeParameterName_": 0,
|
||||
"typeAliasName_": 0,
|
||||
"outerTypeId_": 0,
|
||||
"abbreviatedTypeId_": 0,
|
||||
"flags_": 0,
|
||||
"memoizedIsInitialized": -1,
|
||||
"memoizedSerializedSize": -1,
|
||||
"extensions": {
|
||||
"fields": {},
|
||||
"isImmutable": false,
|
||||
"hasLazyField": false
|
||||
},
|
||||
"memoizedHashCode": 0
|
||||
},
|
||||
"abbreviatedTypeId_": 0,
|
||||
"flags_": 0,
|
||||
"memoizedIsInitialized": 1,
|
||||
"memoizedSerializedSize": -1,
|
||||
"extensions": {
|
||||
"fields": {},
|
||||
"isImmutable": true,
|
||||
"hasLazyField": false
|
||||
},
|
||||
"memoizedHashCode": 0
|
||||
},
|
||||
"returnTypeId_": 0,
|
||||
"typeParameter_": [],
|
||||
"receiverType_": {
|
||||
"unknownFields": {
|
||||
"bytes": [],
|
||||
"hash": 0
|
||||
},
|
||||
"bitField0_": 0,
|
||||
"argument_": [],
|
||||
"nullable_": false,
|
||||
"flexibleTypeCapabilitiesId_": 0,
|
||||
"flexibleUpperBoundId_": 0,
|
||||
"className_": 0,
|
||||
"typeParameter_": 0,
|
||||
"typeParameterName_": 0,
|
||||
"typeAliasName_": 0,
|
||||
"outerTypeId_": 0,
|
||||
"abbreviatedTypeId_": 0,
|
||||
"flags_": 0,
|
||||
"memoizedIsInitialized": -1,
|
||||
"memoizedSerializedSize": -1,
|
||||
"extensions": {
|
||||
"fields": {},
|
||||
"isImmutable": false,
|
||||
"hasLazyField": false
|
||||
},
|
||||
"memoizedHashCode": 0
|
||||
},
|
||||
"receiverTypeId_": 0,
|
||||
"valueParameter_": [],
|
||||
"typeTable_": {
|
||||
"unknownFields": {
|
||||
"bytes": [],
|
||||
"hash": 0
|
||||
},
|
||||
"bitField0_": 0,
|
||||
"type_": [],
|
||||
"firstNullable_": -1,
|
||||
"memoizedIsInitialized": -1,
|
||||
"memoizedSerializedSize": -1,
|
||||
"memoizedHashCode": 0
|
||||
},
|
||||
"versionRequirement_": [],
|
||||
"contract_": {
|
||||
"unknownFields": {
|
||||
"bytes": [],
|
||||
"hash": 0
|
||||
},
|
||||
"effect_": [],
|
||||
"memoizedIsInitialized": -1,
|
||||
"memoizedSerializedSize": -1,
|
||||
"memoizedHashCode": 0
|
||||
},
|
||||
"memoizedIsInitialized": 1,
|
||||
"memoizedSerializedSize": -1,
|
||||
"extensions": {
|
||||
"fields": {},
|
||||
"isImmutable": true,
|
||||
"hasLazyField": false
|
||||
},
|
||||
"memoizedHashCode": 0
|
||||
}
|
||||
],
|
||||
"property_": [],
|
||||
"typeAlias_": [],
|
||||
"enumEntry_": [],
|
||||
"sealedSubclassFqName_": [],
|
||||
"sealedSubclassFqNameMemoizedSerializedSize": -1,
|
||||
"inlineClassUnderlyingPropertyName_": 0,
|
||||
"inlineClassUnderlyingType_": {
|
||||
"unknownFields": {
|
||||
"bytes": [],
|
||||
"hash": 0
|
||||
},
|
||||
"bitField0_": 0,
|
||||
"argument_": [],
|
||||
"nullable_": false,
|
||||
"flexibleTypeCapabilitiesId_": 0,
|
||||
"flexibleUpperBoundId_": 0,
|
||||
"className_": 0,
|
||||
"typeParameter_": 0,
|
||||
"typeParameterName_": 0,
|
||||
"typeAliasName_": 0,
|
||||
"outerTypeId_": 0,
|
||||
"abbreviatedTypeId_": 0,
|
||||
"flags_": 0,
|
||||
"memoizedIsInitialized": -1,
|
||||
"memoizedSerializedSize": -1,
|
||||
"extensions": {
|
||||
"fields": {},
|
||||
"isImmutable": false,
|
||||
"hasLazyField": false
|
||||
},
|
||||
"memoizedHashCode": 0
|
||||
},
|
||||
"inlineClassUnderlyingTypeId_": 0,
|
||||
"typeTable_": {
|
||||
"unknownFields": {
|
||||
"bytes": [],
|
||||
"hash": 0
|
||||
},
|
||||
"bitField0_": 0,
|
||||
"type_": [],
|
||||
"firstNullable_": -1,
|
||||
"memoizedIsInitialized": -1,
|
||||
"memoizedSerializedSize": -1,
|
||||
"memoizedHashCode": 0
|
||||
},
|
||||
"versionRequirement_": [],
|
||||
"versionRequirementTable_": {
|
||||
"unknownFields": {
|
||||
"bytes": [],
|
||||
"hash": 0
|
||||
},
|
||||
"requirement_": [],
|
||||
"memoizedIsInitialized": -1,
|
||||
"memoizedSerializedSize": -1,
|
||||
"memoizedHashCode": 0
|
||||
},
|
||||
"memoizedIsInitialized": 1,
|
||||
"memoizedSerializedSize": -1,
|
||||
"extensions": {
|
||||
"fields": {},
|
||||
"isImmutable": true,
|
||||
"hasLazyField": false
|
||||
},
|
||||
"memoizedHashCode": 0
|
||||
},
|
||||
"stringTable": {
|
||||
"unknownFields": {
|
||||
"bytes": [],
|
||||
"hash": 0
|
||||
},
|
||||
"string_": [
|
||||
"com",
|
||||
"example",
|
||||
"SimpleJavaClass",
|
||||
"java",
|
||||
"lang",
|
||||
"Object",
|
||||
"privateMethod",
|
||||
"kotlin",
|
||||
"Unit",
|
||||
"publicMethod"
|
||||
],
|
||||
"memoizedIsInitialized": 1,
|
||||
"memoizedSerializedSize": -1,
|
||||
"memoizedHashCode": 0
|
||||
},
|
||||
"qualifiedNameTable": {
|
||||
"unknownFields": {
|
||||
"bytes": [],
|
||||
"hash": 0
|
||||
},
|
||||
"qualifiedName_": [
|
||||
{
|
||||
"unknownFields": {
|
||||
"bytes": [],
|
||||
"hash": 0
|
||||
},
|
||||
"bitField0_": 2,
|
||||
"parentQualifiedName_": -1,
|
||||
"shortName_": 0,
|
||||
"kind_": "PACKAGE",
|
||||
"memoizedIsInitialized": 1,
|
||||
"memoizedSerializedSize": -1,
|
||||
"memoizedHashCode": 0
|
||||
},
|
||||
{
|
||||
"unknownFields": {
|
||||
"bytes": [],
|
||||
"hash": 0
|
||||
},
|
||||
"bitField0_": 3,
|
||||
"parentQualifiedName_": 0,
|
||||
"shortName_": 1,
|
||||
"kind_": "PACKAGE",
|
||||
"memoizedIsInitialized": 1,
|
||||
"memoizedSerializedSize": -1,
|
||||
"memoizedHashCode": 0
|
||||
},
|
||||
{
|
||||
"unknownFields": {
|
||||
"bytes": [],
|
||||
"hash": 0
|
||||
},
|
||||
"bitField0_": 7,
|
||||
"parentQualifiedName_": 1,
|
||||
"shortName_": 2,
|
||||
"kind_": "CLASS",
|
||||
"memoizedIsInitialized": 1,
|
||||
"memoizedSerializedSize": -1,
|
||||
"memoizedHashCode": 0
|
||||
},
|
||||
{
|
||||
"unknownFields": {
|
||||
"bytes": [],
|
||||
"hash": 0
|
||||
},
|
||||
"bitField0_": 2,
|
||||
"parentQualifiedName_": -1,
|
||||
"shortName_": 3,
|
||||
"kind_": "PACKAGE",
|
||||
"memoizedIsInitialized": 1,
|
||||
"memoizedSerializedSize": -1,
|
||||
"memoizedHashCode": 0
|
||||
},
|
||||
{
|
||||
"unknownFields": {
|
||||
"bytes": [],
|
||||
"hash": 0
|
||||
},
|
||||
"bitField0_": 3,
|
||||
"parentQualifiedName_": 3,
|
||||
"shortName_": 4,
|
||||
"kind_": "PACKAGE",
|
||||
"memoizedIsInitialized": 1,
|
||||
"memoizedSerializedSize": -1,
|
||||
"memoizedHashCode": 0
|
||||
},
|
||||
{
|
||||
"unknownFields": {
|
||||
"bytes": [],
|
||||
"hash": 0
|
||||
},
|
||||
"bitField0_": 7,
|
||||
"parentQualifiedName_": 4,
|
||||
"shortName_": 5,
|
||||
"kind_": "CLASS",
|
||||
"memoizedIsInitialized": 1,
|
||||
"memoizedSerializedSize": -1,
|
||||
"memoizedHashCode": 0
|
||||
},
|
||||
{
|
||||
"unknownFields": {
|
||||
"bytes": [],
|
||||
"hash": 0
|
||||
},
|
||||
"bitField0_": 2,
|
||||
"parentQualifiedName_": -1,
|
||||
"shortName_": 7,
|
||||
"kind_": "PACKAGE",
|
||||
"memoizedIsInitialized": 1,
|
||||
"memoizedSerializedSize": -1,
|
||||
"memoizedHashCode": 0
|
||||
},
|
||||
{
|
||||
"unknownFields": {
|
||||
"bytes": [],
|
||||
"hash": 0
|
||||
},
|
||||
"bitField0_": 7,
|
||||
"parentQualifiedName_": 6,
|
||||
"shortName_": 8,
|
||||
"kind_": "CLASS",
|
||||
"memoizedIsInitialized": 1,
|
||||
"memoizedSerializedSize": -1,
|
||||
"memoizedHashCode": 0
|
||||
}
|
||||
],
|
||||
"memoizedIsInitialized": 1,
|
||||
"memoizedSerializedSize": -1,
|
||||
"memoizedHashCode": 0
|
||||
}
|
||||
}
|
||||
}
|
||||
-35
@@ -1,35 +0,0 @@
|
||||
{
|
||||
"classInfo": {
|
||||
"classId": {
|
||||
"packageFqName": {
|
||||
"fqName": {
|
||||
"fqName": "com.example"
|
||||
}
|
||||
},
|
||||
"relativeClassName": {
|
||||
"fqName": {
|
||||
"fqName": "SimpleKotlinClass"
|
||||
}
|
||||
},
|
||||
"local": false
|
||||
},
|
||||
"classKind": "CLASS",
|
||||
"classHeaderData": [
|
||||
"\u0000\u0012\n\u0002\u0018\u0002\n\u0002\u0010\u0000\n\u0002\b\u0002\n\u0002\u0010\u0002\n\u0000\u0018\u00002\u00020\u0001B\u0005¢\u0006\u0002\u0010\u0002J\b\u0010\u0003\u001a\u00020\u0004H\u0002J\u0006\u0010\u0005\u001a\u00020\u0004"
|
||||
],
|
||||
"classHeaderStrings": [
|
||||
"Lcom/example/SimpleKotlinClass;",
|
||||
"",
|
||||
"()V",
|
||||
"privateMethod",
|
||||
"",
|
||||
"publicMethod"
|
||||
],
|
||||
"constantsMap": {},
|
||||
"inlineFunctionsMap": {},
|
||||
"className$delegate": {
|
||||
"initializer": {},
|
||||
"_value": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
-12
@@ -1,12 +0,0 @@
|
||||
package com.example
|
||||
|
||||
class SimpleKotlinClass {
|
||||
|
||||
fun publicMethod() {
|
||||
println("I'm in a public method")
|
||||
}
|
||||
|
||||
private fun privateMethod() {
|
||||
println("I'm in a private method")
|
||||
}
|
||||
}
|
||||
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
+10
@@ -0,0 +1,10 @@
|
||||
package com.example;
|
||||
|
||||
public class ChangedJavaSuperClass {
|
||||
|
||||
public String changedField = "";
|
||||
|
||||
public String changedMethod() {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
package com.example;
|
||||
|
||||
open class ChangedKotlinSuperClass {
|
||||
val changedProperty = ""
|
||||
fun changedFunction() = ""
|
||||
}
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
package com.example;
|
||||
|
||||
public class JavaSubClassOfJavaSuperClass extends ChangedJavaSuperClass {
|
||||
}
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
package com.example;
|
||||
|
||||
public class JavaSubClassOfKotlinSuperClass extends ChangedKotlinSuperClass {
|
||||
}
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
package com.example
|
||||
|
||||
class KotlinSubClassOfJavaSuperClass : ChangedJavaSuperClass()
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
package com.example
|
||||
|
||||
class KotlinSubClassOfKotlinSuperClass : ChangedKotlinSuperClass()
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
package com.example;
|
||||
|
||||
public class UnimpactedJavaClass {
|
||||
}
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
package com.example
|
||||
|
||||
class UnimpactedKotlinClass
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
package com.example;
|
||||
|
||||
public class ChangedJavaSuperClass {
|
||||
|
||||
public int changedField = 0;
|
||||
|
||||
public void changedMethod() {
|
||||
}
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
package com.example;
|
||||
|
||||
open class ChangedKotlinSuperClass {
|
||||
val changedProperty = 0
|
||||
fun changedFunction() {}
|
||||
}
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
package com.example;
|
||||
|
||||
public class JavaSubClassOfJavaSuperClass extends ChangedJavaSuperClass {
|
||||
}
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
package com.example;
|
||||
|
||||
public class JavaSubClassOfKotlinSuperClass extends ChangedKotlinSuperClass {
|
||||
}
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
package com.example
|
||||
|
||||
class KotlinSubClassOfJavaSuperClass : ChangedJavaSuperClass()
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
package com.example
|
||||
|
||||
class KotlinSubClassOfKotlinSuperClass : ChangedKotlinSuperClass()
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
package com.example;
|
||||
|
||||
public class UnimpactedJavaClass {
|
||||
}
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
package com.example
|
||||
|
||||
class UnimpactedKotlinClass
|
||||
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
+10
@@ -0,0 +1,10 @@
|
||||
package com.example;
|
||||
|
||||
public class ChangedSuperClass {
|
||||
|
||||
public String changedField = "";
|
||||
|
||||
public String changedMethod() {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
package com.example;
|
||||
|
||||
public class SubClass extends ChangedSuperClass {
|
||||
}
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
package com.example;
|
||||
|
||||
public class SubSubClass extends SubClass {
|
||||
}
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
package com.example;
|
||||
|
||||
public class UnimpactedClass {
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
package com.example;
|
||||
|
||||
public class ChangedSuperClass {
|
||||
|
||||
public int changedField = 0;
|
||||
|
||||
public void changedMethod() {
|
||||
}
|
||||
}
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
package com.example;
|
||||
|
||||
public class SubClass extends ChangedSuperClass {
|
||||
}
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
package com.example;
|
||||
|
||||
public class SubSubClass extends SubClass {
|
||||
}
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
package com.example;
|
||||
|
||||
public class UnimpactedClass {
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
package com.example;
|
||||
|
||||
open class ChangedSuperClass {
|
||||
val changedProperty = ""
|
||||
fun changedFunction() = ""
|
||||
}
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
package com.example
|
||||
|
||||
open class SubClass : ChangedSuperClass()
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
package com.example
|
||||
|
||||
class SubSubClass : SubClass()
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
package com.example
|
||||
|
||||
class UnimpactedClass
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
package com.example;
|
||||
|
||||
open class ChangedSuperClass {
|
||||
val changedProperty = 0
|
||||
fun changedFunction() {}
|
||||
}
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
package com.example
|
||||
|
||||
open class SubClass : ChangedSuperClass()
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
package com.example
|
||||
|
||||
class SubSubClass : SubClass()
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
package com.example
|
||||
|
||||
class UnimpactedClass
|
||||
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user