Compare module metadata in JVM ABI consistency tests
This commit is contained in:
committed by
Space Team
parent
ba217ad688
commit
b74501ee93
@@ -25,6 +25,14 @@ fun ClassFileFactory.getClassFiles(): Iterable<OutputFile> {
|
||||
return asList().filterClassFiles()
|
||||
}
|
||||
|
||||
fun ClassFileFactory.getKotlinModuleFile(): OutputFile? = asList().filter { it.relativePath.endsWith(".kotlin_module") }.run {
|
||||
when (size) {
|
||||
0 -> null
|
||||
1 -> single()
|
||||
else -> error("Module has non-unique .kotlin_metadata file")
|
||||
}
|
||||
}
|
||||
|
||||
fun List<OutputFile>.filterClassFiles(): List<OutputFile> {
|
||||
return filter { it.relativePath.endsWith(".class") }
|
||||
}
|
||||
|
||||
+6
@@ -9,3 +9,9 @@ MODULE main
|
||||
<set-?>: kotlin/Int
|
||||
K2
|
||||
value: kotlin/Int
|
||||
MODULE METADATA
|
||||
Property: module.metadata.optionalAnnotations
|
||||
K1
|
||||
[Anno]
|
||||
K2
|
||||
[]
|
||||
|
||||
+35
-9
@@ -10,8 +10,10 @@ import org.jetbrains.kotlin.abicmp.defects.Location
|
||||
import org.jetbrains.kotlin.abicmp.reports.*
|
||||
import org.jetbrains.kotlin.abicmp.tag
|
||||
import org.jetbrains.kotlin.abicmp.tasks.ClassTask
|
||||
import org.jetbrains.kotlin.abicmp.tasks.ModuleMetadataTask
|
||||
import org.jetbrains.kotlin.abicmp.tasks.checkerConfiguration
|
||||
import org.jetbrains.kotlin.codegen.getClassFiles
|
||||
import org.jetbrains.kotlin.codegen.getKotlinModuleFile
|
||||
import org.jetbrains.kotlin.test.backend.ir.AbiCheckerSuppressor
|
||||
import org.jetbrains.kotlin.test.directives.CodegenTestDirectives
|
||||
import org.jetbrains.kotlin.test.model.*
|
||||
@@ -35,6 +37,7 @@ class JvmAbiConsistencyHandler(testServices: TestServices) : AnalysisHandler<Bin
|
||||
val missingInK1: Set<String>,
|
||||
val missingInK2: Set<String>,
|
||||
val nonEmptyClassReports: MutableList<ClassReport>,
|
||||
val moduleMetadataReport: ModuleMetadataReport,
|
||||
)
|
||||
|
||||
private class TestReport {
|
||||
@@ -63,9 +66,14 @@ class JvmAbiConsistencyHandler(testServices: TestServices) : AnalysisHandler<Bin
|
||||
node("MODULE $module") {
|
||||
reportMissing("K1", report.missingInK1)
|
||||
reportMissing("K2", report.missingInK2)
|
||||
|
||||
report.nonEmptyClassReports.forEach { classReport ->
|
||||
with(classReport) { appendClassReport() }
|
||||
}
|
||||
|
||||
if (!report.moduleMetadataReport.isEmpty()) {
|
||||
with(report.moduleMetadataReport) { appendModuleMetadataReport() }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -164,27 +172,45 @@ class JvmAbiConsistencyHandler(testServices: TestServices) : AnalysisHandler<Bin
|
||||
if (AbiCheckerSuppressor.ignoredByBackendOrInliner(testServices)) return
|
||||
val classesFromK1 = info.fromK1.classFileFactory.getClassFiles().associate { it.relativePath to it.asByteArray() }
|
||||
val classesFromK2 = info.fromK2.classFileFactory.getClassFiles().associate { it.relativePath to it.asByteArray() }
|
||||
val missingInK2 = Sets.difference(classesFromK1.keys, classesFromK2.keys)
|
||||
val missingInK1 = Sets.difference(classesFromK2.keys, classesFromK1.keys)
|
||||
val missingInK2 = Sets.difference(classesFromK1.keys, classesFromK2.keys).toMutableSet()
|
||||
val missingInK1 = Sets.difference(classesFromK2.keys, classesFromK1.keys).toMutableSet()
|
||||
val commonClasses = Sets.intersection(classesFromK1.keys, classesFromK2.keys)
|
||||
|
||||
val configuration = checkerConfiguration {
|
||||
// Indication of constants is different in K2 as expected
|
||||
// We simply turn the checker off to avoid producing an enormous number of difference reports
|
||||
disable("class.metadata.property.hasConstant")
|
||||
}
|
||||
|
||||
val nonEmptyClassReports = mutableListOf<ClassReport>()
|
||||
commonClasses.forEach { classInternalName ->
|
||||
val k1ClassNode = parseClassNode(classesFromK1[classInternalName]!!)
|
||||
val k2ClassNode = parseClassNode(classesFromK2[classInternalName]!!)
|
||||
val classReport = ClassReport(Location.Class("", classInternalName), classInternalName, "K1", "K2", DefectReport())
|
||||
ClassTask(checkerConfiguration {
|
||||
// Indication of constants is different in K2 as expected
|
||||
// We simply turn the checker off to avoid producing an enormous number of difference reports
|
||||
disable("class.metadata.property.hasConstant")
|
||||
}, k1ClassNode, k2ClassNode, classReport).run()
|
||||
ClassTask(configuration, k1ClassNode, k2ClassNode, classReport).run()
|
||||
if (classReport.isNotEmpty()) {
|
||||
nonEmptyClassReports.add(classReport)
|
||||
}
|
||||
}
|
||||
|
||||
if (nonEmptyClassReports.isNotEmpty() || missingInK1.isNotEmpty() || missingInK2.isNotEmpty()) {
|
||||
testReport.addModuleReport(module.name, ModuleReport(missingInK1, missingInK2, nonEmptyClassReports))
|
||||
val k1ModuleFile = info.fromK1.classFileFactory.getKotlinModuleFile()
|
||||
val k2ModuleFile = info.fromK2.classFileFactory.getKotlinModuleFile()
|
||||
|
||||
if (k1ModuleFile == null && k2ModuleFile != null) {
|
||||
missingInK1.add(k2ModuleFile.relativePath)
|
||||
}
|
||||
|
||||
if (k1ModuleFile != null && k2ModuleFile == null) {
|
||||
missingInK2.add(k1ModuleFile.relativePath)
|
||||
}
|
||||
|
||||
val moduleMetadataReport = ModuleMetadataReport("K1", "K2")
|
||||
if (k1ModuleFile != null && k2ModuleFile != null) {
|
||||
ModuleMetadataTask(configuration, k1ModuleFile.asByteArray(), k2ModuleFile.asByteArray(), moduleMetadataReport).run()
|
||||
}
|
||||
|
||||
if (nonEmptyClassReports.isNotEmpty() || missingInK1.isNotEmpty() || missingInK2.isNotEmpty() || !moduleMetadataReport.isEmpty()) {
|
||||
testReport.addModuleReport(module.name, ModuleReport(missingInK1, missingInK2, nonEmptyClassReports, moduleMetadataReport))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+22
-7
@@ -5,19 +5,22 @@
|
||||
|
||||
package org.jetbrains.kotlin.abicmp.checkers
|
||||
|
||||
import org.jetbrains.kotlin.abicmp.reports.MetadataPropertyReport
|
||||
import org.jetbrains.kotlin.abicmp.reports.NamedDiffEntry
|
||||
import kotlin.metadata.KmConstructor
|
||||
import kotlin.metadata.KmFunction
|
||||
import kotlin.metadata.KmProperty
|
||||
import kotlin.metadata.KmTypeAlias
|
||||
import org.jetbrains.kotlin.abicmp.reports.MetadataPropertyReport
|
||||
import org.jetbrains.kotlin.abicmp.reports.NamedDiffEntry
|
||||
import kotlin.metadata.jvm.KmModule
|
||||
import kotlin.metadata.jvm.KmPackageParts
|
||||
import kotlin.metadata.jvm.UnstableMetadataApi
|
||||
|
||||
interface GenericMetadataChecker<T> : Checker {
|
||||
fun check(metadata1: T, metadata2: T, report: MetadataPropertyReport)
|
||||
}
|
||||
|
||||
abstract class GenericMetadataPropertyChecker<T>(name: String) :
|
||||
PropertyChecker<String, T>("class.metadata.$name"),
|
||||
PropertyChecker<String, T>(name),
|
||||
GenericMetadataChecker<T> {
|
||||
|
||||
override fun check(metadata1: T, metadata2: T, report: MetadataPropertyReport) {
|
||||
@@ -29,22 +32,34 @@ abstract class GenericMetadataPropertyChecker<T>(name: String) :
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(UnstableMetadataApi::class)
|
||||
fun moduleMetadataPropertyChecker(name: String, propertyGetter: (KmModule) -> String) =
|
||||
object : GenericMetadataPropertyChecker<KmModule>("module.metadata.$name") {
|
||||
override fun getProperty(node: KmModule) = propertyGetter(node)
|
||||
}
|
||||
|
||||
@OptIn(UnstableMetadataApi::class)
|
||||
fun packagePartsPropertyChecker(name: String, propertyGetter: (KmPackageParts) -> String) =
|
||||
object : GenericMetadataPropertyChecker<KmPackageParts>("module.metadata.$name") {
|
||||
override fun getProperty(node: KmPackageParts) = propertyGetter(node)
|
||||
}
|
||||
|
||||
fun constructorMetadataPropertyChecker(name: String, propertyGetter: (KmConstructor) -> String) =
|
||||
object : GenericMetadataPropertyChecker<KmConstructor>("constructor.$name") {
|
||||
object : GenericMetadataPropertyChecker<KmConstructor>("class.metadata.constructor.$name") {
|
||||
override fun getProperty(node: KmConstructor) = propertyGetter(node)
|
||||
}
|
||||
|
||||
fun functionMetadataPropertyChecker(name: String, propertyGetter: (KmFunction) -> String) =
|
||||
object : GenericMetadataPropertyChecker<KmFunction>("function.$name") {
|
||||
object : GenericMetadataPropertyChecker<KmFunction>("class.metadata.function.$name") {
|
||||
override fun getProperty(node: KmFunction) = propertyGetter(node)
|
||||
}
|
||||
|
||||
fun typeAliasMetadataPropertyChecker(name: String, propertyGetter: (KmTypeAlias) -> String) =
|
||||
object : GenericMetadataPropertyChecker<KmTypeAlias>("typeAlias.$name") {
|
||||
object : GenericMetadataPropertyChecker<KmTypeAlias>("class.metadata.typeAlias.$name") {
|
||||
override fun getProperty(node: KmTypeAlias) = propertyGetter(node)
|
||||
}
|
||||
|
||||
fun propertyMetadataPropertyChecker(name: String, propertyGetter: (KmProperty) -> String) =
|
||||
object : GenericMetadataPropertyChecker<KmProperty>("property.$name") {
|
||||
object : GenericMetadataPropertyChecker<KmProperty>("class.metadata.property.$name") {
|
||||
override fun getProperty(node: KmProperty) = propertyGetter(node)
|
||||
}
|
||||
|
||||
+2
-2
@@ -8,9 +8,9 @@ package org.jetbrains.kotlin.abicmp.reports
|
||||
import org.jetbrains.kotlin.abicmp.tag
|
||||
import java.io.PrintWriter
|
||||
|
||||
class MetadataPropertyReport(val id: String, val header1: String, val header2: String) : ComparisonReport {
|
||||
open class MetadataPropertyReport(val id: String, val header1: String, val header2: String) : ComparisonReport {
|
||||
|
||||
private val propertyDiffs = ArrayList<NamedDiffEntry>()
|
||||
protected val propertyDiffs = ArrayList<NamedDiffEntry>()
|
||||
|
||||
override fun isEmpty() = propertyDiffs.isEmpty()
|
||||
|
||||
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Copyright 2010-2024 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.abicmp.reports
|
||||
|
||||
import java.io.PrintWriter
|
||||
|
||||
class ModuleMetadataReport(header1: String, header2: String) : MetadataPropertyReport("MODULE METADATA", header1, header2) {
|
||||
|
||||
private val packagePartsReports = ArrayList<MetadataPropertyReport>()
|
||||
|
||||
override fun isEmpty() = propertyDiffs.isEmpty() && getFilteredPackagePartsReports().isEmpty()
|
||||
|
||||
override fun writeAsHtml(output: PrintWriter) {
|
||||
if (isEmpty()) return
|
||||
super.writeAsHtml(output)
|
||||
packagePartsReports.forEach { it.writeAsHtml(output) }
|
||||
}
|
||||
|
||||
fun TextTreeBuilderContext.appendModuleMetadataReport() {
|
||||
node("MODULE METADATA") {
|
||||
appendNamedDiffEntries(header1, header2, propertyDiffs, "Property")
|
||||
|
||||
for (report in getFilteredPackagePartsReports()) {
|
||||
with(report) { appendReport() }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun packagePartsReport(id: String) = MetadataPropertyReport(id, header1, header2).also { packagePartsReports.add(it) }
|
||||
|
||||
private fun getFilteredPackagePartsReports() = packagePartsReports.filter { !it.isEmpty() }.sortedBy { it.id }
|
||||
|
||||
}
|
||||
+20
@@ -153,6 +153,23 @@ private val allPackageMetadataCheckers = listOf(
|
||||
fileFacadeMetadataListChecker("localDelegatedProperties") { loadLocalDelegatedProperties(it).keys.toList() }
|
||||
)
|
||||
|
||||
@OptIn(UnstableMetadataApi::class)
|
||||
private val allPackagePartsMetadataCheckers = listOf(
|
||||
packagePartsPropertyChecker("multiFileParts") {
|
||||
it.multiFileClassParts.toList().map { filePart -> "(${filePart.first}, ${filePart.second})" }.toList().sorted()
|
||||
.joinToString(prefix = "[", postfix = "]")
|
||||
},
|
||||
packagePartsPropertyChecker("fileFacades") { it.fileFacades.toList().sorted().joinToString(prefix = "[", postfix = "]") }
|
||||
)
|
||||
|
||||
@OptIn(UnstableMetadataApi::class)
|
||||
private val allModuleMetadataCheckers = listOf(
|
||||
moduleMetadataPropertyChecker("optionalAnnotations") {
|
||||
it.optionalAnnotationClasses.map { clazz -> clazz.name }.sorted().joinToString(prefix = "[", postfix = "]")
|
||||
},
|
||||
moduleMetadataPropertyChecker("packageParts") { it.packageParts.keys.toList().sorted().joinToString(prefix = "[", postfix = "]") }
|
||||
)
|
||||
|
||||
private val allMultifileClassFacadeMetadataCheckers = listOf(
|
||||
multiFileClassFacadeMetadataListChecker("partClassNames") { it.partClassNames }
|
||||
)
|
||||
@@ -190,6 +207,7 @@ inline fun checkerConfiguration(b: CheckerConfigurationBuilder.() -> Unit): Chec
|
||||
return builder.build()
|
||||
}
|
||||
|
||||
@OptIn(UnstableMetadataApi::class)
|
||||
class CheckerConfiguration(private val enabledExclusively: Set<String>, private val disabled: Set<String>) {
|
||||
|
||||
private fun <T : Checker> List<T>.filterOutDisabled() = filter { it.isEnabled() }
|
||||
@@ -206,6 +224,8 @@ class CheckerConfiguration(private val enabledExclusively: Set<String>, private
|
||||
val enabledMultifileClassFacadeMetadataCheckers = allMultifileClassFacadeMetadataCheckers.filterOutDisabled()
|
||||
val enabledMultifileClassPartMetadataCheckers = allMultifileClassPartMetadataCheckers.filterOutDisabled()
|
||||
val enabledAllSyntheticClassMetadataCheckers = allSyntheticClassMetadataCheckers.filterOutDisabled()
|
||||
val enabledModuleMetadataCheckers = allModuleMetadataCheckers.filterOutDisabled()
|
||||
val enabledPackagePartsMetadataCheckers = allPackagePartsMetadataCheckers.filterOutDisabled()
|
||||
|
||||
private fun Checker.isEnabled(): Boolean {
|
||||
if (enabledExclusively.isNotEmpty() && name !in enabledExclusively) return false
|
||||
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* Copyright 2010-2024 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.abicmp.tasks
|
||||
|
||||
import org.jetbrains.kotlin.abicmp.reports.ModuleMetadataReport
|
||||
import kotlin.metadata.jvm.KotlinModuleMetadata
|
||||
import kotlin.metadata.jvm.UnstableMetadataApi
|
||||
|
||||
@OptIn(UnstableMetadataApi::class)
|
||||
class ModuleMetadataTask(
|
||||
private val configuration: CheckerConfiguration,
|
||||
metadata1Bytes: ByteArray,
|
||||
metadata2Bytes: ByteArray,
|
||||
private val report: ModuleMetadataReport,
|
||||
) : Runnable {
|
||||
|
||||
private val metadata1 = metadata1Bytes.toKmModule()
|
||||
private val metadata2 = metadata2Bytes.toKmModule()
|
||||
|
||||
|
||||
override fun run() {
|
||||
for (checker in configuration.enabledModuleMetadataCheckers) {
|
||||
checker.check(metadata1, metadata2, report)
|
||||
}
|
||||
|
||||
checkPackageParts()
|
||||
}
|
||||
|
||||
private fun checkPackageParts() {
|
||||
val packageParts1 = metadata1.packageParts
|
||||
val packageParts2 = metadata2.packageParts
|
||||
|
||||
val commonIds = packageParts1.keys.intersect(packageParts2.keys).sorted()
|
||||
for (id in commonIds) {
|
||||
val packagePart1 = packageParts1[id]!!
|
||||
val packagePart2 = packageParts2[id]!!
|
||||
val packagePartsReport = report.packagePartsReport(id)
|
||||
PackagePartsMetadataTask(configuration, packagePart1, packagePart2, packagePartsReport).run()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(UnstableMetadataApi::class)
|
||||
private fun ByteArray.toKmModule() = KotlinModuleMetadata.read(this).kmModule
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
* Copyright 2010-2024 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.abicmp.tasks
|
||||
|
||||
import org.jetbrains.kotlin.abicmp.reports.MetadataPropertyReport
|
||||
import kotlin.metadata.jvm.KmPackageParts
|
||||
import kotlin.metadata.jvm.UnstableMetadataApi
|
||||
|
||||
@OptIn(UnstableMetadataApi::class)
|
||||
class PackagePartsMetadataTask(
|
||||
private val configuration: CheckerConfiguration,
|
||||
private val packagePart1: KmPackageParts,
|
||||
private val packagePart2: KmPackageParts,
|
||||
private val packagePartsReport: MetadataPropertyReport
|
||||
): Runnable {
|
||||
override fun run() {
|
||||
for (checker in configuration.enabledPackagePartsMetadataCheckers) {
|
||||
checker.check(packagePart1, packagePart2, packagePartsReport)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user