[Commonizer] Rework test infrastructure to compare metadata instead of descriptors

This commit is contained in:
Dmitriy Dolovov
2021-01-29 13:49:51 +03:00
parent f67a9615b8
commit 5ff6b5ef42
7 changed files with 164 additions and 707 deletions
+2
View File
@@ -36,6 +36,8 @@ dependencies {
testImplementation(commonDep("junit:junit"))
testImplementation(projectTests(":compiler:tests-common"))
testImplementation(project(":kotlinx-metadata-klib")) { isTransitive = false }
testImplementation(project(":kotlinx-metadata")) { isTransitive = false }
}
val runCommonizer by tasks.registering(JavaExec::class) {
@@ -1,5 +1,5 @@
/*
* Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
@@ -15,11 +15,8 @@ import org.jetbrains.kotlin.konan.file.File as KFile
/**
* Provides access to metadata using default compiler's routine.
*/
// TODO: move to a separate module (kotlin-native-utils-metadata?) to share with C-interop tool?
class TrivialLibraryProvider(
private val library: MetadataLibrary
) : KlibModuleMetadata.MetadataLibraryProvider {
// TODO: extract to a separate module (kotlin-native-utils-metadata?) to share with C-interop tool?
class KotlinMetadataLibraryProvider(private val library: MetadataLibrary) : KlibModuleMetadata.MetadataLibraryProvider {
override val moduleHeaderData: ByteArray
get() = library.moduleHeaderData
@@ -34,7 +31,7 @@ class TrivialLibraryProvider(
check(libraryPath.exists()) { "Library does not exist: $libraryPath" }
val library = resolveSingleFileKlib(KFile(libraryPath.absolutePath), strategy = ToolingSingleFileKlibResolveStrategy)
return KlibModuleMetadata.read(TrivialLibraryProvider(library))
return KlibModuleMetadata.read(KotlinMetadataLibraryProvider(library))
}
}
}
@@ -11,8 +11,68 @@ import org.jetbrains.kotlin.descriptors.commonizer.utils.KNI_BRIDGE_FUNCTION_PRE
import java.util.*
import kotlin.reflect.KProperty0
// TODO: move to kotlinx-metadata library?
// TODO: extract to kotlinx-metadata-klib library?
class MetadataDeclarationsComparator(private val config: Config = Config.Default) {
interface Config {
val rootPathElement: String
get() = "<root>"
/**
* Certain auxiliary metadata entities may be intentionally excluded from comparison.
* Ex: Kotlin/Native interface bridge functions.
*/
fun shouldCheckDeclaration(declaration: Any): Boolean =
when (declaration) {
is KmFunction -> !declaration.name.startsWith(KNI_BRIDGE_FUNCTION_PREFIX)
else -> true
}
companion object Default : Config
}
sealed class Result {
object Success : Result() {
override fun toString() = "Success"
}
class Failure(val mismatches: Collection<Mismatch>) : Result() {
init {
check(mismatches.isNotEmpty())
}
override fun toString() = "Failure (${mismatches.size} mismatches)"
}
}
sealed class Mismatch {
abstract val kind: String
abstract val name: String
abstract val path: List<String>
// an entity has different non-nullable values
data class DifferentValues(
override val kind: String,
override val name: String,
override val path: List<String>,
val valueA: Any,
val valueB: Any
) : Mismatch()
// an entity is missing at one side and present at another side,
// or: an entity has nullable value at one side and non-nullable value at another side
data class MissingEntity(
override val kind: String,
override val name: String,
override val path: List<String>,
val existentValue: Any,
val missingInA: Boolean
) : Mismatch() {
val missingInB: Boolean
get() = !missingInA
}
}
private val mismatches = mutableListOf<Mismatch>()
private class Context(pathElement: String, parent: Context? = null) {
@@ -831,62 +891,3 @@ class MetadataDeclarationsComparator(private val config: Config = Config.Default
}
}
}
interface Config {
val rootPathElement: String
get() = "<root>"
/**
* Certain auxiliary metadata entities may be intentionally excluded from comparison.
* Ex: Kotlin/Native interface bridge functions.
*/
fun shouldCheckDeclaration(declaration: Any): Boolean =
when (declaration) {
is KmFunction -> !declaration.name.startsWith(KNI_BRIDGE_FUNCTION_PREFIX)
else -> true
}
companion object Default : Config
}
sealed class Result {
object Success : Result() {
override fun toString() = "Success"
}
class Failure(val mismatches: Collection<Mismatch>) : Result() {
init {
check(mismatches.isNotEmpty())
}
override fun toString() = "Failure (${mismatches.size} mismatches)"
}
}
sealed class Mismatch {
abstract val kind: String
abstract val name: String
abstract val path: List<String>
// an entity has different non-nullable values
data class DifferentValues(
override val kind: String,
override val name: String,
override val path: List<String>,
val valueA: Any,
val valueB: Any
) : Mismatch()
// an entity is missing at one side and present at another side,
// or: an entity has nullable value at one side and non-nullable value at another side
data class MissingEntity(
override val kind: String,
override val name: String,
override val path: List<String>,
val presentValue: Any,
val missingInA: Boolean
) : Mismatch() {
val missingInB: Boolean
get() = !missingInA
}
}
@@ -0,0 +1,44 @@
/*
* Copyright 2010-2020 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.descriptors.commonizer.metadata.utils
import kotlinx.metadata.klib.KlibModuleMetadata
class SerializedMetadataLibraryProvider(
override val moduleHeaderData: ByteArray,
fragments: List<List<ByteArray>>,
fragmentNames: List<String>
) : KlibModuleMetadata.MetadataLibraryProvider {
private val fragmentMap: Map<String, Map<String, ByteArray>>
init {
check(fragments.size == fragmentNames.size)
fragmentMap = fragmentNames.mapIndexed { fragmentIndex, fragmentName ->
// fragmentName is package FQ name, fragmentShortName is right-most part of package FQ name
val fragmentShortName = fragmentName.substringAfterLast('.')
val fragmentParts = fragments[fragmentIndex]
val digitCount = fragmentParts.size.toString().length
// N.B. the same fragment part numbering scheme as in org.jetbrains.kotlin.library.impl.MetadataWriterImpl
val fragmentPartMap = fragmentParts.mapIndexed { partIndex, part ->
val partName = partIndex.toString().padStart(digitCount, '0') + "_" + fragmentShortName
partName to part
}.toMap()
fragmentName to fragmentPartMap
}.toMap()
}
override fun packageMetadataParts(fqName: String): Set<String> {
return fragmentMap.getValue(fqName).keys
}
override fun packageMetadata(fqName: String, partName: String): ByteArray {
return fragmentMap.getValue(fqName).getValue(partName)
}
}
@@ -11,11 +11,14 @@ import org.jetbrains.kotlin.analyzer.ModuleInfo
import org.jetbrains.kotlin.analyzer.common.CommonDependenciesContainer
import org.jetbrains.kotlin.analyzer.common.CommonPlatformAnalyzerServices
import org.jetbrains.kotlin.analyzer.common.CommonResolverForModuleFactory
import org.jetbrains.kotlin.backend.common.serialization.metadata.KlibMetadataMonolithicSerializer
import org.jetbrains.kotlin.backend.common.serialization.metadata.KlibMetadataVersion
import org.jetbrains.kotlin.builtins.DefaultBuiltIns
import org.jetbrains.kotlin.cli.jvm.compiler.EnvironmentConfigFiles
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment
import org.jetbrains.kotlin.config.CommonConfigurationKeys
import org.jetbrains.kotlin.config.CompilerConfiguration
import org.jetbrains.kotlin.config.LanguageVersionSettingsImpl
import org.jetbrains.kotlin.config.languageVersionSettings
import org.jetbrains.kotlin.descriptors.CallableMemberDescriptor
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
@@ -30,6 +33,7 @@ import org.jetbrains.kotlin.descriptors.impl.DeclarationDescriptorVisitorEmptyBo
import org.jetbrains.kotlin.descriptors.impl.FunctionDescriptorImpl
import org.jetbrains.kotlin.descriptors.impl.ModuleDescriptorImpl
import org.jetbrains.kotlin.js.resolve.diagnostics.findPsi
import org.jetbrains.kotlin.library.SerializedMetadata
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.platform.CommonPlatforms
import org.jetbrains.kotlin.psi.KtFile
@@ -74,25 +78,21 @@ abstract class AbstractCommonizationFromSourcesTest : KtUsefulTestCase() {
val sharedTarget: SharedTarget = analyzedModules.sharedTarget
assertEquals(sharedTarget, result.sharedTarget)
val sharedModuleAsExpected: ModuleDescriptor = analyzedModules.commonizedModules.getValue(sharedTarget)
val sharedModuleByCommonizer: ModuleDescriptor =
(result.modulesByTargets.getValue(sharedTarget).single() as ModuleResult.Commonized).module!!
val sharedModuleAsExpected: SerializedMetadata = analyzedModules.commonizedModules.getValue(sharedTarget)
val sharedModuleByCommonizer: SerializedMetadata =
(result.modulesByTargets.getValue(sharedTarget).single() as ModuleResult.Commonized).metadata.metadata
assertValidModule(sharedModuleAsExpected)
assertValidModule(sharedModuleByCommonizer)
assertModulesAreEqual(sharedModuleAsExpected, sharedModuleByCommonizer, "\"$sharedTarget\" target")
assertModulesAreEqual(sharedModuleAsExpected, sharedModuleByCommonizer, sharedTarget)
val leafTargets: Set<LeafTarget> = analyzedModules.leafTargets
assertEquals(leafTargets, result.leafTargets)
for (leafTarget in leafTargets) {
val leafTargetModuleAsExpected: ModuleDescriptor = analyzedModules.commonizedModules.getValue(leafTarget)
val leafTargetModuleByCommonizer: ModuleDescriptor =
(result.modulesByTargets.getValue(leafTarget).single() as ModuleResult.Commonized).module!!
val leafTargetModuleAsExpected: SerializedMetadata = analyzedModules.commonizedModules.getValue(leafTarget)
val leafTargetModuleByCommonizer: SerializedMetadata =
(result.modulesByTargets.getValue(leafTarget).single() as ModuleResult.Commonized).metadata.metadata
assertValidModule(leafTargetModuleAsExpected)
assertValidModule(leafTargetModuleByCommonizer)
assertModulesAreEqual(leafTargetModuleAsExpected, leafTargetModuleByCommonizer, "\"$leafTarget\" target")
assertModulesAreEqual(leafTargetModuleAsExpected, leafTargetModuleByCommonizer, leafTarget)
}
}
}
@@ -182,7 +182,7 @@ private class AnalyzedModuleDependencies(
private class AnalyzedModules(
val originalModules: Map<CommonizerTarget, ModuleDescriptor>,
val commonizedModules: Map<CommonizerTarget, ModuleDescriptor>,
val commonizedModules: Map<CommonizerTarget, SerializedMetadata>,
val dependeeModules: Map<CommonizerTarget, List<ModuleDescriptor>>
) {
val leafTargets: Set<LeafTarget>
@@ -232,11 +232,16 @@ private class AnalyzedModules(
parentDisposable: Disposable
): AnalyzedModules = with(sourceModuleRoots) {
// phase 1: provide the modules that are the dependencies for "original" and "commonized" modules
val (dependeeModules, dependencies) = createDependeeModules(sharedTarget, dependeeRoots, parentDisposable)
val (dependeeModules: Map<CommonizerTarget, List<ModuleDescriptor>>, dependencies: AnalyzedModuleDependencies) =
createDependeeModules(sharedTarget, dependeeRoots, parentDisposable)
// phase 2: build "original" and "commonized" modules
val originalModules = createModules(sharedTarget, originalRoots, dependencies, parentDisposable)
val commonizedModules = createModules(sharedTarget, commonizedRoots, dependencies, parentDisposable)
val originalModules: Map<CommonizerTarget, ModuleDescriptor> =
createModules(sharedTarget, originalRoots, dependencies, parentDisposable)
val commonizedModules: Map<CommonizerTarget, SerializedMetadata> =
createModules(sharedTarget, commonizedRoots, dependencies, parentDisposable)
.mapValues { (_, moduleDescriptor) -> serializer.serializeModule(moduleDescriptor) }
return AnalyzedModules(originalModules, commonizedModules, dependeeModules)
}
@@ -334,6 +339,13 @@ private class AnalyzedModules(
return module
}
private val serializer = KlibMetadataMonolithicSerializer(
languageVersionSettings = LanguageVersionSettingsImpl.DEFAULT,
metadataVersion = KlibMetadataVersion.INSTANCE,
skipExpects = false,
project = null
)
}
}
@@ -1,577 +0,0 @@
/*
* Copyright 2010-2019 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.descriptors.commonizer.utils
import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.descriptors.annotations.AnnotationDescriptor
import org.jetbrains.kotlin.descriptors.annotations.Annotations
import org.jetbrains.kotlin.descriptors.commonizer.mergedtree.*
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.resolve.constants.AnnotationValue
import org.jetbrains.kotlin.resolve.constants.ConstantValue
import org.jetbrains.kotlin.resolve.descriptorUtil.classId
import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe
import org.jetbrains.kotlin.resolve.scopes.MemberScope
import org.jetbrains.kotlin.types.AbbreviatedType
import org.jetbrains.kotlin.types.KotlinType
import kotlin.contracts.ExperimentalContracts
import kotlin.reflect.KCallable
import kotlin.test.fail
@ExperimentalContracts
internal class ComparingDeclarationsVisitor(
val designatorMessage: String
) : DeclarationDescriptorVisitor<Unit, ComparingDeclarationsVisitor.Context> {
inner class Context private constructor(
private val actual: DeclarationDescriptor?,
private val path: List<String>
) {
constructor(actual: DeclarationDescriptor?) : this(actual, listOf(actual.toString()))
fun nextLevel(nextActual: DeclarationDescriptor?) = Context(nextActual, path + nextActual.toString())
fun nextLevel(customPathElement: String) = Context(actual, path + customPathElement)
inline fun <reified T> getActualAs() = actual as T
override fun toString() =
"""
|Context: ${this@ComparingDeclarationsVisitor.designatorMessage}
|Path: ${path.joinToString(separator = " ->\n\t")}"
""".trimMargin()
}
override fun visitModuleDeclaration(expected: ModuleDescriptor, context: Context) {
val actual = context.getActualAs<ModuleDescriptor>()
context.assertFieldsEqual(expected::getName, actual::getName)
fun collectPackageMemberScopes(module: ModuleDescriptor): Map<FqName, MemberScope> = mutableMapOf<FqName, MemberScope>().also {
module.collectNonEmptyPackageMemberScopes { packageFqName, memberScope ->
if (memberScope.getContributedDescriptors().isNotEmpty())
it[packageFqName] = memberScope
}
}
val expectedPackageMemberScopes = collectPackageMemberScopes(expected)
val actualPackageMemberScopes = collectPackageMemberScopes(actual)
context.assertSetsEqual(expectedPackageMemberScopes.keys, actualPackageMemberScopes.keys, "sets of packages")
for (packageFqName in expectedPackageMemberScopes.keys) {
val expectedMemberScope = expectedPackageMemberScopes.getValue(packageFqName)
val actualMemberScope = actualPackageMemberScopes.getValue(packageFqName)
visitMemberScopes(expectedMemberScope, actualMemberScope, context.nextLevel("package member scope [$packageFqName]"))
}
}
private fun visitMemberScopes(expected: MemberScope, actual: MemberScope, context: Context) {
val expectedProperties = mutableMapOf<PropertyApproximationKey, PropertyDescriptor>()
val expectedFunctions = mutableMapOf<FunctionApproximationKey, SimpleFunctionDescriptor>()
val expectedClasses = mutableMapOf<FqName, ClassDescriptor>()
val expectedTypeAliases = mutableMapOf<FqName, TypeAliasDescriptor>()
expected.collectMembers(
PropertyCollector { expectedProperties[PropertyApproximationKey(it)] = it },
FunctionCollector { expectedFunctions[FunctionApproximationKey(it)] = it },
ClassCollector { expectedClasses[it.fqNameSafe] = it },
TypeAliasCollector { expectedTypeAliases[it.fqNameSafe] = it }
)
val actualProperties = mutableMapOf<PropertyApproximationKey, PropertyDescriptor>()
val actualFunctions = mutableMapOf<FunctionApproximationKey, SimpleFunctionDescriptor>()
val actualClasses = mutableMapOf<FqName, ClassDescriptor>()
val actualTypeAliases = mutableMapOf<FqName, TypeAliasDescriptor>()
actual.collectMembers(
PropertyCollector { actualProperties[PropertyApproximationKey(it)] = it },
FunctionCollector { actualFunctions[FunctionApproximationKey(it)] = it },
ClassCollector { actualClasses[it.fqNameSafe] = it },
TypeAliasCollector { actualTypeAliases[it.fqNameSafe] = it }
)
context.assertSetsEqual(expectedProperties.keys, actualProperties.keys, "sets of properties")
expectedProperties.forEach { (propertyKey, expectedProperty) ->
val actualProperty = actualProperties.getValue(propertyKey)
expectedProperty.accept(this, context.nextLevel(actualProperty))
}
context.assertSetsEqual(expectedFunctions.keys, actualFunctions.keys, "sets of functions")
expectedFunctions.forEach { (functionKey, expectedFunction) ->
val actualFunction = actualFunctions.getValue(functionKey)
expectedFunction.accept(this, context.nextLevel(actualFunction))
}
context.assertSetsEqual(expectedClasses.keys, actualClasses.keys, "sets of classes")
expectedClasses.forEach { (classFqName, expectedClass) ->
val actualClass = actualClasses.getValue(classFqName)
expectedClass.accept(this, context.nextLevel(actualClass))
}
context.assertSetsEqual(expectedTypeAliases.keys, actualTypeAliases.keys, "sets of type aliases")
expectedTypeAliases.forEach { (typeAliasFqName, expectedTypeAlias) ->
val actualTypeAlias = actualTypeAliases.getValue(typeAliasFqName)
expectedTypeAlias.accept(this, context.nextLevel(actualTypeAlias))
}
}
override fun visitFunctionDescriptor(expected: FunctionDescriptor, context: Context) {
@Suppress("NAME_SHADOWING")
val expected = expected as SimpleFunctionDescriptor
val actual = context.getActualAs<SimpleFunctionDescriptor>()
visitAnnotations(expected.annotations, actual.annotations, context.nextLevel("Function annotations"))
context.assertFieldsEqual(expected::getName, actual::getName)
context.assertFieldsEqual(expected::getVisibility, actual::getVisibility)
context.assertFieldsEqual(expected::getModality, actual::getModality)
context.assertFieldsEqual(expected::getKind, actual::getKind)
context.assertFieldsEqual(expected::isOperator, actual::isOperator)
context.assertFieldsEqual(expected::isInfix, actual::isInfix)
context.assertFieldsEqual(expected::isInline, actual::isInline)
context.assertFieldsEqual(expected::isTailrec, actual::isTailrec)
context.assertFieldsEqual(expected::isSuspend, actual::isSuspend)
context.assertFieldsEqual(expected::isExternal, actual::isExternal)
context.assertFieldsEqual(expected::isExpect, actual::isExpect)
context.assertFieldsEqual(expected::hasStableParameterNames, actual::hasStableParameterNames)
context.assertFieldsEqual(expected::hasSynthesizedParameterNames, actual::hasSynthesizedParameterNames)
if (!expected.isActual || actual.kind != CallableMemberDescriptor.Kind.DELEGATION) {
context.assertFieldsEqual(expected::isActual, actual::isActual)
} /* else {
// don't check, because there can be any value in expect.isActual
// see org.jetbrains.kotlin.resolve.DelegationResolver
} */
visitType(expected.returnType, actual.returnType, context.nextLevel("Function type"))
visitValueParameterDescriptorList(expected.valueParameters, actual.valueParameters, context.nextLevel("Function value parameters"))
visitReceiverParameterDescriptor(expected.extensionReceiverParameter, context.nextLevel(actual.extensionReceiverParameter))
visitReceiverParameterDescriptor(expected.dispatchReceiverParameter, context.nextLevel(actual.dispatchReceiverParameter))
visitTypeParameters(expected.typeParameters, actual.typeParameters, context.nextLevel("Function type parameters"))
}
private fun visitValueParameterDescriptorList(
expected: List<ValueParameterDescriptor>,
actual: List<ValueParameterDescriptor>,
context: Context
) {
context.assertEquals(expected.size, actual.size, "Size of value parameters list")
expected.forEachIndexed { index, expectedParam ->
val actualParam = actual[index]
expectedParam.accept(this, context.nextLevel(actualParam))
}
}
override fun visitValueParameterDescriptor(expected: ValueParameterDescriptor, context: Context) {
val actual = context.getActualAs<ValueParameterDescriptor>()
visitAnnotations(expected.annotations, actual.annotations, context.nextLevel("Value parameter annotations"))
context.assertFieldsEqual(expected::getName, actual::getName)
context.assertFieldsEqual(expected::index, actual::index)
context.assertFieldsEqual(expected::declaresDefaultValue, actual::declaresDefaultValue)
context.assertFieldsEqual(expected::isCrossinline, actual::isCrossinline)
context.assertFieldsEqual(expected::isNoinline, actual::isNoinline)
visitType(expected.type, actual.type, context.nextLevel("Value parameter type"))
visitType(expected.varargElementType, actual.varargElementType, context.nextLevel("Value parameter vararg element type"))
}
private fun visitTypeParameters(expected: List<TypeParameterDescriptor>, actual: List<TypeParameterDescriptor>, context: Context) {
context.assertEquals(expected.size, actual.size, "Type parameters list size")
expected.forEachIndexed { index, expectedParam ->
val actualParam = actual[index]
visitTypeParameterDescriptor(expectedParam, context.nextLevel(actualParam))
}
}
override fun visitTypeParameterDescriptor(expected: TypeParameterDescriptor, context: Context) {
val actual = context.getActualAs<TypeParameterDescriptor>()
visitAnnotations(expected.annotations, actual.annotations, context.nextLevel("Type parameter annotations"))
context.assertFieldsEqual(expected::getName, actual::getName)
context.assertFieldsEqual(expected::getIndex, actual::getIndex)
context.assertFieldsEqual(expected::isCapturedFromOuterDeclaration, actual::isCapturedFromOuterDeclaration)
context.assertFieldsEqual(expected::isReified, actual::isReified)
context.assertFieldsEqual(expected::getVariance, actual::getVariance)
val expectedUpperBounds = expected.upperBounds
val actualUpperBounds = actual.upperBounds
context.assertEquals(expectedUpperBounds.size, actualUpperBounds.size, "Size of upper bound types")
expectedUpperBounds.forEachIndexed { index, expectedType ->
val actualType = actualUpperBounds[index]
visitType(expectedType, actualType, context.nextLevel("Type parameter type"))
}
}
override fun visitClassDescriptor(expected: ClassDescriptor, context: Context) {
val actual = context.getActualAs<ClassDescriptor>()
visitAnnotations(expected.annotations, actual.annotations, context.nextLevel("Class annotations"))
context.assertFieldsEqual(expected::getName, actual::getName)
context.assertFieldsEqual(expected::getVisibility, actual::getVisibility)
context.assertFieldsEqual(expected::getModality, actual::getModality)
context.assertFieldsEqual(expected::getKind, actual::getKind)
context.assertFieldsEqual(expected::isCompanionObject, actual::isCompanionObject)
context.assertFieldsEqual(expected::isData, actual::isData)
context.assertFieldsEqual(expected::isInline, actual::isInline)
context.assertFieldsEqual(expected::isValue, actual::isValue)
context.assertFieldsEqual(expected::isInner, actual::isInner)
context.assertFieldsEqual(expected::isExternal, actual::isExternal)
context.assertFieldsEqual(expected::isExpect, actual::isExpect)
context.assertFieldsEqual(expected::isActual, actual::isActual)
visitTypeParameters(
expected.declaredTypeParameters,
actual.declaredTypeParameters,
context.nextLevel("Class declared type parameters")
)
if (expected.sealedSubclasses.isNotEmpty() || actual.sealedSubclasses.isNotEmpty()) {
val expectedSealedSubclassesFqNames = expected.sealedSubclasses.mapTo(HashSet()) { it.fqNameSafe }
val actualSealedSubclassesFqNames = actual.sealedSubclasses.mapTo(HashSet()) { it.fqNameSafe }
context.assertSetsEqual(expectedSealedSubclassesFqNames, actualSealedSubclassesFqNames, "Sealed subclasses FQ names")
}
val expectedSupertypeSignatures = expected.typeConstructor.supertypes.mapTo(HashSet()) { it.signature }
val actualSupertypeSignatures = actual.typeConstructor.supertypes.mapTo(HashSet()) { it.signature }
context.assertSetsEqual(expectedSupertypeSignatures, actualSupertypeSignatures, "Supertypes signatures")
if (expected.constructors.isNotEmpty() || actual.constructors.isNotEmpty()) {
val expectedConstructors = expected.constructors.associateBy { ConstructorApproximationKey(it) }
val actualConstructors = actual.constructors.associateBy { ConstructorApproximationKey(it) }
context.assertSetsEqual(expectedConstructors.keys, actualConstructors.keys, "sets of class constructors")
for (key in expectedConstructors.keys) {
val expectedConstructor = expectedConstructors.getValue(key)
val actualConstructor = actualConstructors.getValue(key)
visitConstructorDescriptor(expectedConstructor, context.nextLevel(actualConstructor))
}
}
visitMemberScopes(
expected.unsubstitutedMemberScope,
actual.unsubstitutedMemberScope,
context.nextLevel("class member scope [${expected.fqNameSafe}]")
)
}
override fun visitConstructorDescriptor(expected: ConstructorDescriptor, context: Context) {
val actual = context.getActualAs<ConstructorDescriptor>()
visitAnnotations(expected.annotations, actual.annotations, context.nextLevel("Constructor annotations"))
context.assertFieldsEqual(expected::getVisibility, actual::getVisibility)
context.assertFieldsEqual(expected::isPrimary, actual::isPrimary)
context.assertFieldsEqual(expected::getKind, actual::getKind)
context.assertFieldsEqual(expected::hasStableParameterNames, actual::hasStableParameterNames)
context.assertFieldsEqual(expected::hasSynthesizedParameterNames, actual::hasSynthesizedParameterNames)
context.assertFieldsEqual(expected::isExpect, actual::isExpect)
context.assertFieldsEqual(expected::isActual, actual::isActual)
visitValueParameterDescriptorList(
expected.valueParameters,
actual.valueParameters,
context.nextLevel("Constructor value parameters")
)
visitTypeParameters(expected.typeParameters, actual.typeParameters, context.nextLevel("Constructor type parameters"))
}
override fun visitTypeAliasDescriptor(expected: TypeAliasDescriptor, context: Context) {
val actual = context.getActualAs<TypeAliasDescriptor>()
visitAnnotations(expected.annotations, actual.annotations, context.nextLevel("Type alias annotations"))
context.assertFieldsEqual(expected::getName, actual::getName)
context.assertFieldsEqual(expected::getVisibility, actual::getVisibility)
context.assertFieldsEqual(expected::isActual, actual::isActual)
visitTypeParameters(
expected.declaredTypeParameters,
actual.declaredTypeParameters,
context.nextLevel("Type alias declared type parameters")
)
visitType(expected.underlyingType, actual.underlyingType, context.nextLevel("Type alias underlying type"))
visitType(expected.expandedType, actual.expandedType, context.nextLevel("Type alias expanded type"))
}
override fun visitPropertyDescriptor(expected: PropertyDescriptor, context: Context) {
val actual = context.getActualAs<PropertyDescriptor>()
visitAnnotations(expected.annotations, actual.annotations, context.nextLevel("Property annotations"))
context.assertFieldsEqual(expected::getName, actual::getName)
context.assertFieldsEqual(expected::getVisibility, actual::getVisibility)
context.assertFieldsEqual(expected::getModality, actual::getModality)
context.assertFieldsEqual(expected::isVar, actual::isVar)
context.assertFieldsEqual(expected::getKind, actual::getKind)
context.assertFieldsEqual(expected::isLateInit, actual::isLateInit)
context.assertFieldsEqual(expected::isConst, actual::isConst)
context.assertFieldsEqual(expected::isExternal, actual::isExternal)
context.assertFieldsEqual(expected::isExpect, actual::isExpect)
if (!expected.isActual || actual.kind != CallableMemberDescriptor.Kind.DELEGATION) {
context.assertFieldsEqual(expected::isActual, actual::isActual)
} /* else {
// don't check, because there can be any value in expect.isActual
// see org.jetbrains.kotlin.resolve.DelegationResolver
} */
context.assertFieldsEqual(expected::isDelegated, actual::isDelegated)
visitAnnotations(
expected.delegateField?.annotations,
actual.delegateField?.annotations,
context.nextLevel("Property delegate field annotations")
)
visitAnnotations(
expected.backingField?.annotations,
actual.backingField?.annotations,
context.nextLevel("Property backing field annotations")
)
context.assertEquals(expected.compileTimeInitializer.isNull(), actual.compileTimeInitializer.isNull(), "compile-time initializers")
visitType(expected.type, actual.type, context.nextLevel("Property type"))
visitPropertyGetterDescriptor(expected.getter, context.nextLevel(actual.getter))
visitPropertySetterDescriptor(expected.setter, context.nextLevel(actual.setter))
visitReceiverParameterDescriptor(expected.extensionReceiverParameter, context.nextLevel(actual.extensionReceiverParameter))
visitReceiverParameterDescriptor(expected.dispatchReceiverParameter, context.nextLevel(actual.dispatchReceiverParameter))
visitTypeParameters(expected.typeParameters, actual.typeParameters, context.nextLevel("Property type parameters"))
}
override fun visitPropertyGetterDescriptor(expected: PropertyGetterDescriptor?, context: Context) {
val actual = context.getActualAs<PropertyGetterDescriptor?>()
if (expected === actual) return
check(actual != null && expected != null)
visitAnnotations(expected.annotations, actual.annotations, context.nextLevel("Property getter annotations"))
context.assertFieldsEqual(expected::isDefault, actual::isDefault)
context.assertFieldsEqual(expected::isExternal, actual::isExternal)
context.assertFieldsEqual(expected::isInline, actual::isInline)
}
override fun visitPropertySetterDescriptor(expected: PropertySetterDescriptor?, context: Context) {
val actual = context.getActualAs<PropertySetterDescriptor?>()
if (expected === actual) return
check(actual != null && expected != null)
visitAnnotations(expected.annotations, actual.annotations, context.nextLevel("Property setter annotations"))
context.assertFieldsEqual(expected::isDefault, actual::isDefault)
context.assertFieldsEqual(expected::isExternal, actual::isExternal)
context.assertFieldsEqual(expected::isInline, actual::isInline)
context.assertFieldsEqual(expected::getVisibility, actual::getVisibility)
visitAnnotations(
expected.valueParameters.single().annotations,
actual.valueParameters.single().annotations,
context.nextLevel("Property setter value parameter annotations")
)
}
override fun visitReceiverParameterDescriptor(expected: ReceiverParameterDescriptor?, context: Context) {
val actual = context.getActualAs<ReceiverParameterDescriptor?>()
if (expected === actual) return
check(actual != null && expected != null)
visitType(expected.type, actual.type, context.nextLevel("Receiver parameter type"))
visitAnnotations(expected.annotations, actual.annotations, context.nextLevel("Receiver parameter annotations"))
}
private fun visitAnnotations(expected: Annotations?, actual: Annotations?, context: Context) {
if (expected === actual || (expected?.isEmpty() != false && actual?.isEmpty() != false)) return
fun AnnotationDescriptor.getMandatoryFqName(): FqName = fqName ?: context.fail("No FQ name for annotation $this")
val expectedAnnotations: Map<FqName, AnnotationDescriptor> = expected?.associateBy { it.getMandatoryFqName() }.orEmpty()
val actualAnnotations: Map<FqName, AnnotationDescriptor> = actual?.associateBy { it.getMandatoryFqName() }.orEmpty()
context.assertSetsEqual(expectedAnnotations.keys, actualAnnotations.keys, "annotation FQ names")
for (annotationFqName in expectedAnnotations.keys) {
val expectedAnnotation = expectedAnnotations.getValue(annotationFqName)
val actualAnnotation = actualAnnotations.getValue(annotationFqName)
visitAnnotation(expectedAnnotation, actualAnnotation, context.nextLevel("Annotation $annotationFqName"))
}
}
private fun visitAnnotation(expected: AnnotationDescriptor, actual: AnnotationDescriptor, context: Context) {
visitType(expected.type, actual.type, context.nextLevel("annotation type"))
val expectedValueArguments: Map<Name, ConstantValue<*>> = expected.allValueArguments
val actualValueArguments: Map<Name, ConstantValue<*>> = actual.allValueArguments
context.assertSetsEqual(expectedValueArguments.keys, actualValueArguments.keys, "annotation value argument names")
for (name in expectedValueArguments.keys) {
val expectedValueArgument = expectedValueArguments.getValue(name)
checkConstantSupportedInCommonization(
constantValue = expectedValueArgument,
constantName = name,
owner = expected,
allowAnnotationValues = true,
onError = { context.fail(it) }
)
val actualValueArgument = actualValueArguments.getValue(name)
checkConstantSupportedInCommonization(
constantValue = actualValueArgument,
constantName = name,
owner = actual,
allowAnnotationValues = true,
onError = { context.fail(it) }
)
context.assertEquals(expectedValueArgument::class, actualValueArgument::class, "annotation value argument value")
if (expectedValueArgument is AnnotationValue && actualValueArgument is AnnotationValue) {
context.assertEquals(
expectedValueArgument.value.fqName,
actualValueArgument.value.fqName,
"nested annotation FQ name"
)
visitAnnotation(
expectedValueArgument.value,
actualValueArgument.value,
context.nextLevel("Annotation ${expectedValueArgument.value.fqName}")
)
} else {
context.assertEquals(expectedValueArgument.value, actualValueArgument.value, "annotation value argument value")
}
}
}
private fun visitType(expected: KotlinType?, actual: KotlinType?, context: Context) {
if (expected === actual) return
check(actual != null && expected != null)
val expectedUnwrapped = expected.unwrap()
val actualUnwrapped = actual.unwrap()
if (expectedUnwrapped === actualUnwrapped) return
val expectedAbbreviated = expectedUnwrapped as? AbbreviatedType
val actualAbbreviated = actualUnwrapped as? AbbreviatedType
context.assertEquals(!expectedAbbreviated.isNull(), !actualAbbreviated.isNull(), "type is abbreviated")
if (expectedAbbreviated != null && actualAbbreviated != null) {
visitType(
expectedAbbreviated.abbreviation,
actualAbbreviated.abbreviation,
context.nextLevel("Abbreviation type")
)
visitType(
extractExpandedType(expectedAbbreviated),
extractExpandedType(actualAbbreviated),
context.nextLevel("Expanded type")
)
} else {
visitAnnotations(
expectedUnwrapped.annotations,
actualUnwrapped.annotations,
context.nextLevel("Type annotations")
)
val expectedId = expectedUnwrapped.declarationDescriptor.run { classId?.asString() ?: name.asString() }
val actualId = actualUnwrapped.declarationDescriptor.run { classId?.asString() ?: name.asString() }
context.assertEquals(expectedId, actualId, "type class ID / name")
val expectedArguments = expectedUnwrapped.arguments
val actualArguments = actualUnwrapped.arguments
context.assertEquals(expectedArguments.size, actualArguments.size, "size of type arguments list")
expectedArguments.forEachIndexed { index, expectedArgument ->
val actualArgument = actualArguments[index]
context.assertFieldsEqual(expectedArgument::isStarProjection, actualArgument::isStarProjection)
if (!expectedArgument.isStarProjection) {
context.assertFieldsEqual(expectedArgument::getProjectionKind, actualArgument::getProjectionKind)
visitType(expectedArgument.type, actualArgument.type, context.nextLevel("Type argument type"))
}
}
}
}
private fun <T> Context.assertEquals(expected: T?, actual: T?, subject: String) {
if (expected != actual)
fail(
buildString {
append("Comparing <$subject>:\n")
append("\"$expected\" is not equal to \"$actual\"\n")
}
)
}
private fun <T> Context.assertFieldsEqual(expected: KCallable<T>, actual: KCallable<T>) {
val expectedValue = expected.call()
val actualValue = actual.call()
assertEquals(expectedValue, actualValue, "fields \"$expected\"")
}
private fun <T> Context.assertSetsEqual(expected: Set<T>, actual: Set<T>, subject: String) {
val expectedMinusActual = expected.subtract(actual)
val actualMinusExpected = actual.subtract(expected)
if (expectedMinusActual.isNotEmpty() || actualMinusExpected.isNotEmpty())
fail(
buildString {
appendLine("Comparing $subject:")
appendLine("$expected is not equal to $actual")
appendLine("Expected size: ${expected.size}")
appendLine("Actual size: ${actual.size}")
appendLine("Expected minus actual: $expectedMinusActual")
appendLine("Actual minus expected: $actualMinusExpected")
}
)
}
private fun Context.fail(message: String): Nothing {
kotlin.test.fail(
buildString {
if (message.isNotEmpty()) {
if (message.last() != '\n') appendLine(message) else append(message)
}
append(this@fail.toString())
}
)
}
override fun visitPackageViewDescriptor(expected: PackageViewDescriptor, context: Context) =
fail("Comparison of package views not supported")
override fun visitPackageFragmentDescriptor(expected: PackageFragmentDescriptor, context: Context) =
fail("Comparison of package fragments not supported")
override fun visitScriptDescriptor(expected: ScriptDescriptor, context: Context) =
fail("Comparison of script descriptors not supported")
override fun visitVariableDescriptor(expected: VariableDescriptor, context: Context) =
fail("Comparison of variables not supported")
}
@@ -5,15 +5,16 @@
package org.jetbrains.kotlin.descriptors.commonizer.utils
import org.jetbrains.kotlin.descriptors.*
import kotlinx.metadata.klib.KlibModuleMetadata
import org.jetbrains.kotlin.descriptors.commonizer.CommonizerResult
import org.jetbrains.kotlin.resolve.scopes.MemberScope
import org.jetbrains.kotlin.test.util.DescriptorValidator.*
import org.jetbrains.kotlin.types.ErrorUtils
import org.jetbrains.kotlin.descriptors.commonizer.CommonizerTarget
import org.jetbrains.kotlin.descriptors.commonizer.metadata.utils.MetadataDeclarationsComparator
import org.jetbrains.kotlin.descriptors.commonizer.metadata.utils.MetadataDeclarationsComparator.Result
import org.jetbrains.kotlin.descriptors.commonizer.metadata.utils.SerializedMetadataLibraryProvider
import org.jetbrains.kotlin.library.SerializedMetadata
import java.io.File
import kotlin.contracts.ExperimentalContracts
import kotlin.contracts.contract
import kotlin.test.assertFalse
import kotlin.test.fail
fun assertIsDirectory(file: File) {
@@ -32,47 +33,24 @@ fun assertCommonizationPerformed(result: CommonizerResult) {
}
@ExperimentalContracts
fun assertModulesAreEqual(expected: ModuleDescriptor, actual: ModuleDescriptor, designatorMessage: String) {
val visitor = ComparingDeclarationsVisitor(designatorMessage)
val context = visitor.Context(actual)
fun assertModulesAreEqual(expected: SerializedMetadata, actual: SerializedMetadata, target: CommonizerTarget) {
val expectedModule = with(expected) { KlibModuleMetadata.read(SerializedMetadataLibraryProvider(module, fragments, fragmentNames)) }
val actualModule = with(actual) { KlibModuleMetadata.read(SerializedMetadataLibraryProvider(module, fragments, fragmentNames)) }
expected.accept(visitor, context)
}
fun assertValidModule(module: ModuleDescriptor) = validate(
object : ValidationVisitor() {
override fun validateScope(scopeOwner: DeclarationDescriptor?, scope: MemberScope, collector: DiagnosticCollector) = Unit
override fun visitModuleDeclaration(descriptor: ModuleDescriptor, collector: DiagnosticCollector): Boolean {
assertValid(descriptor)
return super.visitModuleDeclaration(descriptor, collector)
}
override fun visitClassDescriptor(descriptor: ClassDescriptor, collector: DiagnosticCollector): Boolean {
assertValid(descriptor)
return super.visitClassDescriptor(descriptor, collector)
}
override fun visitFunctionDescriptor(descriptor: FunctionDescriptor, collector: DiagnosticCollector): Boolean {
assertValid(descriptor)
return super.visitFunctionDescriptor(descriptor, collector)
}
override fun visitPropertyDescriptor(descriptor: PropertyDescriptor, collector: DiagnosticCollector): Boolean {
assertValid(descriptor)
return super.visitPropertyDescriptor(descriptor, collector)
}
override fun visitConstructorDescriptor(constructorDescriptor: ConstructorDescriptor, collector: DiagnosticCollector): Boolean {
assertValid(constructorDescriptor)
return super.visitConstructorDescriptor(constructorDescriptor, collector)
}
},
module
)
@Suppress("NOTHING_TO_INLINE")
private inline fun assertValid(descriptor: DeclarationDescriptor) = when (descriptor) {
is ModuleDescriptor -> descriptor.assertValid()
else -> assertFalse(ErrorUtils.isError(descriptor), "$descriptor is error")
when (val result = MetadataDeclarationsComparator().compare(expectedModule, actualModule)) {
is Result.Success -> Unit
is Result.Failure -> {
val mismatches = result.mismatches.sortedBy { it::class.java.simpleName + "_" + it.kind }
val digitCount = mismatches.size.toString().length
val failureMessage = buildString {
appendLine("${mismatches.size} mismatches found while comparing module ${expectedModule.name} and ${actualModule.name} for target ${target.prettyName}:")
mismatches.forEachIndexed { index, mismatch ->
appendLine((index + 1).toString().padStart(digitCount, ' ') + ". " + mismatch)
}
}
fail(failureMessage)
}
}
}