[PL][tests] W/a for broken @Deprecated annotations in existing c-interop KLIBs

^KT-59030
This commit is contained in:
Dmitriy Dolovov
2023-06-05 11:14:21 +02:00
committed by Space Team
parent 280caf003f
commit 502cbe8b17
4 changed files with 230 additions and 4 deletions
+1
View File
@@ -46,6 +46,7 @@ versions.kotlinx-coroutines-core-jvm=1.5.0
versions.kotlinx-coroutines-core=1.5.0
versions.kotlinx-metadata-jvm=0.6.0
versions.kotlinx-metadata-klib=0.0.1-dev-10
versions.kotlinx-serialization-json=1.3.3
versions.ktor-network=1.4.0
versions.ktor-utils=1.4.0
+1
View File
@@ -21,6 +21,7 @@ dependencies {
testImplementation(project(":native:kotlin-native-utils"))
testImplementation(project(":native:executors"))
testApiJUnit5()
testImplementation(commonDependency("org.jetbrains.kotlinx", "kotlinx-metadata-klib"))
testImplementation(commonDependency("org.jetbrains.kotlinx", "kotlinx-coroutines-core")) { isTransitive = false }
testRuntimeOnly(commonDependency("org.jetbrains.intellij.deps:trove4j"))
@@ -0,0 +1,165 @@
/*
* Copyright 2010-2023 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.konan.blackboxtest
import kotlinx.metadata.KmAnnotation
import kotlinx.metadata.KmAnnotationArgument
import kotlinx.metadata.KmClass
import kotlinx.metadata.KmDeclarationContainer
import kotlinx.metadata.klib.KlibModuleMetadata
import kotlinx.metadata.klib.annotations
import org.jetbrains.kotlin.backend.common.serialization.*
import org.jetbrains.kotlin.backend.common.serialization.proto.*
import org.jetbrains.kotlin.backend.common.serialization.proto.IrDeclaration.DeclaratorCase.*
import org.jetbrains.kotlin.konan.blackboxtest.support.EnforcedHostTarget
import org.jetbrains.kotlin.konan.blackboxtest.support.TestCase
import org.jetbrains.kotlin.konan.blackboxtest.support.TestCompilerArgs
import org.jetbrains.kotlin.konan.blackboxtest.support.TestKind
import org.jetbrains.kotlin.konan.blackboxtest.support.compilation.TestCompilationArtifact
import org.jetbrains.kotlin.konan.blackboxtest.support.compilation.TestCompilationResult.Companion.assertSuccess
import org.jetbrains.kotlin.konan.blackboxtest.support.group.UsePartialLinkage
import org.jetbrains.kotlin.konan.blackboxtest.support.settings.CacheMode
import org.jetbrains.kotlin.konan.blackboxtest.support.settings.CacheMode.WithStaticCache
import org.jetbrains.kotlin.konan.file.unzipTo
import org.jetbrains.kotlin.konan.file.zipDirAs
import org.jetbrains.kotlin.library.*
import org.jetbrains.kotlin.library.impl.KotlinLibraryLayoutForWriter
import org.jetbrains.kotlin.library.impl.MetadataWriterImpl
import org.junit.jupiter.api.Assumptions.assumeTrue
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Tag
import org.junit.jupiter.api.Test
import java.io.File
import org.jetbrains.kotlin.konan.file.File as KFile
// See KT-59030.
@Tag("partial-linkage")
@EnforcedHostTarget
@UsePartialLinkage(UsePartialLinkage.Mode.ENABLED_WITH_ERROR)
class KT59030WorkaroundTest : AbstractNativeSimpleTest() {
// This test relies on static caches. So, run it along with other PL tests but only when caches are enabled.
@BeforeEach
fun assumeOnlyStaticCacheEverywhere() {
val cacheMode = testRunSettings.get<CacheMode>()
assumeTrue(cacheMode is WithStaticCache)
assumeTrue(cacheMode.useStaticCacheForUserLibraries)
}
@Test
fun kt59030() {
val library = cinteropToLibrary(
targets = targets,
defFile = File(DEF_FILE_PATH),
outputDir = buildDir,
freeCompilerArgs = TestCompilerArgs.EMPTY
).assertSuccess().resultingArtifact
spoilDeprecatedAnnotationsInLibrary(library)
compileToExecutable(
generateTestCaseWithSingleFile(
sourceFile = File(MAIN_FILE_PATH),
testKind = TestKind.STANDALONE_NO_TR,
extras = TestCase.NoTestRunnerExtras("main")
),
library.asLibraryDependency()
).assertSuccess()
}
private fun spoilDeprecatedAnnotationsInLibrary(klib: TestCompilationArtifact.KLIB) {
// Make a backup.
val oldLibraryFile = KFile(with(klib.klibFile) { parentFile.newDir("__backup__").resolve(name).path })
val newLibraryFile = KFile(klib.klibFile.path)
newLibraryFile.renameTo(oldLibraryFile)
// Unzip the new library.
val newLibraryTmpDir = KFile(newLibraryFile.path + ".tmp")
oldLibraryFile.unzipTo(newLibraryTmpDir)
// Read the library.
val oldLibrary = resolveSingleFileKlib(oldLibraryFile, strategy = ToolingSingleFileKlibResolveStrategy)
val newLibraryLayout = KotlinLibraryLayoutForWriter(newLibraryFile, newLibraryTmpDir)
// Patch the library.
spoilDeprecatedAnnotationsInMetadata(oldLibrary, newLibraryLayout)
// Zip and clean-up.
newLibraryTmpDir.zipDirAs(newLibraryFile)
newLibraryTmpDir.deleteRecursively()
}
companion object {
private const val TEST_DATA_DIR = "kotlin-native/backend.native/tests/interop/basics"
const val DEF_FILE_PATH = "${TEST_DATA_DIR}/cvectors.def"
const val MAIN_FILE_PATH = "${TEST_DATA_DIR}/vectors.kt"
private const val DEPRECATED_CLASS_NAME = "kotlin/Deprecated"
private const val REPLACE_WITH_ARG = "replaceWith"
private const val EXPRESSION_ARG = "expression"
private fun File.newDir(name: String): File = resolve(name).apply { mkdirs() }
private fun spoilDeprecatedAnnotationsInMetadata(
oldLibrary: KotlinLibrary,
newLibraryLayout: KotlinLibraryLayoutForWriter,
) {
// Read the metadata.
val moduleMetadata = KlibModuleMetadata.read(
object : KlibModuleMetadata.MetadataLibraryProvider {
override val moduleHeaderData get() = oldLibrary.moduleHeaderData
override fun packageMetadataParts(fqName: String) = oldLibrary.packageMetadataParts(fqName)
override fun packageMetadata(fqName: String, partName: String) = oldLibrary.packageMetadata(fqName, partName)
}
)
// Patch the metadata.
moduleMetadata.fragments.forEach { fragment ->
fragment.pkg?.let(this::spoilDeprecatedAnnotationsInMetadataContainer)
fragment.classes.forEach(this::spoilDeprecatedAnnotationsInMetadataClass)
}
// Write back the metadata.
val serializedMetadata = with(moduleMetadata.write()) {
SerializedMetadata(module = header, fragments, fragmentNames)
}
newLibraryLayout.metadataDir.deleteRecursively() // Drop old metadata.
MetadataWriterImpl(newLibraryLayout).addMetadata(serializedMetadata) // Write new metadata.
}
private fun spoilDeprecatedAnnotationsInMetadataContainer(container: KmDeclarationContainer) {
container.functions.forEach { spoilDeprecatedAnnotationsInMetadataAnnotationList(it.annotations) }
container.properties.forEach { spoilDeprecatedAnnotationsInMetadataAnnotationList(it.annotations) }
container.typeAliases.forEach { spoilDeprecatedAnnotationsInMetadataAnnotationList(it.annotations) }
}
private fun spoilDeprecatedAnnotationsInMetadataClass(clazz: KmClass) {
spoilDeprecatedAnnotationsInMetadataAnnotationList(clazz.annotations)
clazz.constructors.forEach { spoilDeprecatedAnnotationsInMetadataAnnotationList(it.annotations) }
spoilDeprecatedAnnotationsInMetadataContainer(clazz)
}
private fun spoilDeprecatedAnnotationsInMetadataAnnotationList(annotations: MutableList<KmAnnotation>) {
annotations.replaceAll { annotation ->
if (annotation.className == DEPRECATED_CLASS_NAME) spoilDeprecatedAnnotationInMetadata(annotation) else annotation
}
}
private fun spoilDeprecatedAnnotationInMetadata(deprecated: KmAnnotation): KmAnnotation =
deprecated.copy(
arguments = deprecated.arguments.mapValues { (argName, argValue) ->
if (argName == REPLACE_WITH_ARG) spoilReplaceWithAnnotationInMetadata(argValue.unwrap()).wrap() else argValue
}
)
private fun spoilReplaceWithAnnotationInMetadata(replaceWith: KmAnnotation): KmAnnotation =
replaceWith.copy(
arguments = replaceWith.arguments.filterKeys { argName -> argName != EXPRESSION_ARG }
)
private fun KmAnnotationArgument<*>.unwrap(): KmAnnotation = (this as KmAnnotationArgument.AnnotationValue).value
private fun KmAnnotation.wrap(): KmAnnotationArgument.AnnotationValue = KmAnnotationArgument.AnnotationValue(this)
}
}
@@ -20,8 +20,6 @@ import org.jetbrains.kotlin.test.InTextDirectivesUtils
import org.junit.jupiter.api.Assumptions
import java.io.File
private val DEFAULT_EXTRAS = TestCase.WithTestRunnerExtras(TestRunnerType.DEFAULT)
internal abstract class ArtifactBuilder<T>(
val test: AbstractNativeSimpleTest,
val rootDir: File,
@@ -100,6 +98,7 @@ internal class ExecutableBuilder(
}
internal val AbstractNativeSimpleTest.buildDir: File get() = testRunSettings.get<Binaries>().testBinariesDir
internal val AbstractNativeSimpleTest.targets: KotlinNativeTargets get() = testRunSettings.get()
internal fun TestCompilationArtifact.KLIB.asLibraryDependency() =
ExistingDependency(this, TestCompilationDependencyType.Library)
@@ -130,6 +129,22 @@ internal fun AbstractNativeSimpleTest.compileToLibrary(
return compilationResult.resultingArtifact
}
internal fun AbstractNativeSimpleTest.cinteropToLibrary(
targets: KotlinNativeTargets,
defFile: File,
outputDir: File,
freeCompilerArgs: TestCompilerArgs
): TestCompilationResult<out TestCompilationArtifact.KLIB> {
val testCase: TestCase = generateCInteropTestCaseFromSingleDefFile(defFile, freeCompilerArgs)
return CInteropCompilation(
classLoader = testRunSettings.get(),
targets = targets,
freeCompilerArgs = freeCompilerArgs,
defFile = testCase.modules.single().files.single().location,
expectedArtifact = getLibraryArtifact(testCase, outputDir)
).result
}
internal class CompiledExecutable(
val testCase: TestCase,
val compilationResult: TestCompilationResult.Success<out TestCompilationArtifact.Executable>
@@ -191,7 +206,51 @@ internal fun AbstractNativeSimpleTest.generateTestCaseWithSingleModule(
freeCompilerArgs = freeCompilerArgs,
nominalPackageName = PackageName.EMPTY,
checks = TestRunChecks.Default(testRunSettings.get<Timeouts>().executionTimeout),
extras = DEFAULT_EXTRAS
extras = TestCase.WithTestRunnerExtras(TestRunnerType.DEFAULT)
).apply {
initialize(null, null)
}
}
internal fun AbstractNativeSimpleTest.generateTestCaseWithSingleFile(
sourceFile: File,
freeCompilerArgs: TestCompilerArgs = TestCompilerArgs.EMPTY,
testKind: TestKind = TestKind.STANDALONE,
extras: TestCase.Extras = TestCase.WithTestRunnerExtras(TestRunnerType.DEFAULT)
): TestCase {
val moduleName: String = sourceFile.name ?: LAUNCHER_MODULE_NAME
val module = TestModule.Exclusive(moduleName, emptySet(), emptySet(), emptySet())
module.files += TestFile.createCommitted(sourceFile, module)
return TestCase(
id = TestCaseId.Named(moduleName),
kind = testKind,
modules = setOf(module),
freeCompilerArgs = freeCompilerArgs,
nominalPackageName = PackageName.EMPTY,
checks = TestRunChecks.Default(testRunSettings.get<Timeouts>().executionTimeout),
extras = extras
).apply {
initialize(null, null)
}
}
internal fun AbstractNativeSimpleTest.generateCInteropTestCaseFromSingleDefFile(
defFile: File,
freeCompilerArgs: TestCompilerArgs,
): TestCase {
val moduleName: String = defFile.name
val module = TestModule.Exclusive(moduleName, emptySet(), emptySet(), emptySet())
module.files += TestFile.createCommitted(defFile, module)
return TestCase(
id = TestCaseId.Named(moduleName),
kind = TestKind.STANDALONE,
modules = setOf(module),
freeCompilerArgs = freeCompilerArgs,
nominalPackageName = PackageName.EMPTY,
checks = TestRunChecks.Default(testRunSettings.get<Timeouts>().executionTimeout),
extras = TestCase.WithTestRunnerExtras(TestRunnerType.DEFAULT)
).apply {
initialize(null, null)
}
@@ -220,7 +279,7 @@ private fun AbstractNativeSimpleTest.compileToExecutable(
settings = testRunSettings,
freeCompilerArgs = testCase.freeCompilerArgs,
sourceModules = testCase.modules,
extras = DEFAULT_EXTRAS,
extras = testCase.extras,
dependencies = dependencies,
expectedArtifact = getExecutableArtifact()
)