KT-45777: Reorganize unit test data for classpath snapshot feature
to make it easier to add more tests in the next commits. - Add unit tests for constants and inline functions Also add tests for different kinds of Kotlin classes: CLASS, FILE_FACADE, MULTIFILE_CLASS. -Add unit test for nested classes Also remove the existing integration test for nested classes to keep the integration tests focused on the key scenarios while unit tests will cover the corner cases. Ignore inline functions that are private
This commit is contained in:
committed by
nataliya.valtman
parent
534cf0c6c8
commit
a900f2b66d
Generated
+1
@@ -1,6 +1,7 @@
|
||||
<component name="ProjectDictionaryState">
|
||||
<dictionary name="hungnv">
|
||||
<words>
|
||||
<w>multifile</w>
|
||||
<w>shrinker</w>
|
||||
<w>snapshotter</w>
|
||||
<w>snapshotter's</w>
|
||||
|
||||
@@ -652,8 +652,10 @@ private fun getConstantsMap(bytes: ByteArray): LinkedHashMap<String, Any> {
|
||||
|
||||
ClassReader(bytes).accept(object : ClassVisitor(Opcodes.API_VERSION) {
|
||||
override fun visitField(access: Int, name: String, desc: String, signature: String?, value: Any?): FieldVisitor? {
|
||||
val staticFinal = Opcodes.ACC_STATIC or Opcodes.ACC_FINAL or Opcodes.ACC_PRIVATE
|
||||
if (value != null && access and staticFinal == Opcodes.ACC_STATIC or Opcodes.ACC_FINAL) {
|
||||
if (access and Opcodes.ACC_PRIVATE == Opcodes.ACC_PRIVATE) return null
|
||||
|
||||
val staticFinal = Opcodes.ACC_STATIC or Opcodes.ACC_FINAL
|
||||
if (value != null && access and staticFinal == staticFinal) {
|
||||
result[name] = value
|
||||
}
|
||||
return null
|
||||
@@ -689,7 +691,9 @@ private fun getInlineFunctionsMap(header: KotlinClassHeader, bytes: ByteArray):
|
||||
desc: String,
|
||||
signature: String?,
|
||||
exceptions: Array<out String>?
|
||||
): MethodVisitor {
|
||||
): MethodVisitor? {
|
||||
if (access and Opcodes.ACC_PRIVATE == Opcodes.ACC_PRIVATE) return null
|
||||
|
||||
val dummyClassWriter = ClassWriter(0)
|
||||
dummyClassWriter.visit(dummyVersion, 0, "dummy", null, AsmTypes.OBJECT_TYPE.internalName, null)
|
||||
|
||||
|
||||
@@ -204,6 +204,9 @@ object ConstantExternalizer : DataExternalizer<Any> {
|
||||
}
|
||||
}
|
||||
|
||||
// The constants' values are provided by ASM, so their types can only be the following.
|
||||
// See https://asm.ow2.io/javadoc/org/objectweb/asm/ClassVisitor.html#visitField(int,java.lang.String,java.lang.String,java.lang.String,java.lang.Object)
|
||||
// (Note: Boolean constants have Integer (0, 1) values in ASM.)
|
||||
private enum class Kind {
|
||||
INT, FLOAT, LONG, DOUBLE, STRING
|
||||
}
|
||||
|
||||
+1
-1
@@ -143,7 +143,7 @@ internal fun ClasspathSnapshot.removeDuplicateAndInaccessibleClasses(): List<Cla
|
||||
*
|
||||
* If there are duplicate classes on the classpath, retain only the first one to match the compiler's behavior.
|
||||
*/
|
||||
internal fun ClasspathSnapshot.getNonDuplicateClassSnapshots(): List<ClassSnapshotWithHash> {
|
||||
private fun ClasspathSnapshot.getNonDuplicateClassSnapshots(): List<ClassSnapshotWithHash> {
|
||||
val classSnapshots = LinkedHashMap<String, ClassSnapshotWithHash>(classpathEntrySnapshots.sumOf { it.classSnapshots.size })
|
||||
for (classpathEntrySnapshot in classpathEntrySnapshots) {
|
||||
for ((unixStyleRelativePath, classSnapshot) in classpathEntrySnapshot.classSnapshots) {
|
||||
|
||||
+159
-133
@@ -8,9 +8,8 @@ package org.jetbrains.kotlin.incremental.classpathDiff
|
||||
import org.jetbrains.kotlin.build.report.metrics.DoNothingBuildMetricsReporter
|
||||
import org.jetbrains.kotlin.incremental.ChangesEither
|
||||
import org.jetbrains.kotlin.incremental.LookupSymbol
|
||||
import org.jetbrains.kotlin.incremental.classpathDiff.ClasspathSnapshotTestCommon.Util.compileAll
|
||||
import org.jetbrains.kotlin.incremental.classpathDiff.ClasspathSnapshotTestCommon.Util.snapshot
|
||||
import org.jetbrains.kotlin.incremental.classpathDiff.ClasspathSnapshotTestCommon.Util.snapshotAll
|
||||
import org.jetbrains.kotlin.incremental.classpathDiff.ClasspathSnapshotTestCommon.ClassFileUtil.snapshot
|
||||
import org.jetbrains.kotlin.incremental.classpathDiff.ClasspathSnapshotTestCommon.CompileUtil.compileAll
|
||||
import org.jetbrains.kotlin.resolve.sam.SAM_LOOKUP_NAME
|
||||
import org.junit.Test
|
||||
import org.junit.rules.TemporaryFolder
|
||||
@@ -21,20 +20,16 @@ import kotlin.test.fail
|
||||
|
||||
abstract class ClasspathChangesComputerTest : ClasspathSnapshotTestCommon() {
|
||||
|
||||
// TODO Add more test cases:
|
||||
// - private/non-private fields
|
||||
// - inline functions
|
||||
// - changing supertype by adding somethings that changes/does not change the supertype ABI
|
||||
// - adding an annotation
|
||||
companion object {
|
||||
val testDataDir =
|
||||
File("compiler/incremental-compilation-impl/testData/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathChangesComputerTest")
|
||||
}
|
||||
|
||||
@Test
|
||||
abstract fun testAbiChange_changePublicMethodSignature()
|
||||
abstract fun testAbiVersusNonAbiChanges()
|
||||
|
||||
@Test
|
||||
abstract fun testNonAbiChange_changeMethodImplementation()
|
||||
|
||||
@Test
|
||||
abstract fun testVariousAbiChanges()
|
||||
abstract fun testModifiedAddedRemovedElements()
|
||||
|
||||
@Test
|
||||
abstract fun testImpactAnalysis()
|
||||
@@ -43,38 +38,21 @@ abstract class ClasspathChangesComputerTest : ClasspathSnapshotTestCommon() {
|
||||
class KotlinOnlyClasspathChangesComputerTest : ClasspathChangesComputerTest() {
|
||||
|
||||
@Test
|
||||
override fun testAbiChange_changePublicMethodSignature() {
|
||||
val sourceFile = SimpleKotlinClass(tmpDir)
|
||||
val previousSnapshot = sourceFile.compileAndSnapshot()
|
||||
val currentSnapshot = sourceFile.changePublicMethodSignature().compileAndSnapshot()
|
||||
val changes = computeClassChanges(currentSnapshot, previousSnapshot)
|
||||
|
||||
override fun testAbiVersusNonAbiChanges() {
|
||||
val changes = computeClasspathChanges(File(testDataDir, "testAbiVersusNonAbiChanges/src/kotlin"), tmpDir)
|
||||
Changes(
|
||||
lookupSymbols = setOf(
|
||||
LookupSymbol(name = "publicFunction", scope = "com.example.SimpleKotlinClass"),
|
||||
LookupSymbol(name = SAM_LOOKUP_NAME.asString(), scope = "com.example.SimpleKotlinClass")
|
||||
LookupSymbol(name = "publicPropertyChangedType", scope = "com.example.SomeClass"),
|
||||
LookupSymbol(name = "publicFunctionChangedSignature", scope = "com.example.SomeClass"),
|
||||
LookupSymbol(name = SAM_LOOKUP_NAME.asString(), scope = "com.example.SomeClass"),
|
||||
),
|
||||
fqNames = setOf("com.example.SimpleKotlinClass")
|
||||
fqNames = setOf("com.example.SomeClass")
|
||||
).assertEquals(changes)
|
||||
}
|
||||
|
||||
@Test
|
||||
override fun testNonAbiChange_changeMethodImplementation() {
|
||||
val sourceFile = SimpleKotlinClass(tmpDir)
|
||||
val previousSnapshot = sourceFile.compileAndSnapshot()
|
||||
val currentSnapshot = sourceFile.changeMethodImplementation().compileAndSnapshot()
|
||||
val changes = computeClassChanges(currentSnapshot, previousSnapshot)
|
||||
|
||||
Changes(emptySet(), emptySet()).assertEquals(changes)
|
||||
}
|
||||
|
||||
@Test
|
||||
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 = computeClasspathChanges(currentSnapshot, previousSnapshot)
|
||||
|
||||
override fun testModifiedAddedRemovedElements() {
|
||||
val changes = computeClasspathChanges(File(testDataDir, "testModifiedAddedRemovedElements/src/kotlin"), tmpDir)
|
||||
Changes(
|
||||
lookupSymbols = setOf(
|
||||
// ModifiedClassUnchangedMembers
|
||||
@@ -94,8 +72,44 @@ class KotlinOnlyClasspathChangesComputerTest : ClasspathChangesComputerTest() {
|
||||
|
||||
// RemovedClass
|
||||
LookupSymbol(name = "RemovedClass", scope = "com.example"),
|
||||
),
|
||||
fqNames = setOf(
|
||||
"com.example.ModifiedClassUnchangedMembers",
|
||||
"com.example.ModifiedClassChangedMembers",
|
||||
"com.example.AddedClass",
|
||||
"com.example.RemovedClass",
|
||||
)
|
||||
).assertEquals(changes)
|
||||
}
|
||||
|
||||
// Top-level properties and functions
|
||||
@Test
|
||||
override fun testImpactAnalysis() {
|
||||
val changes = computeClasspathChanges(File(testDataDir, "testImpactAnalysis_KotlinOnly/src"), tmpDir)
|
||||
Changes(
|
||||
lookupSymbols = setOf(
|
||||
LookupSymbol(name = "changedProperty", scope = "com.example.ChangedSuperClass"),
|
||||
LookupSymbol(name = "changedProperty", scope = "com.example.SubClassOfChangedSuperClass"),
|
||||
LookupSymbol(name = "changedProperty", scope = "com.example.SubSubClassOfChangedSuperClass"),
|
||||
LookupSymbol(name = "changedFunction", scope = "com.example.ChangedSuperClass"),
|
||||
LookupSymbol(name = "changedFunction", scope = "com.example.SubClassOfChangedSuperClass"),
|
||||
LookupSymbol(name = "changedFunction", scope = "com.example.SubSubClassOfChangedSuperClass"),
|
||||
LookupSymbol(name = SAM_LOOKUP_NAME.asString(), scope = "com.example.ChangedSuperClass"),
|
||||
LookupSymbol(name = SAM_LOOKUP_NAME.asString(), scope = "com.example.SubClassOfChangedSuperClass"),
|
||||
LookupSymbol(name = SAM_LOOKUP_NAME.asString(), scope = "com.example.SubSubClassOfChangedSuperClass")
|
||||
),
|
||||
fqNames = setOf(
|
||||
"com.example.ChangedSuperClass",
|
||||
"com.example.SubClassOfChangedSuperClass",
|
||||
"com.example.SubSubClassOfChangedSuperClass"
|
||||
)
|
||||
).assertEquals(changes)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testTopLevelMembers() {
|
||||
val changes = computeClasspathChanges(File(testDataDir, "testTopLevelMembers_KotlinOnly/src"), tmpDir)
|
||||
Changes(
|
||||
lookupSymbols = setOf(
|
||||
LookupSymbol(name = "modifiedTopLevelProperty", scope = "com.example"),
|
||||
LookupSymbol(name = "addedTopLevelProperty", scope = "com.example"),
|
||||
LookupSymbol(name = "removedTopLevelProperty", scope = "com.example"),
|
||||
@@ -106,40 +120,78 @@ class KotlinOnlyClasspathChangesComputerTest : ClasspathChangesComputerTest() {
|
||||
LookupSymbol(name = "movedTopLevelFunction", scope = "com.example"),
|
||||
LookupSymbol(name = SAM_LOOKUP_NAME.asString(), scope = "com.example")
|
||||
),
|
||||
fqNames = setOf("com.example")
|
||||
).assertEquals(changes)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testDifferentClassKinds() {
|
||||
val changes = computeClasspathChanges(File(testDataDir, "testDifferentClassKinds_KotlinOnly/src"), tmpDir)
|
||||
Changes(
|
||||
lookupSymbols = setOf(
|
||||
// NormalClass
|
||||
LookupSymbol(name = "propertyInNormalClass", scope = "com.example.NormalClass"),
|
||||
LookupSymbol(name = "functionInNormalClass", scope = "com.example.NormalClass"),
|
||||
|
||||
// NormalClass.CompanionObject
|
||||
LookupSymbol(name = "propertyInCompanionObject", scope = "com.example.NormalClass.CompanionObject"),
|
||||
LookupSymbol(name = "functionInCompanionObject", scope = "com.example.NormalClass.CompanionObject"),
|
||||
|
||||
// NormalClass.NestedClass
|
||||
LookupSymbol(name = "propertyInNestedClass", scope = "com.example.NormalClass.NestedClass"),
|
||||
LookupSymbol(name = "functionInNestedClass", scope = "com.example.NormalClass.NestedClass"),
|
||||
|
||||
// FileFacade
|
||||
LookupSymbol(name = "propertyInFileFacade", scope = "com.example"),
|
||||
LookupSymbol(name = "functionInFileFacade", scope = "com.example"),
|
||||
|
||||
// MultifileClass
|
||||
LookupSymbol(name = "propertyInMultifileClass1", scope = "com.example"),
|
||||
LookupSymbol(name = "functionInMultifileClass1", scope = "com.example"),
|
||||
LookupSymbol(name = "propertyInMultifileClass2", scope = "com.example"),
|
||||
LookupSymbol(name = "functionInMultifileClass2", scope = "com.example"),
|
||||
|
||||
LookupSymbol(name = SAM_LOOKUP_NAME.asString(), scope = "com.example.NormalClass"),
|
||||
LookupSymbol(name = SAM_LOOKUP_NAME.asString(), scope = "com.example.NormalClass.CompanionObject"),
|
||||
LookupSymbol(name = SAM_LOOKUP_NAME.asString(), scope = "com.example.NormalClass.NestedClass"),
|
||||
LookupSymbol(name = SAM_LOOKUP_NAME.asString(), scope = "com.example"),
|
||||
),
|
||||
fqNames = setOf(
|
||||
"com.example.ModifiedClassUnchangedMembers",
|
||||
"com.example.ModifiedClassChangedMembers",
|
||||
"com.example.AddedClass",
|
||||
"com.example.RemovedClass",
|
||||
"com.example.NormalClass",
|
||||
"com.example.NormalClass.CompanionObject",
|
||||
"com.example.NormalClass.NestedClass",
|
||||
"com.example"
|
||||
)
|
||||
).assertEquals(changes)
|
||||
}
|
||||
|
||||
@Test
|
||||
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 = computeClasspathChanges(currentSnapshot, previousSnapshot)
|
||||
|
||||
fun testConstantsAndInlineFunctions() {
|
||||
val changes = computeClasspathChanges(File(testDataDir, "testConstantsAndInlineFunctions_KotlinOnly/src"), tmpDir)
|
||||
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")
|
||||
LookupSymbol(name = "constantChangedType", scope = "com.example.SomeClass.CompanionObject"),
|
||||
// TODO (Fix in next commit). Missing:
|
||||
// LookupSymbol(name = "constantChangedValue", scope = "com.example.SomeClass.CompanionObject")
|
||||
|
||||
LookupSymbol(name = "inlineFunctionChangedSignature", scope = "com.example.SomeClass"),
|
||||
LookupSymbol(name = "inlineFunctionChangedImplementation", scope = "com.example.SomeClass"),
|
||||
|
||||
LookupSymbol(name = SAM_LOOKUP_NAME.asString(), scope = "com.example.SomeClass"),
|
||||
LookupSymbol(name = SAM_LOOKUP_NAME.asString(), scope = "com.example.SomeClass.CompanionObject"),
|
||||
|
||||
// TODO (Fix in next commit). Incorrect:
|
||||
LookupSymbol(name = "constantChangedType", scope = "com.example.SomeClass"),
|
||||
LookupSymbol(name = "constantChangedType", scope = "com.example.SomeClass.Companion"),
|
||||
LookupSymbol(name = "constantChangedValue", scope = "com.example.SomeClass"),
|
||||
LookupSymbol(name = "constantChangedValue", scope = "com.example.SomeClass.Companion"),
|
||||
LookupSymbol(name = SAM_LOOKUP_NAME.asString(), scope = "com.example.SomeClass.Companion"),
|
||||
),
|
||||
fqNames = setOf(
|
||||
"com.example.ChangedSuperClass",
|
||||
"com.example.SubClass",
|
||||
"com.example.SubSubClass"
|
||||
"com.example.SomeClass",
|
||||
"com.example.SomeClass.CompanionObject",
|
||||
// TODO (Fix in next commit). Incorrect:
|
||||
"com.example.SomeClass.Companion"
|
||||
)
|
||||
).assertEquals(changes)
|
||||
}
|
||||
@@ -155,38 +207,21 @@ class JavaOnlyClasspathChangesComputerTest(private val protoBased: Boolean) : Cl
|
||||
}
|
||||
|
||||
@Test
|
||||
override fun testAbiChange_changePublicMethodSignature() {
|
||||
val sourceFile = SimpleJavaClass(tmpDir)
|
||||
val previousSnapshot = sourceFile.compile().snapshot(protoBased)
|
||||
val currentSnapshot = sourceFile.changePublicMethodSignature().compile().snapshot(protoBased)
|
||||
val changes = computeClassChanges(currentSnapshot, previousSnapshot)
|
||||
|
||||
override fun testAbiVersusNonAbiChanges() {
|
||||
val changes = computeClasspathChanges(File(testDataDir, "testAbiVersusNonAbiChanges/src/java"), tmpDir, protoBased)
|
||||
Changes(
|
||||
lookupSymbols = setOf(
|
||||
LookupSymbol(name = "publicMethod", scope = "com.example.SimpleJavaClass"),
|
||||
LookupSymbol(name = SAM_LOOKUP_NAME.asString(), scope = "com.example.SimpleJavaClass")
|
||||
LookupSymbol(name = "publicFieldChangedType", scope = "com.example.SomeClass"),
|
||||
LookupSymbol(name = "publicMethodChangedSignature", scope = "com.example.SomeClass"),
|
||||
LookupSymbol(name = SAM_LOOKUP_NAME.asString(), scope = "com.example.SomeClass")
|
||||
),
|
||||
fqNames = setOf("com.example.SimpleJavaClass")
|
||||
fqNames = setOf("com.example.SomeClass")
|
||||
).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 = computeClassChanges(currentSnapshot, previousSnapshot)
|
||||
|
||||
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 = computeClasspathChanges(currentSnapshot, previousSnapshot)
|
||||
|
||||
override fun testModifiedAddedRemovedElements() {
|
||||
val changes = computeClasspathChanges(File(testDataDir, "testModifiedAddedRemovedElements/src/java"), tmpDir, protoBased)
|
||||
Changes(
|
||||
lookupSymbols = setOf(
|
||||
// ModifiedClassUnchangedMembers
|
||||
@@ -218,27 +253,23 @@ class JavaOnlyClasspathChangesComputerTest(private val protoBased: Boolean) : Cl
|
||||
|
||||
@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 = computeClasspathChanges(currentSnapshot, previousSnapshot)
|
||||
|
||||
val changes = computeClasspathChanges(File(testDataDir, "testImpactAnalysis_JavaOnly/src"), tmpDir, protoBased)
|
||||
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 = "changedField", scope = "com.example.SubClassOfChangedSuperClass"),
|
||||
LookupSymbol(name = "changedField", scope = "com.example.SubSubClassOfChangedSuperClass"),
|
||||
LookupSymbol(name = "changedMethod", scope = "com.example.ChangedSuperClass"),
|
||||
LookupSymbol(name = "changedMethod", scope = "com.example.SubClass"),
|
||||
LookupSymbol(name = "changedMethod", scope = "com.example.SubSubClass"),
|
||||
LookupSymbol(name = "changedMethod", scope = "com.example.SubClassOfChangedSuperClass"),
|
||||
LookupSymbol(name = "changedMethod", scope = "com.example.SubSubClassOfChangedSuperClass"),
|
||||
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")
|
||||
LookupSymbol(name = SAM_LOOKUP_NAME.asString(), scope = "com.example.SubClassOfChangedSuperClass"),
|
||||
LookupSymbol(name = SAM_LOOKUP_NAME.asString(), scope = "com.example.SubSubClassOfChangedSuperClass")
|
||||
),
|
||||
fqNames = setOf(
|
||||
"com.example.ChangedSuperClass",
|
||||
"com.example.SubClass",
|
||||
"com.example.SubSubClass"
|
||||
"com.example.SubClassOfChangedSuperClass",
|
||||
"com.example.SubSubClassOfChangedSuperClass"
|
||||
)
|
||||
).assertEquals(changes)
|
||||
}
|
||||
@@ -248,52 +279,55 @@ 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 = computeClasspathChanges(currentSnapshot, previousSnapshot)
|
||||
|
||||
val changes =
|
||||
computeClasspathChanges(File(ClasspathChangesComputerTest.testDataDir, "testImpactAnalysis_KotlinAndJava/src"), tmpDir)
|
||||
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 = "changedProperty", scope = "com.example.KotlinSubClassOfChangedKotlinSuperClass"),
|
||||
LookupSymbol(name = "changedProperty", scope = "com.example.JavaSubClassOfChangedKotlinSuperClass"),
|
||||
LookupSymbol(name = "changedFunction", scope = "com.example.ChangedKotlinSuperClass"),
|
||||
LookupSymbol(name = "changedFunction", scope = "com.example.KotlinSubClassOfKotlinSuperClass"),
|
||||
LookupSymbol(name = "changedFunction", scope = "com.example.JavaSubClassOfKotlinSuperClass"),
|
||||
LookupSymbol(name = "changedFunction", scope = "com.example.KotlinSubClassOfChangedKotlinSuperClass"),
|
||||
LookupSymbol(name = "changedFunction", scope = "com.example.JavaSubClassOfChangedKotlinSuperClass"),
|
||||
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 = SAM_LOOKUP_NAME.asString(), scope = "com.example.KotlinSubClassOfChangedKotlinSuperClass"),
|
||||
LookupSymbol(name = SAM_LOOKUP_NAME.asString(), scope = "com.example.JavaSubClassOfChangedKotlinSuperClass"),
|
||||
LookupSymbol(name = "changedField", scope = "com.example.ChangedJavaSuperClass"),
|
||||
LookupSymbol(name = "changedField", scope = "com.example.KotlinSubClassOfJavaSuperClass"),
|
||||
LookupSymbol(name = "changedField", scope = "com.example.JavaSubClassOfJavaSuperClass"),
|
||||
LookupSymbol(name = "changedField", scope = "com.example.KotlinSubClassOfChangedJavaSuperClass"),
|
||||
LookupSymbol(name = "changedField", scope = "com.example.JavaSubClassOfChangedJavaSuperClass"),
|
||||
LookupSymbol(name = "changedMethod", scope = "com.example.ChangedJavaSuperClass"),
|
||||
LookupSymbol(name = "changedMethod", scope = "com.example.KotlinSubClassOfJavaSuperClass"),
|
||||
LookupSymbol(name = "changedMethod", scope = "com.example.JavaSubClassOfJavaSuperClass"),
|
||||
LookupSymbol(name = "changedMethod", scope = "com.example.KotlinSubClassOfChangedJavaSuperClass"),
|
||||
LookupSymbol(name = "changedMethod", scope = "com.example.JavaSubClassOfChangedJavaSuperClass"),
|
||||
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")
|
||||
LookupSymbol(name = SAM_LOOKUP_NAME.asString(), scope = "com.example.KotlinSubClassOfChangedJavaSuperClass"),
|
||||
LookupSymbol(name = SAM_LOOKUP_NAME.asString(), scope = "com.example.JavaSubClassOfChangedJavaSuperClass")
|
||||
),
|
||||
fqNames = setOf(
|
||||
"com.example.ChangedKotlinSuperClass",
|
||||
"com.example.KotlinSubClassOfKotlinSuperClass",
|
||||
"com.example.JavaSubClassOfKotlinSuperClass",
|
||||
"com.example.KotlinSubClassOfChangedKotlinSuperClass",
|
||||
"com.example.JavaSubClassOfChangedKotlinSuperClass",
|
||||
"com.example.ChangedJavaSuperClass",
|
||||
"com.example.KotlinSubClassOfJavaSuperClass",
|
||||
"com.example.JavaSubClassOfJavaSuperClass"
|
||||
"com.example.KotlinSubClassOfChangedJavaSuperClass",
|
||||
"com.example.JavaSubClassOfChangedJavaSuperClass"
|
||||
)
|
||||
).assertEquals(changes)
|
||||
}
|
||||
}
|
||||
|
||||
private fun snapshotClasspath(classpathSourceDir: File, tmpDir: TemporaryFolder, protoBased: Boolean = true): ClasspathSnapshot {
|
||||
private fun computeClasspathChanges(classpathSourceDir: File, tmpDir: TemporaryFolder, protoBased: Boolean = false): Changes {
|
||||
val currentSnapshot = snapshotClasspath(File(classpathSourceDir, "current-classpath"), tmpDir, protoBased)
|
||||
val previousSnapshot = snapshotClasspath(File(classpathSourceDir, "previous-classpath"), tmpDir, protoBased)
|
||||
return computeClasspathChanges(currentSnapshot, previousSnapshot)
|
||||
}
|
||||
|
||||
private fun snapshotClasspath(classpathSourceDir: File, tmpDir: TemporaryFolder, protoBased: Boolean): 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).map { it.withHash }
|
||||
val classSnapshots = classFiles.snapshot(protoBased).map { it.withHash }
|
||||
ClasspathEntrySnapshot(
|
||||
classSnapshots = relativePaths.zip(classSnapshots).toMap(LinkedHashMap())
|
||||
)
|
||||
@@ -306,16 +340,8 @@ private fun computeClasspathChanges(
|
||||
previousClasspathSnapshot: ClasspathSnapshot
|
||||
): Changes {
|
||||
return ClasspathChangesComputer.computeChangedAndImpactedSet(
|
||||
currentClasspathSnapshot.getNonDuplicateClassSnapshots(),
|
||||
previousClasspathSnapshot.getNonDuplicateClassSnapshots(),
|
||||
DoNothingBuildMetricsReporter
|
||||
).normalize()
|
||||
}
|
||||
|
||||
private fun computeClassChanges(currentClassSnapshot: ClassSnapshot, previousClassSnapshot: ClassSnapshot): Changes {
|
||||
return ClasspathChangesComputer.computeClassChanges(
|
||||
listOf(currentClassSnapshot),
|
||||
listOf(previousClassSnapshot),
|
||||
currentClasspathSnapshot.removeDuplicateAndInaccessibleClasses(),
|
||||
previousClasspathSnapshot.removeDuplicateAndInaccessibleClasses(),
|
||||
DoNothingBuildMetricsReporter
|
||||
).normalize()
|
||||
}
|
||||
|
||||
+23
-17
@@ -5,19 +5,26 @@
|
||||
|
||||
package org.jetbrains.kotlin.incremental.classpathDiff
|
||||
|
||||
import org.jetbrains.kotlin.incremental.classpathDiff.ClasspathSnapshotTestCommon.Util.readBytes
|
||||
import org.jetbrains.kotlin.incremental.classpathDiff.ClasspathSnapshotTestCommon.SourceFile.JavaSourceFile
|
||||
import org.jetbrains.kotlin.incremental.classpathDiff.ClasspathSnapshotTestCommon.SourceFile.KotlinSourceFile
|
||||
import org.jetbrains.kotlin.incremental.storage.fromByteArray
|
||||
import org.jetbrains.kotlin.incremental.storage.toByteArray
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Test
|
||||
import java.io.File
|
||||
|
||||
abstract class ClasspathSnapshotSerializerTest : ClasspathSnapshotTestCommon() {
|
||||
|
||||
protected abstract val testSourceFile: ChangeableTestSourceFile
|
||||
companion object {
|
||||
val testDataDir =
|
||||
File("compiler/incremental-compilation-impl/testData/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathSnapshotterTest")
|
||||
}
|
||||
|
||||
protected abstract val sourceFile: TestSourceFile
|
||||
|
||||
@Test
|
||||
open fun `test ClassSnapshotDataSerializer`() {
|
||||
val originalSnapshot = testSourceFile.compileAndSnapshot()
|
||||
open fun `test ClassSnapshotExternalizer`() {
|
||||
val originalSnapshot = sourceFile.compileAndSnapshot()
|
||||
val serializedSnapshot = ClassSnapshotExternalizer.toByteArray(originalSnapshot)
|
||||
val deserializedSnapshot = ClassSnapshotExternalizer.fromByteArray(serializedSnapshot)
|
||||
|
||||
@@ -26,21 +33,20 @@ abstract class ClasspathSnapshotSerializerTest : ClasspathSnapshotTestCommon() {
|
||||
}
|
||||
|
||||
class KotlinClassesClasspathSnapshotSerializerTest : ClasspathSnapshotSerializerTest() {
|
||||
override val testSourceFile = SimpleKotlinClass(tmpDir)
|
||||
|
||||
override val sourceFile = TestSourceFile(
|
||||
KotlinSourceFile(
|
||||
baseDir = File(testDataDir, "src/kotlin"), relativePath = "com/example/SimpleClass.kt",
|
||||
preCompiledClassFile = ClassFile(File(testDataDir, "classes/kotlin"), "com/example/SimpleClass.class")
|
||||
), tmpDir
|
||||
)
|
||||
}
|
||||
|
||||
class JavaClassesClasspathSnapshotSerializerTest : ClasspathSnapshotSerializerTest() {
|
||||
|
||||
override val testSourceFile = SimpleJavaClass(tmpDir)
|
||||
|
||||
@Test
|
||||
override fun `test ClassSnapshotDataSerializer`() {
|
||||
val originalSnapshot = testSourceFile.compile().let {
|
||||
ClassSnapshotter.snapshot(listOf(ClassFileWithContents(it, it.readBytes())), includeDebugInfoInSnapshot = false)
|
||||
}.single()
|
||||
val serializedSnapshot = ClassSnapshotExternalizer.toByteArray(originalSnapshot)
|
||||
val deserializedSnapshot = ClassSnapshotExternalizer.fromByteArray(serializedSnapshot)
|
||||
|
||||
assertEquals(originalSnapshot.toGson(), deserializedSnapshot.toGson())
|
||||
}
|
||||
override val sourceFile = TestSourceFile(
|
||||
JavaSourceFile(
|
||||
baseDir = File(testDataDir, "src/java"), relativePath = "com/example/SimpleClass.java",
|
||||
), tmpDir
|
||||
)
|
||||
}
|
||||
|
||||
+27
-109
@@ -6,12 +6,10 @@
|
||||
package org.jetbrains.kotlin.incremental.classpathDiff
|
||||
|
||||
import com.google.gson.GsonBuilder
|
||||
import org.jetbrains.kotlin.incremental.classpathDiff.ClasspathSnapshotTestCommon.SourceFile.JavaSourceFile
|
||||
import org.jetbrains.kotlin.incremental.classpathDiff.ClasspathSnapshotTestCommon.ClassFileUtil.asFile
|
||||
import org.jetbrains.kotlin.incremental.classpathDiff.ClasspathSnapshotTestCommon.ClassFileUtil.snapshot
|
||||
import org.jetbrains.kotlin.incremental.classpathDiff.ClasspathSnapshotTestCommon.CompileUtil.compile
|
||||
import org.jetbrains.kotlin.incremental.classpathDiff.ClasspathSnapshotTestCommon.SourceFile.KotlinSourceFile
|
||||
import org.jetbrains.kotlin.incremental.classpathDiff.ClasspathSnapshotTestCommon.Util.compile
|
||||
import org.jetbrains.kotlin.incremental.classpathDiff.ClasspathSnapshotTestCommon.Util.compileAll
|
||||
import org.jetbrains.kotlin.incremental.classpathDiff.ClasspathSnapshotTestCommon.Util.snapshot
|
||||
import org.jetbrains.kotlin.incremental.classpathDiff.ClasspathSnapshotTestCommon.Util.snapshotAll
|
||||
import org.jetbrains.kotlin.test.KotlinTestUtils
|
||||
import org.junit.Rule
|
||||
import org.junit.rules.TemporaryFolder
|
||||
@@ -19,11 +17,6 @@ import java.io.File
|
||||
|
||||
abstract class ClasspathSnapshotTestCommon {
|
||||
|
||||
companion object {
|
||||
val testDataDir =
|
||||
File("compiler/incremental-compilation-impl/testData/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathSnapshotTestCommon")
|
||||
}
|
||||
|
||||
@get:Rule
|
||||
val tmpDir = TemporaryFolder()
|
||||
|
||||
@@ -53,44 +46,31 @@ abstract class ClasspathSnapshotTestCommon {
|
||||
/** Same as [SourceFile] but with a [TemporaryFolder] to store the results of operations on the [SourceFile]. */
|
||||
open class TestSourceFile(val sourceFile: SourceFile, private val tmpDir: TemporaryFolder) {
|
||||
|
||||
fun replace(oldValue: String, newValue: String, preCompiledKotlinClassFile: ClassFile? = null): TestSourceFile {
|
||||
val fileContents = sourceFile.asFile().readText()
|
||||
check(fileContents.contains(oldValue)) { "String '$oldValue' not found in file '${sourceFile.asFile().path}'" }
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
/**
|
||||
* Compiles this source file and returns a single generated .class file, or fails if zero or more than one .class file was
|
||||
* generated.
|
||||
*
|
||||
* Alternatively, the caller can call [compileAll] to get all generated .class files.
|
||||
*/
|
||||
fun compile(): ClassFile = compileAll().single()
|
||||
fun asFile() = sourceFile.asFile()
|
||||
|
||||
/** Compiles this source file and returns all generated .class files. */
|
||||
@Suppress("MemberVisibilityCanBePrivate")
|
||||
fun compileAll(): List<ClassFile> = sourceFile.compile(tmpDir)
|
||||
fun compile(): 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
|
||||
* file was generated.
|
||||
*
|
||||
* Alternatively, the caller can call [compileAndSnapshotAll] to get the snapshots of all generated .class files.
|
||||
*/
|
||||
fun compileAndSnapshot() = compile().snapshot()
|
||||
fun compileSingle(): ClassFile = compile().single()
|
||||
|
||||
/** Compiles this source file and returns the snapshots of all generated .class files. */
|
||||
fun compileAndSnapshotAll(): List<ClassSnapshot> = compileAll().snapshotAll()
|
||||
fun compileAndSnapshot() = compileSingle().snapshot()
|
||||
}
|
||||
|
||||
object Util {
|
||||
object CompileUtil {
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
/** 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> {
|
||||
@@ -140,19 +120,6 @@ abstract class ClasspathSnapshotTestCommon {
|
||||
}
|
||||
}
|
||||
|
||||
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()
|
||||
@@ -187,70 +154,21 @@ abstract class ClasspathSnapshotTestCommon {
|
||||
.map { ClassFile(classesDir, it.toRelativeString(classesDir)) }
|
||||
.sortedBy { it.unixStyleRelativePath.substringBefore(".class") }
|
||||
}
|
||||
}
|
||||
|
||||
object ClassFileUtil {
|
||||
|
||||
// `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 ClassFile.snapshot(protoBased: Boolean? = null): ClassSnapshot = listOf(this).snapshot(protoBased).single()
|
||||
|
||||
fun List<ClassFile>.snapshotAll(protoBased: Boolean? = null): List<ClassSnapshot> {
|
||||
fun List<ClassFile>.snapshot(protoBased: Boolean? = null): List<ClassSnapshot> {
|
||||
val classFilesWithContents = this.map { ClassFileWithContents(it, it.readBytes()) }
|
||||
return ClassSnapshotter.snapshot(classFilesWithContents, protoBased, includeDebugInfoInSnapshot = true)
|
||||
return ClassSnapshotter.snapshot(classFilesWithContents, protoBased)
|
||||
}
|
||||
}
|
||||
|
||||
abstract class ChangeableTestSourceFile(sourceFile: SourceFile, tmpDir: TemporaryFolder) : TestSourceFile(sourceFile, tmpDir) {
|
||||
|
||||
abstract fun changePublicMethodSignature(): TestSourceFile
|
||||
|
||||
abstract fun changeMethodImplementation(): TestSourceFile
|
||||
}
|
||||
|
||||
class SimpleKotlinClass(tmpDir: TemporaryFolder) : ChangeableTestSourceFile(
|
||||
KotlinSourceFile(
|
||||
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(
|
||||
"publicFunction()", "publicFunction(newParam: Int)",
|
||||
preCompiledKotlinClassFile = ClassFile(
|
||||
File(testDataDir, "classes/kotlin/changedPublicMethodSignature"), "com/example/SimpleKotlinClass.class"
|
||||
)
|
||||
)
|
||||
|
||||
override fun changeMethodImplementation() = replace(
|
||||
"I'm in a public function", "This function's implementation has changed!",
|
||||
preCompiledKotlinClassFile = ClassFile(
|
||||
File(testDataDir, "classes/kotlin/changedMethodImplementation"), "com/example/SimpleKotlinClass.class"
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
class SimpleJavaClass(tmpDir: TemporaryFolder) : ChangeableTestSourceFile(
|
||||
JavaSourceFile(File(testDataDir, "src/java"), "com/example/SimpleJavaClass.java"), tmpDir
|
||||
) {
|
||||
|
||||
override fun changePublicMethodSignature() = replace("publicMethod()", "publicMethod(int newParam)")
|
||||
|
||||
override fun changeMethodImplementation() = replace("I'm in a public method", "This method's implementation has changed!")
|
||||
}
|
||||
|
||||
class JavaClassWithNestedClasses(tmpDir: TemporaryFolder) : ChangeableTestSourceFile(
|
||||
JavaSourceFile(File(testDataDir, "src/java"), "com/example/JavaClassWithNestedClasses.java"), tmpDir
|
||||
) {
|
||||
|
||||
/** 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()", "publicMethod(int newParam)")
|
||||
|
||||
override fun changeMethodImplementation() = replace("I'm in a public method", "This method's implementation has changed!")
|
||||
}
|
||||
}
|
||||
|
||||
+65
-113
@@ -5,141 +5,93 @@
|
||||
|
||||
package org.jetbrains.kotlin.incremental.classpathDiff
|
||||
|
||||
import org.jetbrains.kotlin.incremental.classpathDiff.ClasspathSnapshotTestCommon.Util.snapshot
|
||||
import org.jetbrains.kotlin.incremental.classpathDiff.ClasspathSnapshotTestCommon.ClassFileUtil.readBytes
|
||||
import org.jetbrains.kotlin.incremental.classpathDiff.ClasspathSnapshotTestCommon.SourceFile.JavaSourceFile
|
||||
import org.jetbrains.kotlin.incremental.classpathDiff.ClasspathSnapshotTestCommon.SourceFile.KotlinSourceFile
|
||||
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() {
|
||||
|
||||
protected abstract val testSourceFile: ChangeableTestSourceFile
|
||||
|
||||
private lateinit var testClassSnapshot: ClassSnapshot
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
testClassSnapshot = testSourceFile.compileAndSnapshot()
|
||||
}
|
||||
|
||||
@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('.') + ".json")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test ClassSnapshotter extracts ABI info from a class`() {
|
||||
// Change public method signature
|
||||
val updatedSnapshot = testSourceFile.changePublicMethodSignature().compileAndSnapshot()
|
||||
|
||||
// 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().compileAndSnapshot()
|
||||
|
||||
// The snapshot must not change
|
||||
assertEquals(testClassSnapshot.toGson(), updatedSnapshot.toGson())
|
||||
}
|
||||
}
|
||||
|
||||
class KotlinClassesClasspathSnapshotterTest : ClasspathSnapshotterTest() {
|
||||
override val testSourceFile = SimpleKotlinClass(tmpDir)
|
||||
}
|
||||
|
||||
@RunWith(Parameterized::class)
|
||||
class JavaClassesClasspathSnapshotterTest(private val protoBased: Boolean) : ClasspathSnapshotTestCommon() {
|
||||
|
||||
companion object {
|
||||
@Parameterized.Parameters(name = "protoBased={0}")
|
||||
@JvmStatic
|
||||
fun parameters() = listOf(true, false)
|
||||
val testDataDir =
|
||||
File("compiler/incremental-compilation-impl/testData/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathSnapshotterTest")
|
||||
}
|
||||
|
||||
private val testSourceFile = SimpleJavaClass(tmpDir)
|
||||
protected abstract val sourceFile: TestSourceFile
|
||||
protected abstract val sourceFileWithAbiChange: TestSourceFile
|
||||
protected abstract val sourceFileWithNonAbiChange: TestSourceFile
|
||||
|
||||
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() {
|
||||
|
||||
private val testSourceFile = JavaClassWithNestedClasses(tmpDir)
|
||||
|
||||
private lateinit var testClassSnapshot: ClassSnapshot
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
testClassSnapshot = testSourceFile.compileAndSnapshotNestedClass()
|
||||
}
|
||||
|
||||
private fun TestSourceFile.compileAndSnapshotNestedClass(): ClassSnapshot {
|
||||
return compileAndSnapshotAll().single {
|
||||
if (it is RegularJavaClassSnapshot) {
|
||||
it.classAbiExcludingMembers.name == testSourceFile.nestedClassToTest
|
||||
} else false
|
||||
private val expectedSnapshotFile: File
|
||||
get() = sourceFile.asFile().path.let {
|
||||
File(it.substringBeforeLast("src") + "expected-snapshot" + it.substringAfterLast("src").substringBeforeLast('.') + ".json")
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test ClassSnapshotter's result against expected snapshot`() {
|
||||
val expectedSnapshotFile = File("${testDataDir.path}/expected-snapshot/java/${testSourceFile.nestedClassToTest}.json")
|
||||
assertEquals(expectedSnapshotFile.readText(), testClassSnapshot.toGson())
|
||||
val classSnapshot = sourceFile.compileSingle().let {
|
||||
ClassSnapshotter.snapshot(listOf(ClassFileWithContents(it, it.readBytes())), includeDebugInfoInSnapshot = true)
|
||||
}.single()
|
||||
|
||||
assertEquals(expectedSnapshotFile.readText(), classSnapshot.toGson())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test ClassSnapshotter extracts ABI info from a class`() {
|
||||
val updatedSnapshot = testSourceFile.changePublicMethodSignature().compileAndSnapshotNestedClass()
|
||||
assertNotEquals(testClassSnapshot.toGson(), updatedSnapshot.toGson())
|
||||
// After an ABI change, the snapshot must change
|
||||
assertNotEquals(sourceFile.compileAndSnapshot().toGson(), sourceFileWithAbiChange.compileAndSnapshot().toGson())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test ClassSnapshotter does not extract non-ABI info from a class`() {
|
||||
val updatedSnapshot = testSourceFile.changeMethodImplementation().compileAndSnapshotNestedClass()
|
||||
assertEquals(testClassSnapshot.toGson(), updatedSnapshot.toGson())
|
||||
// After a non-ABI change, the snapshot must not change
|
||||
assertEquals(sourceFile.compileAndSnapshot().toGson(), sourceFileWithNonAbiChange.compileAndSnapshot().toGson())
|
||||
}
|
||||
}
|
||||
|
||||
class KotlinOnlyClasspathSnapshotterTest : ClasspathSnapshotterTest() {
|
||||
|
||||
override val sourceFile = TestSourceFile(
|
||||
KotlinSourceFile(
|
||||
baseDir = File(testDataDir, "src/kotlin"), relativePath = "com/example/SimpleClass.kt",
|
||||
preCompiledClassFile = ClassFile(File(testDataDir, "classes/kotlin"), "com/example/SimpleClass.class")
|
||||
), tmpDir
|
||||
)
|
||||
|
||||
override val sourceFileWithAbiChange = TestSourceFile(
|
||||
KotlinSourceFile(
|
||||
baseDir = File(testDataDir, "src-changed/kotlin/abi-change"), relativePath = "com/example/SimpleClass.kt",
|
||||
preCompiledClassFile = ClassFile(File(testDataDir, "classes-changed/kotllin/abi-change"), "com/example/SimpleClass.class")
|
||||
), tmpDir
|
||||
)
|
||||
|
||||
override val sourceFileWithNonAbiChange = TestSourceFile(
|
||||
KotlinSourceFile(
|
||||
baseDir = File(testDataDir, "src-changed/kotlin/non-abi-change"), relativePath = "com/example/SimpleClass.kt",
|
||||
preCompiledClassFile = ClassFile(File(testDataDir, "classes-changed/kotlin/non-abi-change"), "com/example/SimpleClass.class")
|
||||
), tmpDir
|
||||
)
|
||||
}
|
||||
|
||||
class JavaOnlyClasspathSnapshotterTest : ClasspathSnapshotterTest() {
|
||||
|
||||
override val sourceFile = TestSourceFile(
|
||||
JavaSourceFile(
|
||||
baseDir = File(testDataDir, "src/java"), relativePath = "com/example/SimpleClass.java",
|
||||
), tmpDir
|
||||
)
|
||||
|
||||
override val sourceFileWithAbiChange = TestSourceFile(
|
||||
JavaSourceFile(
|
||||
baseDir = File(testDataDir, "src-changed/java/abi-change"), relativePath = "com/example/SimpleClass.java",
|
||||
), tmpDir
|
||||
)
|
||||
|
||||
override val sourceFileWithNonAbiChange = TestSourceFile(
|
||||
JavaSourceFile(
|
||||
baseDir = File(testDataDir, "src-changed/java/non-abi-change"), relativePath = "com/example/SimpleClass.java",
|
||||
), tmpDir
|
||||
)
|
||||
}
|
||||
|
||||
BIN
Binary file not shown.
BIN
Binary file not shown.
+22
@@ -0,0 +1,22 @@
|
||||
package com.example;
|
||||
|
||||
public class SomeClass {
|
||||
|
||||
public long publicFieldChangedType = 0;
|
||||
|
||||
public int publicFieldChangedValue = 1000;
|
||||
|
||||
private long privateFieldChangedType = 0;
|
||||
|
||||
public long publicMethodChangedSignature() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
public int publicMethodChangedImplementation() {
|
||||
return 1000;
|
||||
}
|
||||
|
||||
private long privateMethodChangedSignature() {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
package com.example;
|
||||
|
||||
public class SomeClass {
|
||||
|
||||
public int publicFieldChangedType = 0;
|
||||
|
||||
public int publicFieldChangedValue = 0;
|
||||
|
||||
private int privateFieldChangedType = 0;
|
||||
|
||||
public int publicMethodChangedSignature() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
public int publicMethodChangedImplementation() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
private int privateMethodChangedSignature() {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
package com.example
|
||||
|
||||
class SomeClass {
|
||||
|
||||
val publicPropertyChangedType: Long = 0
|
||||
|
||||
val publicPropertyChangedValue: Int = 1000
|
||||
|
||||
private val privatePropertyChangedType: Long = 0
|
||||
|
||||
fun publicFunctionChangedSignature(): Long = 0
|
||||
|
||||
fun publicFunctionChangedImplementation(): Int = 1000
|
||||
|
||||
private fun privateFunctionChangedSignature(): Long = 0
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
package com.example
|
||||
|
||||
class SomeClass {
|
||||
|
||||
val publicPropertyChangedType: Int = 0
|
||||
|
||||
val publicPropertyChangedValue: Int = 0
|
||||
|
||||
private val privatePropertyChangedType: Int = 0
|
||||
|
||||
fun publicFunctionChangedSignature(): Int = 0
|
||||
|
||||
fun publicFunctionChangedImplementation(): Int = 0
|
||||
|
||||
private fun privateFunctionChangedSignature(): Int = 0
|
||||
}
|
||||
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
+18
@@ -0,0 +1,18 @@
|
||||
package com.example
|
||||
|
||||
class SomeClass {
|
||||
|
||||
// Constants are not allowed in a class, only allowed at the top level or in an object.
|
||||
|
||||
companion object CompanionObject {
|
||||
const val constantChangedType: Long = 0
|
||||
const val constantChangedValue: Int = 1000
|
||||
const val constantUnchanged: Int = 0
|
||||
private const val privateConstantChangedType: Long = 0
|
||||
}
|
||||
|
||||
inline fun inlineFunctionChangedSignature(): Long = 0
|
||||
inline fun inlineFunctionChangedImplementation(): Int = 1000
|
||||
inline fun inlineFunctionChangedUnchanged(): Int = 0
|
||||
private inline fun privateInlineFunctionChangedSignature(): Long = 0
|
||||
}
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
package com.example
|
||||
|
||||
class SomeClass {
|
||||
|
||||
// Constants are not allowed in a class, only allowed at the top level or in an object.
|
||||
|
||||
companion object CompanionObject {
|
||||
const val constantChangedType: Int = 0
|
||||
const val constantChangedValue: Int = 0
|
||||
const val constantUnchanged: Int = 0
|
||||
private const val privateConstantChangedType: Int = 0
|
||||
}
|
||||
|
||||
inline fun inlineFunctionChangedSignature(): Int = 0
|
||||
inline fun inlineFunctionChangedImplementation(): Int = 0
|
||||
inline fun inlineFunctionChangedUnchanged(): Int = 0
|
||||
private inline fun privateInlineFunctionChangedSignature(): Int = 0
|
||||
}
|
||||
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.
+4
@@ -0,0 +1,4 @@
|
||||
package com.example
|
||||
|
||||
val propertyInFileFacade = 0
|
||||
fun functionInFileFacade() = 0
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
@file:JvmName("MultifileClass")
|
||||
@file:JvmMultifileClass
|
||||
|
||||
package com.example
|
||||
|
||||
val propertyInMultifileClass1 = 0
|
||||
fun functionInMultifileClass1() = 0
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
@file:JvmName("MultifileClass")
|
||||
@file:JvmMultifileClass
|
||||
|
||||
package com.example
|
||||
|
||||
val propertyInMultifileClass2 = 0
|
||||
fun functionInMultifileClass2() = 0
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
package com.example
|
||||
|
||||
class NormalClass {
|
||||
val propertyInNormalClass = 0
|
||||
fun functionInNormalClass() = 0
|
||||
|
||||
companion object CompanionObject {
|
||||
val propertyInCompanionObject = 0
|
||||
fun functionInCompanionObject() = 0
|
||||
}
|
||||
|
||||
class NestedClass {
|
||||
val propertyInNestedClass = 0
|
||||
fun functionInNestedClass() = 0
|
||||
}
|
||||
}
|
||||
+1
@@ -0,0 +1 @@
|
||||
package com.example
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
@file:JvmName("MultifileClass")
|
||||
@file:JvmMultifileClass
|
||||
|
||||
package com.example
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
@file:JvmName("MultifileClass")
|
||||
@file:JvmMultifileClass
|
||||
|
||||
package com.example
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
package com.example
|
||||
|
||||
class NormalClass {
|
||||
|
||||
companion object CompanionObject {
|
||||
}
|
||||
|
||||
class NestedClass {
|
||||
}
|
||||
}
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
package com.example;
|
||||
|
||||
public class SubClassOfChangedSuperClass extends ChangedSuperClass {
|
||||
}
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
package com.example;
|
||||
|
||||
public class SubSubClassOfChangedSuperClass extends SubClassOfChangedSuperClass {
|
||||
}
|
||||
+2
-1
@@ -4,6 +4,7 @@ public class ChangedSuperClass {
|
||||
|
||||
public int changedField = 0;
|
||||
|
||||
public void changedMethod() {
|
||||
public int changedMethod() {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
package com.example;
|
||||
|
||||
public class SubClassOfChangedSuperClass extends ChangedSuperClass {
|
||||
}
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
package com.example;
|
||||
|
||||
public class SubSubClassOfChangedSuperClass extends SubClassOfChangedSuperClass {
|
||||
}
|
||||
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.
+4
@@ -0,0 +1,4 @@
|
||||
package com.example;
|
||||
|
||||
public class JavaSubClassOfChangedJavaSuperClass extends ChangedJavaSuperClass {
|
||||
}
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
package com.example;
|
||||
|
||||
public class JavaSubClassOfChangedKotlinSuperClass extends ChangedKotlinSuperClass {
|
||||
}
|
||||
-4
@@ -1,4 +0,0 @@
|
||||
package com.example;
|
||||
|
||||
public class JavaSubClassOfJavaSuperClass extends ChangedJavaSuperClass {
|
||||
}
|
||||
-4
@@ -1,4 +0,0 @@
|
||||
package com.example;
|
||||
|
||||
public class JavaSubClassOfKotlinSuperClass extends ChangedKotlinSuperClass {
|
||||
}
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
package com.example
|
||||
|
||||
class KotlinSubClassOfChangedJavaSuperClass : ChangedJavaSuperClass()
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
package com.example
|
||||
|
||||
class KotlinSubClassOfChangedKotlinSuperClass : ChangedKotlinSuperClass()
|
||||
-3
@@ -1,3 +0,0 @@
|
||||
package com.example
|
||||
|
||||
class KotlinSubClassOfJavaSuperClass : ChangedJavaSuperClass()
|
||||
-3
@@ -1,3 +0,0 @@
|
||||
package com.example
|
||||
|
||||
class KotlinSubClassOfKotlinSuperClass : ChangedKotlinSuperClass()
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
package com.example;
|
||||
|
||||
public class JavaSubClassOfChangedJavaSuperClass extends ChangedJavaSuperClass {
|
||||
}
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
package com.example;
|
||||
|
||||
public class JavaSubClassOfChangedKotlinSuperClass extends ChangedKotlinSuperClass {
|
||||
}
|
||||
-4
@@ -1,4 +0,0 @@
|
||||
package com.example;
|
||||
|
||||
public class JavaSubClassOfJavaSuperClass extends ChangedJavaSuperClass {
|
||||
}
|
||||
-4
@@ -1,4 +0,0 @@
|
||||
package com.example;
|
||||
|
||||
public class JavaSubClassOfKotlinSuperClass extends ChangedKotlinSuperClass {
|
||||
}
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
package com.example
|
||||
|
||||
class KotlinSubClassOfChangedJavaSuperClass : ChangedJavaSuperClass()
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
package com.example
|
||||
|
||||
class KotlinSubClassOfChangedKotlinSuperClass : ChangedKotlinSuperClass()
|
||||
-3
@@ -1,3 +0,0 @@
|
||||
package com.example
|
||||
|
||||
class KotlinSubClassOfJavaSuperClass : ChangedJavaSuperClass()
|
||||
-3
@@ -1,3 +0,0 @@
|
||||
package com.example
|
||||
|
||||
class KotlinSubClassOfKotlinSuperClass : ChangedKotlinSuperClass()
|
||||
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.
+3
@@ -0,0 +1,3 @@
|
||||
package com.example
|
||||
|
||||
open class SubClassOfChangedSuperClass : ChangedSuperClass()
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
package com.example
|
||||
|
||||
class SubSubClassOfChangedSuperClass : SubClassOfChangedSuperClass()
|
||||
+1
-1
@@ -2,5 +2,5 @@ package com.example;
|
||||
|
||||
open class ChangedSuperClass {
|
||||
val changedProperty = 0
|
||||
fun changedFunction() {}
|
||||
fun changedFunction() = 0
|
||||
}
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
package com.example
|
||||
|
||||
open class SubClassOfChangedSuperClass : ChangedSuperClass()
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
package com.example
|
||||
|
||||
class SubSubClassOfChangedSuperClass : SubClassOfChangedSuperClass()
|
||||
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
-4
@@ -1,4 +0,0 @@
|
||||
package com.example;
|
||||
|
||||
public class SubClass extends ChangedSuperClass {
|
||||
}
|
||||
-4
@@ -1,4 +0,0 @@
|
||||
package com.example;
|
||||
|
||||
public class SubSubClass extends SubClass {
|
||||
}
|
||||
-4
@@ -1,4 +0,0 @@
|
||||
package com.example;
|
||||
|
||||
public class SubClass extends ChangedSuperClass {
|
||||
}
|
||||
-4
@@ -1,4 +0,0 @@
|
||||
package com.example;
|
||||
|
||||
public class SubSubClass extends SubClass {
|
||||
}
|
||||
-3
@@ -1,3 +0,0 @@
|
||||
package com.example
|
||||
|
||||
open class SubClass : ChangedSuperClass()
|
||||
-3
@@ -1,3 +0,0 @@
|
||||
package com.example
|
||||
|
||||
class SubSubClass : SubClass()
|
||||
-3
@@ -1,3 +0,0 @@
|
||||
package com.example
|
||||
|
||||
open class SubClass : ChangedSuperClass()
|
||||
-3
@@ -1,3 +0,0 @@
|
||||
package com.example
|
||||
|
||||
class SubSubClass : SubClass()
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user