[Interop][Tests] Add infrastructure to test klib metadata (#4134)

We need a structural testing tool for interop metadata itself because it is not always possible to detect metadata errors via usual compiler tests.
This commit is contained in:
Sergey Bogolepov
2020-05-18 15:03:31 +07:00
committed by GitHub
parent c485511f1b
commit 37bf7d7ef9
10 changed files with 631 additions and 0 deletions
+5
View File
@@ -4359,6 +4359,11 @@ if (UtilsKt.getTestTargetSupportsCodeCoverage(project)) {
}
}
task metadata_compare_typedefs(type: MetadataComparisonTest) {
enabled = (project.testTarget != 'wasm32')
defFile = "interop/basics/typedefs.def"
}
/**
* Creates tasks to build and execute stdlib tests.
*/
@@ -0,0 +1,2 @@
---
typedef int my_int;
+8
View File
@@ -40,6 +40,7 @@ repositories {
maven(buildKotlinCompilerRepo)
maven("https://cache-redirector.jetbrains.com/maven-central")
maven("https://kotlin.bintray.com/kotlinx")
maven("https://dl.bintray.com/kotlin/kotlin-dev")
}
val kotlinCompilerJar by configurations.creating
@@ -67,6 +68,8 @@ dependencies {
// Located in <repo root>/shared and always provided by the composite build.
api("org.jetbrains.kotlin:kotlin-native-shared:$konanVersion")
implementation("com.github.jengelman.gradle.plugins:shadow:5.1.0")
implementation("org.jetbrains.kotlinx:kotlinx-metadata-klib:0.0.1-dev-5")
}
sourceSets["main"].withConvention(KotlinSourceSet::class) {
@@ -93,6 +96,11 @@ gradlePlugin {
val compileKotlin: KotlinCompile by tasks
val compileGroovy: GroovyCompile by tasks
// https://youtrack.jetbrains.com/issue/KT-37435
compileKotlin.apply {
kotlinOptions.freeCompilerArgs += "-Xno-optimized-callable-references"
}
// Add Kotlin classes to a classpath for the Groovy compiler
compileGroovy.apply {
classpath += project.files(compileKotlin.destinationDir)
@@ -0,0 +1,87 @@
/*
* Copyright 2010-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the LICENSE file.
*/
package org.jetbrains.kotlin
import org.gradle.api.DefaultTask
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.TaskAction
import org.jetbrains.kotlin.klib.metadata.CInteropComparisonConfig
import org.jetbrains.kotlin.klib.metadata.MetadataCompareResult
import org.jetbrains.kotlin.klib.metadata.compareKlibMetadata
import org.jetbrains.kotlin.klib.metadata.expandFail
import org.jetbrains.kotlin.konan.target.HostManager
import java.io.File
/**
* Produces 2 interop libraries from the given [defFile].
* One in legacy `sourcecode` mode and another one in `metadata` mode.
* Then structurally compares metadata of the produced klibs.
*
*
* Motivation for this kind of tests:
*
* `sourcecode` mode generates Kotlin source file that is passes to a
* regular Kotlin/Native compiler.
* Thus, the produced metadata is correct (from compiler's point of view)
* because it is produced by the compiler itself.
*
* `metadata` mode generates metadata directly from parsed Clang AST.
* Since another algorithm is used, produced metadata may be incorrect.
*
* So we have to check that these two are more or less the same.
*/
open class MetadataComparisonTest : DefaultTask() {
private enum class Mode {
METADATA, SOURCECODE
}
/**
* Path to a cinterop *.def file.
*/
@Input
lateinit var defFile: String
@TaskAction
fun run() {
val metadataLibrary = cinterop(project.file(defFile), Mode.METADATA)
val sourcecodeLibrary = cinterop(project.file(defFile), Mode.SOURCECODE)
compareKlibMetadata(CInteropComparisonConfig(), sourcecodeLibrary.absolutePath, metadataLibrary.absolutePath).let { result ->
if (result is MetadataCompareResult.Fail) {
val message = StringBuilder().also {
expandFail(result, it::appendln)
}.toString()
throw TestFailedException(message)
}
}
}
private fun cinterop(defFile: File, mode: Mode): File {
val dist = project.kotlinNativeDist
val output = "${project.buildDir.absolutePath}/${defFile.nameWithoutExtension}_$mode"
val tool = if (HostManager.hostIsMingw) "cinterop.bat" else "cinterop"
val cinterop = File("${dist.canonicalPath}/bin/$tool").absolutePath
val args = listOf(
"-def", defFile.absolutePath,
"-mode", mode.name.toLowerCase(),
"-target", project.testTarget.visibleName,
"-no-default-libs", "-no-endorsed-libs",
"-o", output
)
runProcess(localExecutor(project), cinterop, args).let { result ->
if (result.exitCode != 0) {
println("""
cinterop failed.
exitCode: ${result.exitCode}
stdout:
${result.stdOut}
stderr:
${result.stdErr}
""".trimIndent())
}
}
return File(output)
}
}
@@ -0,0 +1,186 @@
/*
* Copyright 2010-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the LICENSE file.
*/
package org.jetbrains.kotlin.klib.metadata
import kotlinx.metadata.*
import kotlinx.metadata.klib.*
/**
* Structural comparison of Km* metadata.
* [configuration] allows to tune comparison process.
*
*/
internal class KmComparator(private val configuration: ComparisonConfig) {
fun compare(kmClass1: KmClass, kmClass2: KmClass): MetadataCompareResult = serialComparator(
compare(KmClass::name, ::compare) to "Different names: ${kmClass1.name}, ${kmClass2.name}",
::compareClassFlags to "Different flags for ${kmClass1.name}",
compare(KmClass::constructors, compareLists(::compare)) to "Constructors mismatch for ${kmClass1.name}",
compare(KmClass::properties, compareLists(::compare, KmProperty::mangle)) to "Properties mismatch for ${kmClass1.name}",
compare(KmClass::functions, compareLists(::compare, KmFunction::mangle)) to "Functions mismatch for ${kmClass1.name}",
)(kmClass1, kmClass2)
fun compare(typealias1: KmTypeAlias, typealias2: KmTypeAlias): MetadataCompareResult = serialComparator(
compare(KmTypeAlias::name, ::compare) to "Different names",
compare(KmTypeAlias::underlyingType, ::compareTypes) to "Underlying types mismatch",
compare(KmTypeAlias::expandedType, ::compareTypes) to "Expanded types mismatch",
compare(KmTypeAlias::typeParameters, compareLists(::compare)) to "Type parameters mismatch"
)(typealias1, typealias2)
fun compare(function1: KmFunction, function2: KmFunction): MetadataCompareResult = serialComparator(
compare(KmFunction::name, ::compare) to "Different names",
compare(KmFunction::returnType, ::compareTypes) to "Return type mismatch",
compare(KmFunction::valueParameters, compareLists(::compare)) to "Value parameters mismatch"
)(function1, function2)
fun compare(property1: KmProperty, property2: KmProperty): MetadataCompareResult = serialComparator(
compare(KmProperty::name, ::compare) to "Different names",
compare(KmProperty::returnType, ::compareTypes) to "Return type mismatch",
::comparePropertyFlags to "Flags mismatch",
(compare(KmProperty::getterFlags, ::comparePropertyAccessorFlags) to "Getter flags mismatch")
.takeIf { Flag.Property.HAS_GETTER(property1.flags) && Flag.Property.HAS_GETTER(property2.flags) },
(compare(KmProperty::setterFlags, ::comparePropertyAccessorFlags) to "Setter flags mismatch")
.takeIf { Flag.Property.HAS_SETTER(property1.flags) && Flag.Property.HAS_SETTER(property2.flags) }
)(property1, property2)
private fun compare(entry1: KlibEnumEntry, entry2: KlibEnumEntry): MetadataCompareResult = serialComparator(
compare(KlibEnumEntry::annotations, compareLists(::compare)) to "Different annotations",
compare(KlibEnumEntry::name, ::compare) to "Different names",
compare(KlibEnumEntry::ordinal, compareNullable(::compare)) to "Different ordinals"
)(entry1, entry2)
private fun checkFlag(flag: Flag, flagName: String? = null): (Flags, Flags) -> MetadataCompareResult = { f1, f2 ->
when {
flag(f1) != flag(f2) -> Fail("Flags mismatch: ${flag(f1)}, ${flag(f2)} for $flagName")
else -> Ok
}
}
private fun compare(value1: Int, value2: Int): MetadataCompareResult = when {
value1 == value2 -> Ok
else -> Fail("$value1 != $value2")
}
private fun compare(string1: String, string2: String): MetadataCompareResult = when {
string1 == string2 -> Ok
else -> Fail("$string1 != $string2")
}
private fun comparePropertyFlags(property1: KmProperty, property2: KmProperty): MetadataCompareResult =
serialComparator(
checkFlag(Flag.Property.IS_CONST, "IS_CONST"),
checkFlag(Flag.Property.HAS_SETTER, "HAS_SETTER"),
checkFlag(Flag.Property.HAS_GETTER, "HAS_GETTER"),
checkFlag(Flag.Property.IS_VAR, "IS_VAR"),
checkFlag(Flag.Property.HAS_CONSTANT, "HAS_CONSTANT"),
checkFlag(Flag.Property.IS_DECLARATION, "IS_DECLARATION"),
checkFlag(Flag.Property.IS_EXTERNAL, "IS_EXTERNAL")
)(property1.flags, property2.flags)
private fun comparePropertyAccessorFlags(flags1: Flags, flags2: Flags): MetadataCompareResult = serialComparator(
checkFlag(Flag.PropertyAccessor.IS_NOT_DEFAULT, "IS_NOT_DEFAULT"),
checkFlag(Flag.PropertyAccessor.IS_INLINE, "IS_INLINE"),
checkFlag(Flag.PropertyAccessor.IS_EXTERNAL, "IS_EXTERNAL"),
::compareVisibilityFlags,
::compareModalityFlags
)(flags1, flags2)
private fun compareClassFlags(class1: KmClass, class2: KmClass): MetadataCompareResult = serialComparator(
checkFlag(Flag.Class.IS_CLASS, "IS_CLASS"),
checkFlag(Flag.Class.IS_COMPANION_OBJECT, "IS_COMPANION_OBJECT"),
checkFlag(Flag.Class.IS_ENUM_CLASS, "IS_ENUM_CLASS"),
checkFlag(Flag.Class.IS_ENUM_ENTRY, "IS_ENUM_ENTRY"),
checkFlag(Flag.Class.IS_OBJECT, "IS_OBJECT"),
checkFlag(Flag.IS_FINAL, "IS_FINAL"),
checkFlag(Flag.IS_OPEN, "IS_OPEN"),
checkFlag(Flag.HAS_ANNOTATIONS, "HAS_ANNOTATIONS"),
::compareVisibilityFlags,
::compareModalityFlags
)(class1.flags, class2.flags)
private fun compareVisibilityFlags(flags1: Flags, flags2: Flags): MetadataCompareResult = serialComparator(
checkFlag(Flag.IS_PUBLIC, "IS_PUBLIC"),
checkFlag(Flag.IS_PRIVATE_TO_THIS, "IS_PRIVATE_TO_THIS"),
checkFlag(Flag.IS_PRIVATE, "IS_PRIVATE"),
checkFlag(Flag.IS_PROTECTED, "IS_PROTECTED"),
checkFlag(Flag.IS_INTERNAL, "IS_INTERNAL")
)(flags1, flags2)
private fun compareModalityFlags(flags1: Flags, flags2: Flags): MetadataCompareResult = serialComparator(
checkFlag(Flag.IS_FINAL, "IS_FINAL"),
checkFlag(Flag.IS_ABSTRACT, "IS_ABSTRACT"),
checkFlag(Flag.IS_OPEN, "IS_OPEN"),
checkFlag(Flag.IS_SEALED, "IS_SEALED")
)(flags1, flags2)
private fun compare(annotation1: KmAnnotation, annotation2: KmAnnotation): MetadataCompareResult = when {
annotation1.className != annotation2.className -> Fail("${annotation1.className} != ${annotation2.className}")
// TODO: compare values
else -> Ok
}
private fun compare(p1: KmValueParameter, p2: KmValueParameter): MetadataCompareResult = serialComparator(
compare(KmValueParameter::name, ::compare) to "Different names",
compare(KmValueParameter::type, compareNullable(::compareTypes)) to "Type mismatch",
compare(KmValueParameter::annotations, compareLists(::compare)) to "Annotations mismatch"
)(p1, p2)
private fun compareTypeFlags(flags1: Flags, flags2: Flags): MetadataCompareResult = serialComparator(
checkFlag(Flag.Type.IS_NULLABLE) to "Nullable flag mismatch",
checkFlag(Flag.Type.IS_SUSPEND) to "Suspend flag mismatch"
)(flags1, flags2)
private fun compareConstructorFlags(flags1: Flags, flags2: Flags): MetadataCompareResult = serialComparator(
checkFlag(Flag.Constructor.IS_PRIMARY) to "IS_PRIMARY mismatch"
)(flags1, flags2)
private fun compare(constructor1: KmConstructor, constructor2: KmConstructor): MetadataCompareResult = serialComparator(
::compareVisibilityFlags,
::compareConstructorFlags
)(constructor1.flags, constructor2.flags)
private fun compare(typeArgument1: KmTypeProjection, typeArgument2: KmTypeProjection): MetadataCompareResult = serialComparator(
compare(KmTypeProjection::type, compareNullable(::compareTypes))
)(typeArgument1, typeArgument2)
private fun compare(typeParameter1: KmTypeParameter, typeParameter2: KmTypeParameter): MetadataCompareResult =
when {
typeParameter1.variance != typeParameter2.variance -> Fail("Different variance")
typeParameter1.name != typeParameter2.name -> Fail("${typeParameter1.name}, ${typeParameter2.name}")
else -> Ok
}
private fun compareTypes(type1: KmType, type2: KmType): MetadataCompareResult = serialComparator(
compare(KmType::classifier, ::compare) to "Classifiers mismatch",
compare(KmType::arguments, compareLists(::compare)) to "Type arguments mismatch",
compare(KmType::flags, ::compareTypeFlags) to "Type flags mismatch for",
compare(KmType::abbreviatedType, compareNullable(::compareTypes)) to "Abbreviated types mismatch"
)(type1, type2)
private fun compare(class1: KmClassifier, class2: KmClassifier): MetadataCompareResult = when {
class1 is KmClassifier.TypeAlias && class2 is KmClassifier.TypeAlias -> {
if (class1.name == class2.name) Ok else Fail("Different type aliases: ${class1.name}, ${class2.name}")
}
class1 is KmClassifier.Class && class2 is KmClassifier.Class -> {
when (class1.name) {
class2.name -> Ok
else -> Fail("Different classes: ${class1.name}, ${class2.name}")
}
}
class1 is KmClassifier.TypeParameter && class2 is KmClassifier.TypeParameter -> {
// TODO: How to correctly compare type ids?
Ok
}
else -> Fail("class1 is $class1 and class2 is $class2")
}
private fun <T, R> compare(
property: T.() -> R,
comparator: (R, R) -> MetadataCompareResult
): (T, T) -> MetadataCompareResult = { o1, o2 ->
if (configuration.shouldCheck(property)) comparator(o1.property(), o2.property()) else Ok
}
}
@@ -0,0 +1,60 @@
/*
* Copyright 2010-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the LICENSE file.
*/
package org.jetbrains.kotlin.klib.metadata
import kotlinx.metadata.*
private fun render(element: Any?): String = when (element) {
is KmTypeAlias -> "typealias ${element.name}"
is KmFunction -> "function ${element.name}"
is KmProperty -> "property ${element.name}"
is KmClass -> "class ${element.name}"
is KmType -> "`type ${element.classifier}`"
else -> element.toString()
}
internal fun <T> serialComparator(
vararg comparators: Pair<(T, T) -> MetadataCompareResult, String>?
): (T, T) -> MetadataCompareResult = { o1, o2 ->
comparators.filterNotNull().map { (comparator, message) ->
comparator(o1, o2).let { result ->
if (result is Fail) Fail(message, result) else result
}
}.wrap()
}
internal fun <T> serialComparator(
vararg comparators: (T, T) -> MetadataCompareResult
): (T, T) -> MetadataCompareResult = { o1, o2 ->
comparators
.map { comparator -> comparator(o1, o2) }
.wrap()
}
internal fun Collection<MetadataCompareResult>.wrap(): MetadataCompareResult =
filterIsInstance<Fail>().let { fails ->
when (fails.size) {
0 -> Ok
else -> Fail(fails)
}
}
internal fun <T> compareNullable(
comparator: (T, T) -> MetadataCompareResult
): (T?, T?) -> MetadataCompareResult = { a, b ->
when {
a != null && b != null -> comparator(a, b)
a == null && b == null -> Ok
else -> Fail("${render(a)} ${render(b)}")
}
}
internal fun <T> compareLists(elementComparator: (T, T) -> MetadataCompareResult, sortBy: T.() -> String? = { null }) =
{ list1: List<T>, list2: List<T> -> compareLists(list1.sortedBy(sortBy), list2.sortedBy(sortBy), elementComparator) }
private fun <T> compareLists(l1: List<T>, l2: List<T>, comparator: (T, T) -> MetadataCompareResult) = when {
l1.size != l2.size -> Fail("${l1.size} != ${l2.size}")
else -> l1.zip(l2).map { comparator(it.first, it.second) }.wrap()
}
@@ -0,0 +1,54 @@
/*
* Copyright 2010-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the LICENSE file.
*/
package org.jetbrains.kotlin.klib.metadata
import kotlinx.metadata.*
import kotlinx.metadata.klib.KlibModuleFragmentReadStrategy
import kotlinx.metadata.klib.fqName
/**
* Output klib declarations in predictable sorted order.
*/
internal class SortedMergeStrategy : KlibModuleFragmentReadStrategy {
override fun processModuleParts(parts: List<KmModuleFragment>): List<KmModuleFragment> =
parts.fold(KmModuleFragment(), ::joinFragments).let(::listOf)
}
/**
* We need a stable order for overloaded functions.
*/
internal fun KmFunction.mangle(): String {
val typeParameters = typeParameters.joinToString(prefix = "<", postfix = ">", transform = KmTypeParameter::name)
val valueParameters = valueParameters.joinToString(prefix = "(", postfix = ")", transform = KmValueParameter::name)
val receiver = receiverParameterType?.classifier
return "$receiver.${name}.$typeParameters.$valueParameters"
}
internal fun KmProperty.mangle(): String {
val receiver = receiverParameterType?.classifier
return "$receiver.$name"
}
private fun joinAndSortPackages(pkg1: KmPackage, pkg2: KmPackage) = KmPackage().apply {
functions += (pkg1.functions + pkg2.functions).sortedBy(KmFunction::mangle)
properties += (pkg1.properties + pkg2.properties).sortedBy(KmProperty::name)
typeAliases += (pkg1.typeAliases + pkg2.typeAliases).sortedBy(KmTypeAlias::name)
}
/**
* Merges two fragments of a single module into one.
*/
internal fun joinFragments(fragment1: KmModuleFragment, fragment2: KmModuleFragment) = KmModuleFragment().apply {
assert(fragment1.fqName == fragment2.fqName)
pkg = when {
fragment1.pkg != null && fragment2.pkg != null -> joinAndSortPackages(fragment1.pkg!!, fragment2.pkg!!)
fragment1.pkg != null -> joinAndSortPackages(fragment1.pkg!!, KmPackage())
fragment2.pkg != null -> joinAndSortPackages(KmPackage(), fragment2.pkg!!)
else -> null
}
fqName = fragment1.fqName
classes += fragment1.classes.sortedBy(KmClass::name) + fragment2.classes.sortedBy(KmClass::name)
}
@@ -0,0 +1,25 @@
/*
* Copyright 2010-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the LICENSE file.
*/
package org.jetbrains.kotlin.klib.metadata
import kotlinx.metadata.klib.KlibModuleMetadata
import org.jetbrains.kotlin.library.MetadataLibrary
/**
* Provides access to metadata using default compiler's routine.
*/
internal class TrivialLibraryProvider(
private val library: MetadataLibrary
) : KlibModuleMetadata.MetadataLibraryProvider {
override val moduleHeaderData: ByteArray
get() = library.moduleHeaderData
override fun packageMetadata(fqName: String, partName: String): ByteArray =
library.packageMetadata(fqName, partName)
override fun packageMetadataParts(fqName: String): Set<String> =
library.packageMetadataParts(fqName)
}
@@ -0,0 +1,202 @@
/*
* Copyright 2010-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the LICENSE file.
*/
package org.jetbrains.kotlin.klib.metadata
import kotlinx.metadata.*
import kotlinx.metadata.klib.KlibModuleMetadata
import org.jetbrains.kotlin.konan.file.File
import org.jetbrains.kotlin.library.CompilerSingleFileKlibResolveAllowingIrProvidersStrategy
import org.jetbrains.kotlin.library.resolveSingleFileKlib
private inline fun <reified T : Any> compareElements(
comparisonConfig: ComparisonConfig,
elements: Map<String, Pair<T, T>>,
crossinline comparator: (T, T) -> MetadataCompareResult
): MetadataCompareResult = elements
.entries
.asSequence()
.filter { comparisonConfig.shouldCheckDeclaration(it.key) }
.map { comparator(it.value.first, it.value.second).messageIfFail("${it.key} mismatch") }
.toList()
.wrap()
private class JoinedFragments(
val classes: JoinResult<KmClass>,
val functions: JoinResult<KmFunction>,
val properties: JoinResult<KmProperty>,
val typeAliases: JoinResult<KmTypeAlias>,
)
private fun processMissing(comparisonConfig: ComparisonConfig, joinResult: JoinResult<*>): MetadataCompareResult {
val missingInFirst = joinResult.missingInFirst
.filter(comparisonConfig::shouldCheckDeclaration)
.map { Fail("$it missing in first fragment") }
val missingInSecond = joinResult.missingInSecond
.filter(comparisonConfig::shouldCheckDeclaration)
.map { Fail("$it missing in second fragment") }
return (missingInFirst + missingInSecond).let {
if (it.isEmpty()) Ok else Fail(it)
}
}
private fun MetadataCompareResult.messageIfFail(message: String): MetadataCompareResult =
if (this is Fail) Fail(message, this) else this
private fun processMissing(
comparisonConfig: ComparisonConfig,
joinedFragments: JoinedFragments
): MetadataCompareResult = listOf(
processMissing(comparisonConfig, joinedFragments.classes)
.messageIfFail("Missing classes"),
processMissing(comparisonConfig, joinedFragments.functions)
.messageIfFail("Missing functions"),
processMissing(comparisonConfig, joinedFragments.typeAliases)
.messageIfFail("Missing type aliases"),
processMissing(comparisonConfig, joinedFragments.properties)
.messageIfFail("Missing properties"),
).wrap()
private data class JoinResult<T>(
val joined: Map<String, Pair<T, T>>,
val missingInFirst: List<String>,
val missingInSecond: List<String>
)
private fun <T> buildJoined(e1: List<T>, e2: List<T>, key: T.() -> String): JoinResult<T> {
val m1 = e1.associateBy { it.key() }
val m2 = e2.associateBy { it.key() }
val joinedKeys = e1.map(key).filter { it in m2 }.toSet()
val joined = m1
.filterKeys(joinedKeys::contains)
.mapValues { (key, value) -> value to m2.getValue(key) }
return JoinResult(
joined,
(m1 - joinedKeys).keys.toList(),
(m2 - joinedKeys).keys.toList()
)
}
/**
* Wrapper around direct access to [fragment] that allows
* to uniformly process all its components.
*/
private fun <T> processFragment(
fragment: KmModuleFragment,
action: (List<KmClass>, List<KmFunction>, List<KmProperty>, List<KmTypeAlias>) -> T
): T {
val classes = fragment.classes
val pkg = fragment.pkg
return when {
pkg != null -> action(classes, pkg.functions, pkg.properties, pkg.typeAliases)
else -> action(classes, emptyList(), emptyList(), emptyList())
}
}
private fun compareMetadata(
comparisonConfig: ComparisonConfig,
metadataModuleA: KlibModuleMetadata,
metadataModuleB: KlibModuleMetadata
): MetadataCompareResult {
val fragmentA = metadataModuleA.fragments.fold(KmModuleFragment(), ::joinFragments)
val fragmentB = metadataModuleB.fragments.fold(KmModuleFragment(), ::joinFragments)
val joinedFragments = processFragment(fragmentA) { classesA, functionsA, propertiesA, typeAliasesA ->
processFragment(fragmentB) { classesB, functionsB, propertiesB, typeAliasesB ->
JoinedFragments(
buildJoined(classesA, classesB, KmClass::name),
buildJoined(functionsA, functionsB, KmFunction::name),
buildJoined(propertiesA, propertiesB, KmProperty::name),
buildJoined(typeAliasesA, typeAliasesB, KmTypeAlias::name)
)
}
}
val comparator = KmComparator(comparisonConfig)
return joinedFragments.run {
listOf(
compareElements(comparisonConfig, classes.joined, comparator::compare),
compareElements(comparisonConfig, functions.joined, comparator::compare),
compareElements(comparisonConfig, properties.joined, comparator::compare),
compareElements(comparisonConfig, typeAliases.joined, comparator::compare),
processMissing(comparisonConfig, this)
)
}.wrap()
}
sealed class MetadataCompareResult {
class Fail(
val children: Collection<Fail>, val message: String? = null
) : MetadataCompareResult() {
constructor(message: String, child: Fail? = null)
: this(listOfNotNull(child), message)
}
object Ok : MetadataCompareResult()
}
// A neat way to have short names internally without polluting client's namespace.
internal typealias Fail = MetadataCompareResult.Fail
internal typealias Ok = MetadataCompareResult.Ok
fun expandFail(fail: Fail, output: (String) -> Unit, padding: String = "") {
fail.message?.let { output("$padding$it") }
fail.children.forEach {
expandFail(it, output, "$padding ")
}
}
/**
* Configure what should be tested and what shouldn't.
*
* TODO: Add a way to conditionally disable property comparison.
* E.g. "If class name is Companion do not compare flags".
*/
interface ComparisonConfig {
/**
* Should the declaration be compared at all.
*/
fun shouldCheckDeclaration(element: String): Boolean
/**
* Should we check property of declaration.
*/
fun <T, R> shouldCheck(property: T.() -> R): Boolean
}
/**
* Configuration for comparing `metadata` and `sourcecode` cinterop modes.
*/
class CInteropComparisonConfig : ComparisonConfig {
override fun <T, R> shouldCheck(property: T.() -> R): Boolean = when (property) {
// Kotlin compiler may incorrectly omit abbreviatedType in some cases.
KmType::abbreviatedType -> false
else -> true
}
override fun shouldCheckDeclaration(element: String): Boolean = when {
// kniBridge is generated only in sourcecode mode.
element.startsWith("kniBridge") -> false
else -> true
}
}
/**
* Structurally compares metadata of given libraries.
*/
fun compareKlibMetadata(
comparisonConfig: ComparisonConfig,
pathToFirstLibrary: String,
pathToSecondLibrary: String
): MetadataCompareResult {
val resolveStrategy = CompilerSingleFileKlibResolveAllowingIrProvidersStrategy(
knownIrProviders = listOf("kotlin.native.cinterop")
)
val klib1 = resolveSingleFileKlib(File(pathToFirstLibrary), strategy = resolveStrategy)
val klib2 = resolveSingleFileKlib(File(pathToSecondLibrary), strategy = resolveStrategy)
val metadata1 = KlibModuleMetadata.read(TrivialLibraryProvider(klib1), SortedMergeStrategy())
val metadata2 = KlibModuleMetadata.read(TrivialLibraryProvider(klib2), SortedMergeStrategy())
return compareMetadata(comparisonConfig, metadata1, metadata2)
}
+2
View File
@@ -35,6 +35,7 @@ buildscript {
maven { url kotlinCompilerRepo }
maven { url "https://kotlin.bintray.com/kotlinx" }
maven { url "https://cache-redirector.jetbrains.com/maven-central" }
maven { url "https://dl.bintray.com/kotlin/kotlin-dev" }
jcenter()
}
@@ -103,6 +104,7 @@ allprojects {
maven {
url 'https://cache-redirector.jetbrains.com/jcenter'
}
maven { url "https://dl.bintray.com/kotlin/kotlin-dev" }
}
}
if (path != ":dependencies") {