diff --git a/kotlin-native/backend.native/build.gradle b/kotlin-native/backend.native/build.gradle index 6037a41c93f..c879f24c87c 100644 --- a/kotlin-native/backend.native/build.gradle +++ b/kotlin-native/backend.native/build.gradle @@ -189,6 +189,9 @@ dependencies { cli_bcApiElements sourceSets.cli_bc.output testImplementation(project(path: ":compiler:tests-common", configuration: "tests-jar")) + testImplementation(libs.junit.jupiter.api) + testImplementation(libs.junit.jupiter.params) + testRuntimeOnly(libs.junit.jupiter.engine) } classes.dependsOn 'compilerClasses', 'cli_bcClasses' @@ -254,9 +257,10 @@ RepoArtifacts.javadocJar(project) Configure 'test' task */ -TasksKt.projectTest(project) +TasksKt.projectTest(project, JUnitMode.JUnit5) tasks { test { + useJUnitPlatform() dependsOn ':kotlin-native:dist' systemProperty("org.jetbrains.kotlin.native.home", distDir.canonicalPath) workingDir(rootProject.projectDir) diff --git a/kotlin-native/backend.native/functionalTest/src/org/jetbrains/kotlin/backend/konan/testUtils/Fe10ObjCExportHeaderGenerator.kt b/kotlin-native/backend.native/functionalTest/src/org/jetbrains/kotlin/backend/konan/testUtils/Fe10ObjCExportHeaderGenerator.kt index 914d2fcb9bd..eda60e4602a 100644 --- a/kotlin-native/backend.native/functionalTest/src/org/jetbrains/kotlin/backend/konan/testUtils/Fe10ObjCExportHeaderGenerator.kt +++ b/kotlin-native/backend.native/functionalTest/src/org/jetbrains/kotlin/backend/konan/testUtils/Fe10ObjCExportHeaderGenerator.kt @@ -53,8 +53,6 @@ object Fe10ObjCExportHeaderGenerator : AbstractObjCExportHeaderGeneratorTest.Obj } private fun createObjCExportHeaderGenerator(disposable: Disposable, root: File): ObjCExportHeaderGenerator { - val compilerConfiguration = createCompilerConfiguration() - val mapper = ObjCExportMapper( unitSuspendFunctionExport = UnitSuspendFunctionObjCExport.DEFAULT ) @@ -69,106 +67,22 @@ object Fe10ObjCExportHeaderGenerator : AbstractObjCExportHeaderGeneratorTest.Obj override fun getAdditionalPrefix(module: ModuleDescriptor): String? = null override val objcGenerics: Boolean = true } - - ) - - val environment: KotlinCoreEnvironment = KotlinCoreEnvironment.createForTests( - parentDisposable = disposable, - initialConfiguration = compilerConfiguration, - extensionConfigs = EnvironmentConfigFiles.METADATA_CONFIG_FILES ) + val environment: KotlinCoreEnvironment = createKotlinCoreEnvironment(disposable) val phaseContext = BasicPhaseContext( - KonanConfig(environment.project, compilerConfiguration) + KonanConfig(environment.project, environment.configuration) ) val kotlinFiles = root.walkTopDown().filter { it.isFile }.filter { it.extension == "kt" }.toList() return ObjCExportHeaderGeneratorImpl( context = phaseContext, - moduleDescriptors = listOf(getModuleDescriptor(environment, kotlinFiles)), + moduleDescriptors = listOf(createModuleDescriptor(environment, kotlinFiles)), mapper = mapper, namer = namer, problemCollector = ObjCExportProblemCollector.SILENT, objcGenerics = true ) } - - private fun getModuleDescriptor( - environment: KotlinCoreEnvironment, kotlinFiles: List - ): ModuleDescriptor { - val psiFactory = KtPsiFactory(environment.project) - val kotlinPsiFiles = kotlinFiles.map { file -> psiFactory.createFile(file.name, KtTestUtil.doLoadFile(file)) } - - val analysisResult = CommonResolverForModuleFactory.analyzeFiles( - files = kotlinPsiFiles, - moduleName = Name.special(""), - dependOnBuiltIns = true, - languageVersionSettings = environment.configuration.languageVersionSettings, - targetPlatform = CommonPlatforms.defaultCommonPlatform, - targetEnvironment = CompilerEnvironment, - dependenciesContainer = DependenciesContainerImpl, - ) { content -> - environment.createPackagePartProvider(content.moduleContentScope) - } - - return analysisResult.moduleDescriptor - } - - - private object DependenciesContainerImpl : CommonDependenciesContainer { - override val moduleInfos: List get() = listOf(DefaultBuiltInsModuleInfo, KotlinNativeStdlibModuleInfo) - override val friendModuleInfos: List get() = emptyList() - override val refinesModuleInfos: List get() = emptyList() - override fun registerDependencyForAllModules(moduleInfo: ModuleInfo, descriptorForModule: ModuleDescriptorImpl) = Unit - override fun packageFragmentProviderForModuleInfo(moduleInfo: ModuleInfo): PackageFragmentProvider? = null - - private val stdlibModuleDescriptor = KlibFactories.DefaultDeserializedDescriptorFactory.createDescriptor( - library = resolveSingleFileKlib(org.jetbrains.kotlin.konan.file.File("$konanHomePath/klib/common/stdlib")), - languageVersionSettings = createLanguageVersionSettings(), - builtIns = DefaultBuiltIns.Instance, - storageManager = LockBasedStorageManager.NO_LOCKS, - packageAccessHandler = null - ).also { it.setDependencies(it) } - - override fun moduleDescriptorForModuleInfo(moduleInfo: ModuleInfo): ModuleDescriptor { - if (moduleInfo == DefaultBuiltInsModuleInfo) return DefaultBuiltIns.Instance.builtInsModule - if (moduleInfo == KotlinNativeStdlibModuleInfo) return stdlibModuleDescriptor - error("Unknown module info $moduleInfo") - } - } - - private object DefaultBuiltInsModuleInfo : ModuleInfo { - override val name get() = DefaultBuiltIns.Instance.builtInsModule.name - override fun dependencies() = listOf(this) - override fun dependencyOnBuiltIns() = ModuleInfo.DependencyOnBuiltIns.LAST - override val platform get() = NativePlatforms.unspecifiedNativePlatform - override val analyzerServices get() = NativePlatformAnalyzerServices - } - - private object KotlinNativeStdlibModuleInfo : ModuleInfo { - override val analyzerServices: PlatformDependentAnalyzerServices = NativePlatformAnalyzerServices - override val name: Name = Name.special("") - override val platform: TargetPlatform = NativePlatforms.unspecifiedNativePlatform - override fun dependencies(): List = listOf(this) - } - - private fun createCompilerConfiguration(): CompilerConfiguration { - val configuration = KotlinTestUtils.newConfiguration() - configuration.put(KONAN_HOME, konanHomePath) - configuration.put(CLIConfigurationKeys.FLEXIBLE_PHASE_CONFIG, createFlexiblePhaseConfig(K2NativeCompilerArguments())) - configuration.put(CommonConfigurationKeys.LANGUAGE_VERSION_SETTINGS, createLanguageVersionSettings()) - configuration.put(KonanConfigKeys.PRODUCE, CompilerOutputKind.FRAMEWORK) - configuration.put(KonanConfigKeys.AUTO_CACHEABLE_FROM, emptyList()) - configuration.put(KonanConfigKeys.CACHE_DIRECTORIES, emptyList()) - configuration.put(KonanConfigKeys.CACHED_LIBRARIES, emptyMap()) - configuration.put(KonanConfigKeys.FRAMEWORK_IMPORT_HEADERS, emptyList()) - configuration.put(KonanConfigKeys.EXPORT_KDOC, true) - return configuration - } - - private fun createLanguageVersionSettings() = LanguageVersionSettingsImpl( - languageVersion = LanguageVersion.LATEST_STABLE, - apiVersion = ApiVersion.LATEST_STABLE - ) } \ No newline at end of file diff --git a/kotlin-native/backend.native/functionalTest/src/org/jetbrains/kotlin/backend/konan/testUtils/Fe10Utils.kt b/kotlin-native/backend.native/functionalTest/src/org/jetbrains/kotlin/backend/konan/testUtils/Fe10Utils.kt new file mode 100644 index 00000000000..6ca5300ce9f --- /dev/null +++ b/kotlin-native/backend.native/functionalTest/src/org/jetbrains/kotlin/backend/konan/testUtils/Fe10Utils.kt @@ -0,0 +1,140 @@ +/* + * 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.backend.konan.testUtils + +import com.intellij.openapi.Disposable +import org.jetbrains.kotlin.analyzer.ModuleInfo +import org.jetbrains.kotlin.analyzer.common.CommonDependenciesContainer +import org.jetbrains.kotlin.analyzer.common.CommonResolverForModuleFactory +import org.jetbrains.kotlin.backend.konan.KlibFactories +import org.jetbrains.kotlin.backend.konan.KonanConfigKeys +import org.jetbrains.kotlin.builtins.DefaultBuiltIns +import org.jetbrains.kotlin.cli.common.CLIConfigurationKeys +import org.jetbrains.kotlin.cli.common.arguments.K2NativeCompilerArguments +import org.jetbrains.kotlin.cli.common.createFlexiblePhaseConfig +import org.jetbrains.kotlin.cli.jvm.compiler.EnvironmentConfigFiles +import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment +import org.jetbrains.kotlin.config.* +import org.jetbrains.kotlin.descriptors.ModuleDescriptor +import org.jetbrains.kotlin.descriptors.PackageFragmentProvider +import org.jetbrains.kotlin.descriptors.impl.ModuleDescriptorImpl +import org.jetbrains.kotlin.konan.target.CompilerOutputKind +import org.jetbrains.kotlin.library.resolveSingleFileKlib +import org.jetbrains.kotlin.name.Name +import org.jetbrains.kotlin.platform.CommonPlatforms +import org.jetbrains.kotlin.platform.TargetPlatform +import org.jetbrains.kotlin.platform.konan.NativePlatforms +import org.jetbrains.kotlin.psi.KtPsiFactory +import org.jetbrains.kotlin.resolve.CompilerEnvironment +import org.jetbrains.kotlin.resolve.PlatformDependentAnalyzerServices +import org.jetbrains.kotlin.resolve.konan.platform.NativePlatformAnalyzerServices +import org.jetbrains.kotlin.storage.LockBasedStorageManager +import org.jetbrains.kotlin.test.KotlinTestUtils +import org.jetbrains.kotlin.test.util.KtTestUtil +import java.io.File + +fun createModuleDescriptor( + environment: KotlinCoreEnvironment, + tempDir: File, + kotlinSources: List +): ModuleDescriptor { + val testModuleRoot = tempDir.resolve("testModule") + testModuleRoot.mkdirs() + val testSourcesFiles = kotlinSources.mapIndexed { index, kotlinSource -> + testModuleRoot.resolve("TestSources$index.kt").apply { + writeText(kotlinSource) + } + } + return createModuleDescriptor(environment, testSourcesFiles) +} + +fun createModuleDescriptor( + environment: KotlinCoreEnvironment, + kotlinFiles: List, +): ModuleDescriptor { + val psiFactory = KtPsiFactory(environment.project) + val kotlinPsiFiles = kotlinFiles.map { file -> psiFactory.createFile(file.name, KtTestUtil.doLoadFile(file)) } + + val analysisResult = CommonResolverForModuleFactory.analyzeFiles( + files = kotlinPsiFiles, + moduleName = Name.special(""), + dependOnBuiltIns = true, + languageVersionSettings = environment.configuration.languageVersionSettings, + targetPlatform = CommonPlatforms.defaultCommonPlatform, + targetEnvironment = CompilerEnvironment, + dependenciesContainer = DependenciesContainerImpl, + ) { content -> + environment.createPackagePartProvider(content.moduleContentScope) + } + + return analysisResult.moduleDescriptor +} + +fun createKotlinCoreEnvironment( + disposable: Disposable, compilerConfiguration: CompilerConfiguration = createCompilerConfiguration() +): KotlinCoreEnvironment { + return KotlinCoreEnvironment.createForTests( + parentDisposable = disposable, + initialConfiguration = compilerConfiguration, + extensionConfigs = EnvironmentConfigFiles.METADATA_CONFIG_FILES + ) +} + +private fun createCompilerConfiguration(): CompilerConfiguration { + val configuration = KotlinTestUtils.newConfiguration() + configuration.put(CommonConfigurationKeys.LANGUAGE_VERSION_SETTINGS, createLanguageVersionSettings()) + configuration.put(CLIConfigurationKeys.FLEXIBLE_PHASE_CONFIG, createFlexiblePhaseConfig(K2NativeCompilerArguments())) + configuration.put(KonanConfigKeys.KONAN_HOME, konanHomePath) + configuration.put(KonanConfigKeys.PRODUCE, CompilerOutputKind.FRAMEWORK) + configuration.put(KonanConfigKeys.AUTO_CACHEABLE_FROM, emptyList()) + configuration.put(KonanConfigKeys.CACHE_DIRECTORIES, emptyList()) + configuration.put(KonanConfigKeys.CACHED_LIBRARIES, emptyMap()) + configuration.put(KonanConfigKeys.FRAMEWORK_IMPORT_HEADERS, emptyList()) + configuration.put(KonanConfigKeys.EXPORT_KDOC, true) + return configuration +} + +private fun createLanguageVersionSettings() = LanguageVersionSettingsImpl( + languageVersion = LanguageVersion.LATEST_STABLE, + apiVersion = ApiVersion.LATEST_STABLE +) + +private object DependenciesContainerImpl : CommonDependenciesContainer { + override val moduleInfos: List get() = listOf(DefaultBuiltInsModuleInfo, KotlinNativeStdlibModuleInfo) + override val friendModuleInfos: List get() = emptyList() + override val refinesModuleInfos: List get() = emptyList() + override fun registerDependencyForAllModules(moduleInfo: ModuleInfo, descriptorForModule: ModuleDescriptorImpl) = Unit + override fun packageFragmentProviderForModuleInfo(moduleInfo: ModuleInfo): PackageFragmentProvider? = null + + private val stdlibModuleDescriptor = KlibFactories.DefaultDeserializedDescriptorFactory.createDescriptor( + library = resolveSingleFileKlib(org.jetbrains.kotlin.konan.file.File("$konanHomePath/klib/common/stdlib")), + languageVersionSettings = createLanguageVersionSettings(), + builtIns = DefaultBuiltIns.Instance, + storageManager = LockBasedStorageManager.NO_LOCKS, + packageAccessHandler = null + ).also { it.setDependencies(it) } + + override fun moduleDescriptorForModuleInfo(moduleInfo: ModuleInfo): ModuleDescriptor { + if (moduleInfo == DefaultBuiltInsModuleInfo) return DefaultBuiltIns.Instance.builtInsModule + if (moduleInfo == KotlinNativeStdlibModuleInfo) return stdlibModuleDescriptor + error("Unknown module info $moduleInfo") + } +} + +private object DefaultBuiltInsModuleInfo : ModuleInfo { + override val name get() = DefaultBuiltIns.Instance.builtInsModule.name + override fun dependencies() = listOf(this) + override fun dependencyOnBuiltIns() = ModuleInfo.DependencyOnBuiltIns.LAST + override val platform get() = NativePlatforms.unspecifiedNativePlatform + override val analyzerServices get() = NativePlatformAnalyzerServices +} + +private object KotlinNativeStdlibModuleInfo : ModuleInfo { + override val analyzerServices: PlatformDependentAnalyzerServices = NativePlatformAnalyzerServices + override val name: Name = Name.special("") + override val platform: TargetPlatform = NativePlatforms.unspecifiedNativePlatform + override fun dependencies(): List = listOf(this) +} \ No newline at end of file diff --git a/kotlin-native/backend.native/functionalTest/src/org/jetbrains/kotlin/backend/konan/testUtils/InlineSourceTestEnvironment.kt b/kotlin-native/backend.native/functionalTest/src/org/jetbrains/kotlin/backend/konan/testUtils/InlineSourceTestEnvironment.kt new file mode 100644 index 00000000000..d59a1f4f91b --- /dev/null +++ b/kotlin-native/backend.native/functionalTest/src/org/jetbrains/kotlin/backend/konan/testUtils/InlineSourceTestEnvironment.kt @@ -0,0 +1,72 @@ +/* + * 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.backend.konan.testUtils + +import com.intellij.openapi.Disposable +import org.intellij.lang.annotations.Language +import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment +import org.jetbrains.kotlin.descriptors.ModuleDescriptor +import java.io.File + +/** + * Test Environment that enables quick 'inline' creation of [ModuleDescriptor] instances. + * + * # Examples + * ## Test operating on a single Kotlin Source file: + * + * ```kotlin + * class MyTest : InlineSourceTestEnvironment { + * @Test + * fun `test - my unit`() { + * val moduleDescriptor = createModuleDescriptor(""" + * package com.example + * class MyClassUnderTest + * """.trimIndent() + * ) + * + * // my test! + * } + * } + * ``` + * + * ## Test that requires several source files (e.g. multiple packages are involved) + * ```kotlin + * @Test + * fun `test - my unit`() { + * val moduleDescriptor = createModuleDescriptor { + * source("class Foo") + * source(""" + * package com.example + * class Foo + * """) + * } + * } + * ``` + */ +interface InlineSourceTestEnvironment { + val kotlinCoreEnvironment: KotlinCoreEnvironment + val testDisposable: Disposable + val testTempDir: File +} + + +interface InlineSourceCodeCollector { + fun source(@Language("kotlin") sourceCode: String) +} + +fun InlineSourceTestEnvironment.createModuleDescriptor(@Language("kotlin") sourceCode: String): ModuleDescriptor = + createModuleDescriptor(kotlinCoreEnvironment, testTempDir, listOf(sourceCode)) + +fun InlineSourceTestEnvironment.createModuleDescriptor(build: InlineSourceCodeCollector.() -> Unit): ModuleDescriptor { + val sources = mutableListOf() + object : InlineSourceCodeCollector { + override fun source(sourceCode: String) { + sources.add(sourceCode) + } + }.build() + + return createModuleDescriptor(kotlinCoreEnvironment, testTempDir, sources.toList()) +} diff --git a/kotlin-native/backend.native/functionalTest/src/org/jetbrains/kotlin/backend/konan/testUtils/ObjCExportUtils.kt b/kotlin-native/backend.native/functionalTest/src/org/jetbrains/kotlin/backend/konan/testUtils/ObjCExportUtils.kt new file mode 100644 index 00000000000..2911d17380d --- /dev/null +++ b/kotlin-native/backend.native/functionalTest/src/org/jetbrains/kotlin/backend/konan/testUtils/ObjCExportUtils.kt @@ -0,0 +1,52 @@ +/* + * 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.backend.konan.testUtils + +import org.jetbrains.kotlin.backend.konan.UnitSuspendFunctionObjCExport +import org.jetbrains.kotlin.backend.konan.objcexport.ObjCExportMapper +import org.jetbrains.kotlin.backend.konan.objcexport.ObjCExportNamer +import org.jetbrains.kotlin.backend.konan.objcexport.ObjCExportNamerImpl +import org.jetbrains.kotlin.backend.konan.objcexport.ObjCExportProblemCollector +import org.jetbrains.kotlin.builtins.DefaultBuiltIns +import org.jetbrains.kotlin.builtins.KotlinBuiltIns +import org.jetbrains.kotlin.descriptors.ModuleDescriptor +import org.jetbrains.kotlin.name.Name +import org.jetbrains.kotlin.resolve.deprecation.DeprecationResolver + +internal fun createObjCExportNamerConfiguration( + topLevelNamePrefix: String = "", + additionalPrefix: (moduleName: Name) -> String? = { null } +): ObjCExportNamer.Configuration { + return object : ObjCExportNamer.Configuration { + override val topLevelNamePrefix: String get() = topLevelNamePrefix + override fun getAdditionalPrefix(module: ModuleDescriptor): String? = additionalPrefix(module.name) + override val objcGenerics: Boolean = true + } +} + +internal fun createObjCExportMapper( + deprecationResolver: DeprecationResolver? = null, + local: Boolean = false, + unitSuspendFunctionObjCExport: UnitSuspendFunctionObjCExport = UnitSuspendFunctionObjCExport.DEFAULT +): ObjCExportMapper { + return ObjCExportMapper(deprecationResolver, local, unitSuspendFunctionObjCExport) +} + +internal fun createObjCExportNamer( + configuration: ObjCExportNamer.Configuration = createObjCExportNamerConfiguration(), + builtIns: KotlinBuiltIns = DefaultBuiltIns.Instance, + local: Boolean = false, + problemCollector: ObjCExportProblemCollector = ObjCExportProblemCollector.SILENT, + mapper: ObjCExportMapper = createObjCExportMapper(local = local), +): ObjCExportNamer { + return ObjCExportNamerImpl( + builtIns = builtIns, + mapper = mapper, + local = local, + problemCollector = problemCollector, + configuration = configuration + ) +} \ No newline at end of file diff --git a/kotlin-native/backend.native/functionalTest/src/org/jetbrains/kotlin/backend/konan/tests/Fe10ObjCExportHeaderGeneratorTest.kt b/kotlin-native/backend.native/functionalTest/src/org/jetbrains/kotlin/backend/konan/tests/Fe10ObjCExportHeaderGeneratorTest.kt index ef672c9a069..4d63ca06363 100644 --- a/kotlin-native/backend.native/functionalTest/src/org/jetbrains/kotlin/backend/konan/tests/Fe10ObjCExportHeaderGeneratorTest.kt +++ b/kotlin-native/backend.native/functionalTest/src/org/jetbrains/kotlin/backend/konan/tests/Fe10ObjCExportHeaderGeneratorTest.kt @@ -7,7 +7,7 @@ package org.jetbrains.kotlin.backend.konan.tests import org.jetbrains.kotlin.backend.konan.objcexport.ObjCExportHeaderGenerator import org.jetbrains.kotlin.backend.konan.testUtils.AbstractFE10ObjCExportHeaderGeneratorTest -import org.junit.Test +import org.junit.jupiter.api.Test /** * ## Test Scope diff --git a/kotlin-native/backend.native/functionalTest/src/org/jetbrains/kotlin/backend/konan/tests/ObjCExportMapperTest.kt b/kotlin-native/backend.native/functionalTest/src/org/jetbrains/kotlin/backend/konan/tests/ObjCExportMapperTest.kt new file mode 100644 index 00000000000..e366036e4c4 --- /dev/null +++ b/kotlin-native/backend.native/functionalTest/src/org/jetbrains/kotlin/backend/konan/tests/ObjCExportMapperTest.kt @@ -0,0 +1,101 @@ +/* + * 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.backend.konan.tests + +import com.intellij.openapi.util.Disposer +import org.jetbrains.kotlin.backend.konan.KonanConfig +import org.jetbrains.kotlin.backend.konan.driver.BasicPhaseContext +import org.jetbrains.kotlin.backend.konan.objcexport.* +import org.jetbrains.kotlin.backend.konan.testUtils.* +import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment +import org.jetbrains.kotlin.descriptors.findClassAcrossModuleDependencies +import org.jetbrains.kotlin.name.ClassId +import org.jetbrains.kotlin.types.KotlinTypeFactory +import org.jetbrains.kotlin.types.TypeAttributes +import org.jetbrains.kotlin.types.TypeProjectionImpl +import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.io.TempDir +import java.io.File +import kotlin.test.assertEquals +import kotlin.test.assertNotNull +import kotlin.test.assertNull + +/** + * ## Test Scope + * This test shall cover test the [ObjCExportMapper]; + * The test will invoke the type mapping a known type (e.g. List) and check + * the corresponding conversion to [ObjCNonNullReferenceType]. + * + * The test also acts as quick entry point for debugging the [ObjCExportMapper] + */ +class ObjCExportMapperTest : InlineSourceTestEnvironment { + override val testDisposable = Disposer.newDisposable() + override val kotlinCoreEnvironment: KotlinCoreEnvironment = createKotlinCoreEnvironment(testDisposable) + + @TempDir + override lateinit var testTempDir: File + + @AfterEach + fun dispose() { + Disposer.dispose(testDisposable) + } + + /** + * No 'type mapper' expected for a simple Kotlin class like 'Foo'. + * Only well known standard types (List, ...) will get mapped to their corresponding + * ObjC counterpart (NSArray, ...) + */ + @Test + fun `test - simple class`() { + val module = createModuleDescriptor("class Foo") + val foo = module.findClassAcrossModuleDependencies(ClassId.fromString("Foo"))!! + assertNull(createObjCExportMapper().getCustomTypeMapper(foo)) + } + + /** + * Will test ObjC type mapping from List to NSArray * + */ + @Test + fun `test - List of int`() { + /* We are only using built in / stdlib parts, so we can provide empty source code */ + val module = createModuleDescriptor("") + val objcExportMapper = createObjCExportMapper() + val objcExportNamer = createObjCExportNamer(mapper = objcExportMapper) + + val objcExportTranslator = ObjCExportTranslatorImpl( + generator = ObjCExportHeaderGeneratorImpl( + context = BasicPhaseContext( + KonanConfig(kotlinCoreEnvironment.project, kotlinCoreEnvironment.configuration) + ), + moduleDescriptors = listOf(module), + mapper = objcExportMapper, + namer = objcExportNamer, + problemCollector = ObjCExportProblemCollector.SILENT, + objcGenerics = true + ), + mapper = objcExportMapper, + namer = objcExportNamer, + problemCollector = ObjCExportProblemCollector.SILENT, + objcGenerics = true + ) + + val listClassDescriptor = module.findClassAcrossModuleDependencies(ClassId.fromString("kotlin/collections/List"))!! + val intClassDescriptor = module.findClassAcrossModuleDependencies(ClassId.fromString("kotlin/Int"))!! + + /* Represents List */ + val listOfIntType = KotlinTypeFactory.simpleNotNullType(TypeAttributes.Empty, listClassDescriptor, listOf( + TypeProjectionImpl(KotlinTypeFactory.simpleNotNullType(TypeAttributes.Empty, intClassDescriptor, emptyList())) + )) + + val typeMapper = assertNotNull(objcExportMapper.getCustomTypeMapper(listClassDescriptor)) + assertEquals(ClassId.fromString("kotlin/collections/List"), typeMapper.mappedClassId) + val listOfIntMapped = typeMapper.mapType(listOfIntType, objcExportTranslator, objCExportScope = ObjCRootExportScope) + + assertEquals(ObjCClassType("NSArray", typeArguments = listOf(ObjCClassType("Int"))), listOfIntMapped) + assertEquals("NSArray *", listOfIntMapped.toString()) + } +} diff --git a/kotlin-native/backend.native/functionalTest/src/org/jetbrains/kotlin/backend/konan/tests/ObjCExportNamerTest.kt b/kotlin-native/backend.native/functionalTest/src/org/jetbrains/kotlin/backend/konan/tests/ObjCExportNamerTest.kt new file mode 100644 index 00000000000..1deb129d445 --- /dev/null +++ b/kotlin-native/backend.native/functionalTest/src/org/jetbrains/kotlin/backend/konan/tests/ObjCExportNamerTest.kt @@ -0,0 +1,192 @@ +/* + * 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.backend.konan.tests + +import com.intellij.openapi.util.Disposer +import org.jetbrains.kotlin.backend.konan.descriptors.enumEntries +import org.jetbrains.kotlin.backend.konan.objcexport.ObjCExportNamer +import org.jetbrains.kotlin.backend.konan.objcexport.ObjCExportNamer.ClassOrProtocolName +import org.jetbrains.kotlin.backend.konan.objcexport.ObjCExportNamer.PropertyName +import org.jetbrains.kotlin.backend.konan.testUtils.* +import org.jetbrains.kotlin.descriptors.findClassAcrossModuleDependencies +import org.jetbrains.kotlin.name.ClassId +import org.jetbrains.kotlin.name.FqName +import org.jetbrains.kotlin.name.Name +import org.jetbrains.kotlin.psi2ir.findSingleFunction +import org.jetbrains.kotlin.resolve.scopes.findFirstVariable +import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.io.TempDir +import java.io.File +import kotlin.test.assertEquals + +/** + * ## Test Scope + * This test will cover basic cases of the [ObjCExportNamer].
+ * + * The __input__ to this test will be descriptors defined as 'inline source code'. + * (e.g. a String like "class Foo").
+ * + * The __output__ of this test will be result of the request to the [ObjCExportNamer]. + * (e.g. the [ClassOrProtocolName]) + */ +class ObjCExportNamerTest : InlineSourceTestEnvironment { + + override val testDisposable = Disposer.newDisposable() + + override val kotlinCoreEnvironment = createKotlinCoreEnvironment(testDisposable) + + @TempDir + override lateinit var testTempDir: File + + @AfterEach + fun dispose() { + Disposer.dispose(testDisposable) + } + + @Test + fun `test - simple class`() { + val module = createModuleDescriptor("class Foo") + val clazz = module.findClassAcrossModuleDependencies(ClassId.fromString("Foo"))!! + assertEquals( + ClassOrProtocolName("Foo", "Foo"), + createObjCExportNamer().getClassOrProtocolName(clazz) + ) + } + + @Test + fun `test - simple class - with prefix`() { + val module = createModuleDescriptor("class Foo") + val clazz = module.findClassAcrossModuleDependencies(ClassId.fromString("Foo"))!! + assertEquals( + ClassOrProtocolName("Foo", "XFoo"), + createObjCExportNamer(createObjCExportNamerConfiguration(topLevelNamePrefix = "X")).getClassOrProtocolName(clazz) + ) + } + + @Test + fun `test - simple function`() { + val module = createModuleDescriptor("fun foo() = 42") + val foo = module.getPackage(FqName.ROOT).memberScope.findSingleFunction(Name.identifier("foo")) + assertEquals("foo()", createObjCExportNamer().getSwiftName(foo)) + assertEquals("foo", createObjCExportNamer().getSelector(foo)) + } + + @Test + fun `test - function with parameters`() { + val module = createModuleDescriptor("fun foo(a: Int, b: Int) = a + b") + val foo = module.getPackage(FqName.ROOT).memberScope.findSingleFunction(Name.identifier("foo")) + assertEquals("foo(a:b:)", createObjCExportNamer().getSwiftName(foo)) + assertEquals("fooA:b:", createObjCExportNamer().getSelector(foo)) + } + + @Test + fun `test - simple property`() { + val module = createModuleDescriptor("val foo = 42") + val foo = module.getPackage(FqName.ROOT).memberScope.findFirstVariable("foo") { true }!! + assertEquals(PropertyName("foo", "foo"), createObjCExportNamer().getPropertyName(foo)) + } + + @Test + fun `test - simple property - with prefix`() { + val module = createModuleDescriptor("val foo = 42") + val foo = module.getPackage(FqName.ROOT).memberScope.findFirstVariable("foo") { true }!! + assertEquals( + PropertyName("foo", "foo"), + createObjCExportNamer(createObjCExportNamerConfiguration(topLevelNamePrefix = "X")).getPropertyName(foo)) + } + + @Test + fun `test - function inside class`() { + val module = createModuleDescriptor(""" + package bar + class Foo { + fun someFunction(a: Int, b: Int) = a + b + } + """.trimIndent() + ) + + val fooClass = module.findClassAcrossModuleDependencies(ClassId.fromString("bar/Foo"))!! + val someFunction = fooClass.unsubstitutedMemberScope.findSingleFunction(Name.identifier("someFunction")) + assertEquals("someFunction(a:b:)", createObjCExportNamer().getSwiftName(someFunction)) + assertEquals("someFunctionA:b:", createObjCExportNamer().getSelector(someFunction)) + } + + @Test + fun `test - class with ObjCName annotation`() { + val module = createModuleDescriptor(""" + @kotlin.native.ObjCName("ObjCFoo", "SwiftFoo") + class Foo + """.trimIndent()) + + val fooClass = module.findClassAcrossModuleDependencies(ClassId.fromString("Foo"))!! + assertEquals( + ClassOrProtocolName(swiftName = "SwiftFoo", objCName = "ObjCFoo"), + createObjCExportNamer().getClassOrProtocolName(fooClass) + ) + } + + @Test + fun `test - simple parameter`() { + val module = createModuleDescriptor("fun foo(a: Int)") + val foo = module.getPackage(FqName.ROOT).memberScope.findSingleFunction(Name.identifier("foo")) + val parameterA = foo.valueParameters.find { it.name == Name.identifier("a") }!! + assertEquals("a", createObjCExportNamer().getParameterName(parameterA)) + } + + @Test + fun `test - parameter with ObjCName annotation`() { + val module = createModuleDescriptor(""" + import kotlin.native.ObjCName + fun foo(@ObjCName("aObjC", "aSwift") a: Int) + """.trimIndent()) + + val foo = module.getPackage(FqName.ROOT).memberScope.findSingleFunction(Name.identifier("foo")) + val parameterA = foo.valueParameters.find { it.name == Name.identifier("a") }!! + assertEquals("aObjC", createObjCExportNamer().getParameterName(parameterA)) + } + + @Test + fun `test - class mangling`() { + val module = createModuleDescriptor { + source(""" + package a + class Foo + """.trimIndent()) + source(""" + package b + class Foo + """.trimIndent()) + } + + val aFoo = module.findClassAcrossModuleDependencies(ClassId.fromString("a/Foo"))!! + val bFoo = module.findClassAcrossModuleDependencies(ClassId.fromString("b/Foo"))!! + + val namer = createObjCExportNamer() + assertEquals(ClassOrProtocolName("Foo", "Foo"), namer.getClassOrProtocolName(aFoo)) + assertEquals(ClassOrProtocolName("Foo_", "Foo_"), namer.getClassOrProtocolName(bFoo)) + + /* Check caching in namer: Should return same name again */ + assertEquals(ClassOrProtocolName("Foo", "Foo"), namer.getClassOrProtocolName(aFoo)) + } + + @Test + fun `test - simple enum`() { + val module = createModuleDescriptor(""" + enum class Foo { + A, B, C + } + """.trimIndent()) + + val foo = module.findClassAcrossModuleDependencies(ClassId.fromString("Foo"))!! + val fooA = foo.enumEntries.find { it.name == Name.identifier("A") }!! + val namer = createObjCExportNamer() + + assertEquals(ClassOrProtocolName("Foo", "Foo"), namer.getClassOrProtocolName(foo)) + assertEquals("a", namer.getEnumEntrySelector(fooA)) + assertEquals("a", namer.getEnumEntrySwiftName(fooA)) + } +} diff --git a/repo/gradle-build-conventions/buildsrc-compat/src/main/kotlin/tasks.kt b/repo/gradle-build-conventions/buildsrc-compat/src/main/kotlin/tasks.kt index 6f4e6e45d71..6c52bde979b 100644 --- a/repo/gradle-build-conventions/buildsrc-compat/src/main/kotlin/tasks.kt +++ b/repo/gradle-build-conventions/buildsrc-compat/src/main/kotlin/tasks.kt @@ -108,6 +108,11 @@ enum class JUnitMode { */ fun projectTest(project: Project) = project.projectTest() +/** + * Convenience function to expose Project.projectTest for groovy + */ +fun projectTest(project: Project, jUnitMode: JUnitMode) = project.projectTest(jUnitMode = jUnitMode) + /** * @param parallel is redundant if @param jUnit5Enabled is true, because * JUnit5 supports parallel test execution by itself, without gradle help