[PL][tests] W/a for broken @Deprecated annotations in existing c-interop KLIBs
^KT-59030
This commit is contained in:
committed by
Space Team
parent
280caf003f
commit
502cbe8b17
@@ -46,6 +46,7 @@ versions.kotlinx-coroutines-core-jvm=1.5.0
|
|||||||
versions.kotlinx-coroutines-core=1.5.0
|
versions.kotlinx-coroutines-core=1.5.0
|
||||||
|
|
||||||
versions.kotlinx-metadata-jvm=0.6.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.kotlinx-serialization-json=1.3.3
|
||||||
versions.ktor-network=1.4.0
|
versions.ktor-network=1.4.0
|
||||||
versions.ktor-utils=1.4.0
|
versions.ktor-utils=1.4.0
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ dependencies {
|
|||||||
testImplementation(project(":native:kotlin-native-utils"))
|
testImplementation(project(":native:kotlin-native-utils"))
|
||||||
testImplementation(project(":native:executors"))
|
testImplementation(project(":native:executors"))
|
||||||
testApiJUnit5()
|
testApiJUnit5()
|
||||||
|
testImplementation(commonDependency("org.jetbrains.kotlinx", "kotlinx-metadata-klib"))
|
||||||
testImplementation(commonDependency("org.jetbrains.kotlinx", "kotlinx-coroutines-core")) { isTransitive = false }
|
testImplementation(commonDependency("org.jetbrains.kotlinx", "kotlinx-coroutines-core")) { isTransitive = false }
|
||||||
|
|
||||||
testRuntimeOnly(commonDependency("org.jetbrains.intellij.deps:trove4j"))
|
testRuntimeOnly(commonDependency("org.jetbrains.intellij.deps:trove4j"))
|
||||||
|
|||||||
+165
@@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
+63
-4
@@ -20,8 +20,6 @@ import org.jetbrains.kotlin.test.InTextDirectivesUtils
|
|||||||
import org.junit.jupiter.api.Assumptions
|
import org.junit.jupiter.api.Assumptions
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
private val DEFAULT_EXTRAS = TestCase.WithTestRunnerExtras(TestRunnerType.DEFAULT)
|
|
||||||
|
|
||||||
internal abstract class ArtifactBuilder<T>(
|
internal abstract class ArtifactBuilder<T>(
|
||||||
val test: AbstractNativeSimpleTest,
|
val test: AbstractNativeSimpleTest,
|
||||||
val rootDir: File,
|
val rootDir: File,
|
||||||
@@ -100,6 +98,7 @@ internal class ExecutableBuilder(
|
|||||||
}
|
}
|
||||||
|
|
||||||
internal val AbstractNativeSimpleTest.buildDir: File get() = testRunSettings.get<Binaries>().testBinariesDir
|
internal val AbstractNativeSimpleTest.buildDir: File get() = testRunSettings.get<Binaries>().testBinariesDir
|
||||||
|
internal val AbstractNativeSimpleTest.targets: KotlinNativeTargets get() = testRunSettings.get()
|
||||||
|
|
||||||
internal fun TestCompilationArtifact.KLIB.asLibraryDependency() =
|
internal fun TestCompilationArtifact.KLIB.asLibraryDependency() =
|
||||||
ExistingDependency(this, TestCompilationDependencyType.Library)
|
ExistingDependency(this, TestCompilationDependencyType.Library)
|
||||||
@@ -130,6 +129,22 @@ internal fun AbstractNativeSimpleTest.compileToLibrary(
|
|||||||
return compilationResult.resultingArtifact
|
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(
|
internal class CompiledExecutable(
|
||||||
val testCase: TestCase,
|
val testCase: TestCase,
|
||||||
val compilationResult: TestCompilationResult.Success<out TestCompilationArtifact.Executable>
|
val compilationResult: TestCompilationResult.Success<out TestCompilationArtifact.Executable>
|
||||||
@@ -191,7 +206,51 @@ internal fun AbstractNativeSimpleTest.generateTestCaseWithSingleModule(
|
|||||||
freeCompilerArgs = freeCompilerArgs,
|
freeCompilerArgs = freeCompilerArgs,
|
||||||
nominalPackageName = PackageName.EMPTY,
|
nominalPackageName = PackageName.EMPTY,
|
||||||
checks = TestRunChecks.Default(testRunSettings.get<Timeouts>().executionTimeout),
|
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 {
|
).apply {
|
||||||
initialize(null, null)
|
initialize(null, null)
|
||||||
}
|
}
|
||||||
@@ -220,7 +279,7 @@ private fun AbstractNativeSimpleTest.compileToExecutable(
|
|||||||
settings = testRunSettings,
|
settings = testRunSettings,
|
||||||
freeCompilerArgs = testCase.freeCompilerArgs,
|
freeCompilerArgs = testCase.freeCompilerArgs,
|
||||||
sourceModules = testCase.modules,
|
sourceModules = testCase.modules,
|
||||||
extras = DEFAULT_EXTRAS,
|
extras = testCase.extras,
|
||||||
dependencies = dependencies,
|
dependencies = dependencies,
|
||||||
expectedArtifact = getExecutableArtifact()
|
expectedArtifact = getExecutableArtifact()
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user