diff --git a/native/commonizer/tests/org/jetbrains/kotlin/descriptors/commonizer/AbstractCommonizationFromSourcesTest.kt b/native/commonizer/tests/org/jetbrains/kotlin/descriptors/commonizer/AbstractCommonizationFromSourcesTest.kt index 0bbf9c5dc85..b5ea258b6f9 100644 --- a/native/commonizer/tests/org/jetbrains/kotlin/descriptors/commonizer/AbstractCommonizationFromSourcesTest.kt +++ b/native/commonizer/tests/org/jetbrains/kotlin/descriptors/commonizer/AbstractCommonizationFromSourcesTest.kt @@ -5,11 +5,13 @@ package org.jetbrains.kotlin.descriptors.commonizer +import com.intellij.openapi.Disposable import com.intellij.testFramework.PlatformTestUtil.lowercaseFirstLetter import org.jetbrains.kotlin.analyzer.common.CommonResolverForModuleFactory 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.languageVersionSettings import org.jetbrains.kotlin.descriptors.ModuleDescriptor import org.jetbrains.kotlin.descriptors.commonizer.utils.assertCommonizationPerformed @@ -18,14 +20,15 @@ import org.jetbrains.kotlin.descriptors.commonizer.utils.assertModulesAreEqual import org.jetbrains.kotlin.descriptors.commonizer.utils.assertValidModule import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.platform.CommonPlatforms +import org.jetbrains.kotlin.psi.KtFile import org.jetbrains.kotlin.psi.KtPsiFactory import org.jetbrains.kotlin.test.KotlinTestUtils.* import org.jetbrains.kotlin.test.testFramework.KtUsefulTestCase import java.io.File import kotlin.contracts.ExperimentalContracts +import kotlin.test.fail @ExperimentalContracts -@Suppress("MemberVisibilityCanBePrivate") abstract class AbstractCommonizationFromSourcesTest : KtUsefulTestCase() { companion object { init { @@ -35,137 +38,45 @@ abstract class AbstractCommonizationFromSourcesTest : KtUsefulTestCase() { fun Collection.eachModuleAsTarget() = mapIndexed { index, moduleDescriptor -> InputTarget("target_$index") to moduleDescriptor }.toMap().toCommonizationParameters() - - fun Map.toCommonizationParameters() = Parameters().also { - forEach { (target, moduleDescriptor) -> - it.addTarget( - TargetProvider( - target = target, - builtInsClass = moduleDescriptor.builtIns::class.java, - builtInsProvider = BuiltInsProvider.wrap(moduleDescriptor.builtIns), - modulesProvider = ModulesProvider.wrap(moduleDescriptor) - ) - ) - } - } } - protected fun createEnvironment(bareModuleName: String): KotlinCoreEnvironment { - check(Name.isValidIdentifier(bareModuleName)) - val configuration = newConfiguration() - configuration.put(CommonConfigurationKeys.MODULE_NAME, bareModuleName) + private fun getTestDataDir(): File { + val testCaseDir = lowercaseFirstLetter( + this::class.java.simpleName.substringBefore("FromSources").substringBefore("Test"), + true + ) + val testDir = testDirectoryName - return KotlinCoreEnvironment.createForTests(testRootDisposable, configuration, EnvironmentConfigFiles.METADATA_CONFIG_FILES) + return File(getHomeDirectory()) + .resolve("native/commonizer/testData") + .resolve(testCaseDir) + .resolve(testDir) + .also(::assertIsDirectory) } - protected val KotlinCoreEnvironment.moduleName: Name - get() { - val bareModuleName = configuration.getNotNull(CommonConfigurationKeys.MODULE_NAME) - return Name.special("<$bareModuleName>") - } - - protected val testDataDir: File - get() { - val testCaseDir = lowercaseFirstLetter( - this::class.java.simpleName.substringBefore("FromSources").substringBefore("Test"), - true - ) - val testDir = testDirectoryName - - return File(getHomeDirectory()) - .resolve("native/commonizer/testData") - .resolve(testCaseDir) - .resolve(testDir) - .also(::assertIsDirectory) - } - - protected val sourceModuleRoots: Triple, Set, File> - get() { - val testDataDir = testDataDir - - val originalRoots = testDataDir - .resolve("original") - .also(::assertIsDirectory) - .listFiles() - ?.toSet() - ?.also { it.forEach(::assertIsDirectory) } - - val commonizedRoots = testDataDir - .resolve("commonized") - .also(::assertIsDirectory) - .listFiles() - ?.toSet() - ?.also { it.forEach(::assertIsDirectory) } - - val commonRoot = commonizedRoots?.singleOrNull { it.name == "common" } - - check( - !originalRoots.isNullOrEmpty() && !commonizedRoots.isNullOrEmpty() && commonRoot != null - && (originalRoots + commonRoot).map { it.name }.toSet() == commonizedRoots.map { it.name }.toSet() - ) { - "Source module misconfiguration in $testDataDir" - } - - return Triple(originalRoots, commonizedRoots - commonRoot, commonRoot) - } - - protected val sourceModuleDescriptors: Pair, Map> - get() { - fun analyzeTarget(targetRoot: File): ModuleDescriptor { - val environment = createEnvironment(targetRoot.parentFile.parentFile.name) - val psiFactory = KtPsiFactory(environment.project) - - val psiFiles = targetRoot.walkTopDown() - .filter { it.isFile } - .map { psiFactory.createFile(it.name, doLoadFile(it)) } - .toList() - - return CommonResolverForModuleFactory.analyzeFiles( - files = psiFiles, - moduleName = environment.moduleName, - dependOnBuiltIns = true, - languageVersionSettings = environment.configuration.languageVersionSettings, - targetPlatform = CommonPlatforms.defaultCommonPlatform - ) { content -> - environment.createPackagePartProvider(content.moduleContentScope) - }.moduleDescriptor - } - - val originalModules = sourceModuleRoots.first - .map { InputTarget(it.name) to analyzeTarget(it) } - .toMap() - - val commonizedModules = sourceModuleRoots.second - .map { InputTarget(it.name) to analyzeTarget(it) } - .toMap() - - val commonModule = OutputTarget(commonizedModules.keys) to analyzeTarget(sourceModuleRoots.third) - - return originalModules to commonizedModules + commonModule - } - protected fun doTestSuccessfulCommonization() { - val (originalModules, commonizedModules) = sourceModuleDescriptors + val sourceModuleRoots: SourceModuleRoots = SourceModuleRoots.load(getTestDataDir()) + val analyzedModules: AnalyzedModules = AnalyzedModules.create(sourceModuleRoots, testRootDisposable) - val result = runCommonization(originalModules.toCommonizationParameters()) + val result: Result = runCommonization(analyzedModules.toCommonizationParameters()) assertCommonizationPerformed(result) - val commonTarget = commonizedModules.keys.filterIsInstance().single() + val commonTarget: OutputTarget = analyzedModules.commonizedCommonModule.target assertEquals(commonTarget, result.commonTarget) - val commonModuleAsExpected = commonizedModules.getValue(commonTarget) - val commonModuleByCommonizer = result.modulesByTargets.getValue(commonTarget).single() + val commonModuleAsExpected: ModuleDescriptor = analyzedModules.commonizedCommonModule.module + val commonModuleByCommonizer: ModuleDescriptor = result.modulesByTargets.getValue(commonTarget).single() assertValidModule(commonModuleAsExpected) assertValidModule(commonModuleByCommonizer) assertModulesAreEqual(commonModuleAsExpected, commonModuleByCommonizer, "\"$commonTarget\" target") - val concreteTargets = commonizedModules.keys - commonTarget + val concreteTargets: Set = analyzedModules.commonizedPlatformModules.keys assertEquals(concreteTargets, result.concreteTargets) for (target in concreteTargets) { - val targetModuleAsExpected = commonizedModules.getValue(target) - val targetModuleByCommonizer = result.modulesByTargets.getValue(target).single() + val targetModuleAsExpected: ModuleDescriptor = analyzedModules.commonizedPlatformModules.getValue(target).module + val targetModuleByCommonizer: ModuleDescriptor = result.modulesByTargets.getValue(target).single() assertValidModule(targetModuleAsExpected) assertValidModule(targetModuleByCommonizer) @@ -173,3 +84,178 @@ abstract class AbstractCommonizationFromSourcesTest : KtUsefulTestCase() { } } } + +private const val COMMON_TARGET_NAME = "common" + +private data class SourceModuleRoot( + val targetName: String, + val root: File +) { + init { + assertIsDirectory(root) + } + + companion object { + fun load(directory: File): SourceModuleRoot = SourceModuleRoot( + targetName = directory.name, + root = directory + ) + } +} + +private class SourceModuleRoots( + val originalPlatformRoots: Map, + val commonizedPlatformRoots: Map, + val commonizedCommonRoot: SourceModuleRoot +) { + init { + check(originalPlatformRoots.isNotEmpty()) + check(COMMON_TARGET_NAME !in originalPlatformRoots) + check(originalPlatformRoots.keys == commonizedPlatformRoots.keys) + check(commonizedCommonRoot.targetName == COMMON_TARGET_NAME) + } + + companion object { + fun load(dataDir: File): SourceModuleRoots = try { + val originalRoots = listRoots(dataDir, ORIGINAL_ROOTS_DIR) + val commonizedRoots = listRoots(dataDir, COMMONIZED_ROOTS_DIR) + + SourceModuleRoots( + originalPlatformRoots = originalRoots, + commonizedPlatformRoots = commonizedRoots - COMMON_TARGET_NAME, + commonizedCommonRoot = commonizedRoots.getValue(COMMON_TARGET_NAME) + ) + } catch (e: Exception) { + fail("Source module misconfiguration in $dataDir", cause = e) + } + + private const val ORIGINAL_ROOTS_DIR = "original" + private const val COMMONIZED_ROOTS_DIR = "commonized" + + private fun listRoots(dataDir: File, rootsDirName: String): Map = + dataDir.resolve(rootsDirName).listFiles()?.toSet().orEmpty().map(SourceModuleRoot::load).associateBy { it.targetName } + } +} + +private class AnalyzedModule( + val target: T, + val module: ModuleDescriptor +) { + companion object { + fun create( + target: T, + sourceModuleRoot: SourceModuleRoot, + parentDisposable: Disposable + ): AnalyzedModule { + val moduleName: String = sourceModuleRoot.root.parentFile.parentFile.name + check(Name.isValidIdentifier(moduleName)) + + return AnalyzedModule( + target = target, + module = analyze( + moduleName = moduleName, + moduleRoot = sourceModuleRoot.root, + parentDisposable = parentDisposable + ) + ) + } + + private fun analyze( + moduleName: String, + moduleRoot: File, + parentDisposable: Disposable + ): ModuleDescriptor { + val configuration: CompilerConfiguration = newConfiguration() + configuration.put(CommonConfigurationKeys.MODULE_NAME, moduleName) + + val environment: KotlinCoreEnvironment = KotlinCoreEnvironment.createForTests( + parentDisposable = parentDisposable, + initialConfiguration = configuration, + extensionConfigs = EnvironmentConfigFiles.METADATA_CONFIG_FILES + ) + + val psiFactory = KtPsiFactory(environment.project) + + val psiFiles: List = moduleRoot.walkTopDown() + .filter { it.isFile } + .map { psiFactory.createFile(it.name, doLoadFile(it)) } + .toList() + + return CommonResolverForModuleFactory.analyzeFiles( + files = psiFiles, + moduleName = Name.special("<$moduleName>"), + dependOnBuiltIns = true, + languageVersionSettings = environment.configuration.languageVersionSettings, + targetPlatform = CommonPlatforms.defaultCommonPlatform + ) { content -> + environment.createPackagePartProvider(content.moduleContentScope) + }.moduleDescriptor + } + } +} + +private class AnalyzedModules( + val originalPlatformModules: Map>, + val commonizedPlatformModules: Map>, + val commonizedCommonModule: AnalyzedModule +) { + init { + check(originalPlatformModules.isNotEmpty()) + check(originalPlatformModules.keys == commonizedPlatformModules.keys) + } + + fun toCommonizationParameters(): Parameters = originalPlatformModules.mapValues { it.value.module }.toCommonizationParameters() + + companion object { + fun create( + sourceModuleRoots: SourceModuleRoots, + parentDisposable: Disposable + ): AnalyzedModules { + val originalPlatformModules: Map> = createInputTargetModules( + sourceModuleRoots = sourceModuleRoots.originalPlatformRoots, + parentDisposable = parentDisposable + ) + + val commonizedCommonModule: AnalyzedModule = AnalyzedModule.create( + target = OutputTarget(originalPlatformModules.keys), + sourceModuleRoot = sourceModuleRoots.commonizedCommonRoot, + parentDisposable = parentDisposable + ) + + val commonizedPlatformModules: Map> = createInputTargetModules( + sourceModuleRoots = sourceModuleRoots.commonizedPlatformRoots, + parentDisposable = parentDisposable + ) + + return AnalyzedModules( + originalPlatformModules = originalPlatformModules, + commonizedPlatformModules = commonizedPlatformModules, + commonizedCommonModule = commonizedCommonModule + ) + } + + private fun createInputTargetModules( + sourceModuleRoots: Map, + parentDisposable: Disposable + ): Map> = sourceModuleRoots.map { (targetName, sourceModuleRoot) -> + AnalyzedModule.create( + target = InputTarget(targetName), + sourceModuleRoot = sourceModuleRoot, + parentDisposable = parentDisposable + ) + }.associateBy { it.target } + } +} + +private fun Map.toCommonizationParameters(): Parameters = Parameters().also { + forEach { (target, moduleDescriptor) -> + it.addTarget( + TargetProvider( + target = target, + builtInsClass = moduleDescriptor.builtIns::class.java, + builtInsProvider = BuiltInsProvider.wrap(moduleDescriptor.builtIns), + modulesProvider = ModulesProvider.wrap(moduleDescriptor) + ) + ) + } +}