[K/N] Implement ObjCExportNamerTest and ObjCExportMapperTest

... to provide simple and quick validation
for their corresponding entities.

Those tests can also act as convenient entry points
for debugging.
This commit is contained in:
Sebastian Sellmair
2023-11-23 13:23:37 +01:00
committed by Space Team
parent 269b60b420
commit 04df33eec1
9 changed files with 571 additions and 91 deletions
+5 -1
View File
@@ -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)
@@ -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<File>
): 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("<test_module>"),
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<ModuleInfo> get() = listOf(DefaultBuiltInsModuleInfo, KotlinNativeStdlibModuleInfo)
override val friendModuleInfos: List<ModuleInfo> get() = emptyList()
override val refinesModuleInfos: List<ModuleInfo> 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("<stdlib>")
override val platform: TargetPlatform = NativePlatforms.unspecifiedNativePlatform
override fun dependencies(): List<ModuleInfo> = 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
)
}
@@ -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<String>
): 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<File>,
): 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("<test_module>"),
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<ModuleInfo> get() = listOf(DefaultBuiltInsModuleInfo, KotlinNativeStdlibModuleInfo)
override val friendModuleInfos: List<ModuleInfo> get() = emptyList()
override val refinesModuleInfos: List<ModuleInfo> 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("<stdlib>")
override val platform: TargetPlatform = NativePlatforms.unspecifiedNativePlatform
override fun dependencies(): List<ModuleInfo> = listOf(this)
}
@@ -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<String>()
object : InlineSourceCodeCollector {
override fun source(sourceCode: String) {
sources.add(sourceCode)
}
}.build()
return createModuleDescriptor(kotlinCoreEnvironment, testTempDir, sources.toList())
}
@@ -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
)
}
@@ -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
@@ -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<Int>) 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<Int> to NSArray<Int *> *
*/
@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<Int> */
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<Int *> *", listOfIntMapped.toString())
}
}
@@ -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]. <br>
*
* The __input__ to this test will be descriptors defined as 'inline source code'.
* (e.g. a String like "class Foo").<br>
*
* 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))
}
}
@@ -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