diff --git a/native/native.tests/testData/ObjCExport/kdocExport/kdocExport.filter.txt b/native/native.tests/testData/ObjCExport/kdocExport/kdocExport.filter.txt new file mode 100644 index 00000000000..643141de4cd --- /dev/null +++ b/native/native.tests/testData/ObjCExport/kdocExport/kdocExport.filter.txt @@ -0,0 +1 @@ +EXPORT_KDOC \ No newline at end of file diff --git a/native/native.tests/testData/ObjCExport/kdocExport/kdocExport.gold.txt b/native/native.tests/testData/ObjCExport/kdocExport/kdocExport.gold.txt new file mode 100644 index 00000000000..c91c294ebb5 --- /dev/null +++ b/native/native.tests/testData/ObjCExport/kdocExport/kdocExport.gold.txt @@ -0,0 +1,14 @@ + * `Summary class` [KDocExport]. // EXPORT_KDOC + * @property xyzzy Doc for property xyzzy // EXPORT_KDOC + * @property zzz See below. // EXPORT_KDOC +/** Non-primary ctor KDoc: // EXPORT_KDOC */ +/** @property xyzzy KDoc for foo? // EXPORT_KDOC */ + * @param xyzzy is documented. // EXPORT_KDOC + * This is multi-line KDoc. See a blank line above. // EXPORT_KDOC +/** @property foo KDoc for yxxyz? // EXPORT_KDOC */ + * Kdoc for a property // EXPORT_KDOC + * Useless function [whatever] // EXPORT_KDOC + * This kdoc has some additional formatting. // EXPORT_KDOC + * @param a keep intact and return // EXPORT_KDOC + * @return value of [a] // EXPORT_KDOC + * Check for additional comment (note) below // EXPORT_KDOC \ No newline at end of file diff --git a/native/native.tests/testData/ObjCExport/kdocExport/kdocExport.kt b/native/native.tests/testData/ObjCExport/kdocExport/kdocExport.kt new file mode 100644 index 00000000000..6bc96e6a20e --- /dev/null +++ b/native/native.tests/testData/ObjCExport/kdocExport/kdocExport.kt @@ -0,0 +1,54 @@ +// KT-56090: [K2/N] Emit DocString klib extensions for ObjCExport +// MUTED_WHEN: K2 +/* + * 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 kdocExport + +/** + * `Summary class` [KDocExport]. // EXPORT_KDOC + * + * @property xyzzy Doc for property xyzzy // EXPORT_KDOC + * @property zzz See below. // EXPORT_KDOC + */ + +// Expected: this comment shall not affect KDoc (i.e. kdoc above is still OK) +class KDocExport() { + /** + * @param xyzzy is documented. // EXPORT_KDOC + * + * This is multi-line KDoc. See a blank line above. // EXPORT_KDOC + */ + val xyzzy: String = "String example" + + /** Non-primary ctor KDoc: // EXPORT_KDOC */ + constructor(name: String) : this() { + println(name) + } + + /** @property xyzzy KDoc for foo? // EXPORT_KDOC */ + val foo = "foo" + /** @property foo KDoc for yxxyz? // EXPORT_KDOC */ + var yxxyz = 0; +} + + +/** + * Useless function [whatever] // EXPORT_KDOC + * + * This kdoc has some additional formatting. // EXPORT_KDOC + * @param a keep intact and return // EXPORT_KDOC + * @return value of [a] // EXPORT_KDOC + * Check for additional comment (note) below // EXPORT_KDOC + */ +fun whatever(a:String) = a + +public abstract class SomeClassWithProperty +{ + /** + * Kdoc for a property // EXPORT_KDOC + */ + public abstract val heavyFormattedKDocFoo: SomeClassWithProperty +} \ No newline at end of file diff --git a/native/native.tests/tests-gen/org/jetbrains/kotlin/konan/blackboxtest/FirObjCExportTestGenerated.java b/native/native.tests/tests-gen/org/jetbrains/kotlin/konan/blackboxtest/FirObjCExportTestGenerated.java new file mode 100644 index 00000000000..b3166490162 --- /dev/null +++ b/native/native.tests/tests-gen/org/jetbrains/kotlin/konan/blackboxtest/FirObjCExportTestGenerated.java @@ -0,0 +1,36 @@ +/* + * 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 com.intellij.testFramework.TestDataPath; +import org.jetbrains.kotlin.test.util.KtTestUtil; +import org.junit.jupiter.api.Tag; +import org.jetbrains.kotlin.konan.blackboxtest.support.group.FirPipeline; +import org.jetbrains.kotlin.test.TestMetadata; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +import java.io.File; +import java.util.regex.Pattern; + +/** This class is generated by {@link org.jetbrains.kotlin.generators.tests.GenerateNativeTestsKt}. DO NOT MODIFY MANUALLY */ +@SuppressWarnings("all") +@TestMetadata("native/native.tests/testData/ObjCExport") +@TestDataPath("$PROJECT_ROOT") +@Tag("frontend-fir") +@FirPipeline() +public class FirObjCExportTestGenerated extends AbstractNativeObjCExportTest { + @Test + public void testAllFilesPresentInObjCExport() throws Exception { + KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("native/native.tests/testData/ObjCExport"), Pattern.compile("^([^_](.+))$"), null, false); + } + + @Test + @TestMetadata("kdocExport") + public void testKdocExport() throws Exception { + runTest("native/native.tests/testData/ObjCExport/kdocExport/"); + } +} diff --git a/native/native.tests/tests-gen/org/jetbrains/kotlin/konan/blackboxtest/ObjCExportTestGenerated.java b/native/native.tests/tests-gen/org/jetbrains/kotlin/konan/blackboxtest/ObjCExportTestGenerated.java new file mode 100644 index 00000000000..f077d7c5477 --- /dev/null +++ b/native/native.tests/tests-gen/org/jetbrains/kotlin/konan/blackboxtest/ObjCExportTestGenerated.java @@ -0,0 +1,32 @@ +/* + * 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 com.intellij.testFramework.TestDataPath; +import org.jetbrains.kotlin.test.util.KtTestUtil; +import org.jetbrains.kotlin.test.TestMetadata; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +import java.io.File; +import java.util.regex.Pattern; + +/** This class is generated by {@link org.jetbrains.kotlin.generators.tests.GenerateNativeTestsKt}. DO NOT MODIFY MANUALLY */ +@SuppressWarnings("all") +@TestMetadata("native/native.tests/testData/ObjCExport") +@TestDataPath("$PROJECT_ROOT") +public class ObjCExportTestGenerated extends AbstractNativeObjCExportTest { + @Test + public void testAllFilesPresentInObjCExport() throws Exception { + KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("native/native.tests/testData/ObjCExport"), Pattern.compile("^([^_](.+))$"), null, false); + } + + @Test + @TestMetadata("kdocExport") + public void testKdocExport() throws Exception { + runTest("native/native.tests/testData/ObjCExport/kdocExport/"); + } +} diff --git a/native/native.tests/tests/org/jetbrains/kotlin/generators/tests/GenerateNativeTests.kt b/native/native.tests/tests/org/jetbrains/kotlin/generators/tests/GenerateNativeTests.kt index 0ff74f2d3dd..c33fe53d09e 100644 --- a/native/native.tests/tests/org/jetbrains/kotlin/generators/tests/GenerateNativeTests.kt +++ b/native/native.tests/tests/org/jetbrains/kotlin/generators/tests/GenerateNativeTests.kt @@ -165,6 +165,23 @@ fun main() { } } + // ObjCExport tests. + testGroup("native/native.tests/tests-gen", "native/native.tests/testData") { + testClass( + suiteTestClassName = "ObjCExportTestGenerated" + ) { + model("ObjCExport", pattern = "^([^_](.+))$", recursive = false) + } + testClass( + suiteTestClassName = "FirObjCExportTestGenerated", + annotations = listOf( + *frontendFir() + ), + ) { + model("ObjCExport", pattern = "^([^_](.+))$", recursive = false) + } + } + // Klib contents tests testGroup("native/native.tests/tests-gen", "native/native.tests/testData") { testClass( diff --git a/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/AbstractNativeObjCExportTest.kt b/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/AbstractNativeObjCExportTest.kt new file mode 100644 index 00000000000..cb742692f4f --- /dev/null +++ b/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/AbstractNativeObjCExportTest.kt @@ -0,0 +1,76 @@ +/* + * Copyright 2010-2022 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 com.intellij.testFramework.TestDataFile +import org.jetbrains.kotlin.konan.blackboxtest.support.* +import org.jetbrains.kotlin.konan.blackboxtest.support.compilation.* +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.runner.* +import org.jetbrains.kotlin.konan.blackboxtest.support.settings.* +import org.jetbrains.kotlin.konan.blackboxtest.support.util.getAbsoluteFile +import org.jetbrains.kotlin.test.services.JUnit5Assertions +import org.junit.jupiter.api.Assumptions +import org.junit.jupiter.api.Tag +import java.io.File + +@Tag("objcexport") +abstract class AbstractNativeObjCExportTest : AbstractNativeSimpleTest() { + private val targets: KotlinNativeTargets get() = testRunSettings.get() + private val testCompilationFactory = TestCompilationFactory() + + protected fun runTest(@TestDataFile testDir: String) { + Assumptions.assumeTrue(targets.testTarget.family.isAppleFamily) + val testPathFull = getAbsoluteFile(testDir) + val ktSources = testPathFull.list()!! + .filter { it.endsWith(".kt")} + .map { testPathFull.resolve(it) } + ktSources.forEach { muteTestIfNecessary(it) } + + val testCase: TestCase = generateObjCFrameworkTestCase(testPathFull, ktSources) + val objCFramework: ObjCFramework = testCase.toObjCFramework().assertSuccess().resultingArtifact + + val mainHeaderContents = objCFramework.mainHeader.readText() + val regexFilter = testPathFull.resolve("${testPathFull.name}.filter.txt").readText() + val actualFilteredOutput = filterContentsOutput(mainHeaderContents, regexFilter) + val expectedFilteredOutput = testPathFull.resolve("${testPathFull.name}.gold.txt").readText() + + // Compare to goldfile, ignoring lines order. This is needed to be stable against possible declaration order fluctuations. + // TODO Investigate why ObjCExport declarations order may vary + JUnit5Assertions.assertEquals( + expectedFilteredOutput.lines().sorted().joinToString("\n"), + actualFilteredOutput.lines().sorted().joinToString("\n") + ) + } + + private fun filterContentsOutput(contents: String, pattern: String) = + contents.split("\n").filter { + it.contains(Regex(pattern)) + }.joinToString(separator = "\n") + + private fun TestCase.toObjCFramework(): TestCompilationResult { + return testCompilationFactory.testCaseToObjCFrameworkCompilation(this, testRunSettings).result + } + + private fun generateObjCFrameworkTestCase(testPathFull: File, sources: List): TestCase { + val moduleName: String = testPathFull.name + val module = TestModule.Exclusive(moduleName, emptySet(), emptySet(), emptySet()) + sources.forEach { module.files += TestFile.createCommitted(it, module) } + + return TestCase( + id = TestCaseId.Named(moduleName), + kind = TestKind.STANDALONE, + modules = setOf(module), + freeCompilerArgs = TestCompilerArgs(listOf("-Xexport-kdoc")), + nominalPackageName = PackageName(moduleName), + checks = TestRunChecks.Default(testRunSettings.get().executionTimeout), + extras = TestCase.WithTestRunnerExtras(TestRunnerType.DEFAULT) + ).apply { + initialize(null, null) + } + } +} diff --git a/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/support/NativeTestSupport.kt b/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/support/NativeTestSupport.kt index 2852f55afc4..cd935b49aba 100644 --- a/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/support/NativeTestSupport.kt +++ b/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/support/NativeTestSupport.kt @@ -207,6 +207,7 @@ private object NativeTestSupport { output += computePipelineType(testClass.get()) output += computeUsedPartialLinkageConfig(enclosingTestClass) output += computeCompilerOutputInterceptor(enforcedProperties) + output += computeBinariesDirs(getOrCreateTestProcessSettings().get(), nativeTargets, enclosingTestClass) return nativeTargets } @@ -337,7 +338,6 @@ private object NativeTestSupport { // Put settings that are always required: this += computedTestConfiguration - this += computeBinariesDirs(testProcessSettings.get(), nativeTargets, enclosingTestClass) // Add custom settings: computedTestConfiguration.configuration.requiredSettings.forEach { clazz -> diff --git a/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/support/compilation/TestCompilation.kt b/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/support/compilation/TestCompilation.kt index fc5d4c69f11..8821f047cf4 100644 --- a/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/support/compilation/TestCompilation.kt +++ b/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/support/compilation/TestCompilation.kt @@ -213,6 +213,40 @@ internal class LibraryCompilation( } } +internal class ObjCFrameworkCompilation( + settings: Settings, + freeCompilerArgs: TestCompilerArgs, + sourceModules: Collection, + dependencies: Iterable>, + expectedArtifact: ObjCFramework +) : SourceBasedCompilation( + targets = settings.get(), + home = settings.get(), + classLoader = settings.get(), + optimizationMode = settings.get(), + compilerOutputInterceptor = settings.get(), + memoryModel = settings.get(), + threadStateChecker = settings.get(), + sanitizer = settings.get(), + gcType = settings.get(), + gcScheduler = settings.get(), + pipelineType = settings.get(), + freeCompilerArgs = freeCompilerArgs, + sourceModules = sourceModules, + dependencies = CategorizedDependencies(dependencies), + expectedArtifact = expectedArtifact +) { + override val binaryOptions get() = BinaryOptions.RuntimeAssertionsMode.defaultForTesting + + override fun applySpecificArgs(argsBuilder: ArgsBuilder) = with(argsBuilder) { + add( + "-produce", "framework", + "-output", expectedArtifact.frameworkDir.absolutePath + ) + super.applySpecificArgs(argsBuilder) + } +} + internal class GivenLibraryCompilation(givenArtifact: KLIB) : TestCompilation() { override val result = TestCompilationResult.Success(givenArtifact, LoggedData.NoopCompilerCall(givenArtifact.klibFile)) } diff --git a/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/support/compilation/TestCompilationArtifact.kt b/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/support/compilation/TestCompilationArtifact.kt index 491387b6c3e..7e55d306371 100644 --- a/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/support/compilation/TestCompilationArtifact.kt +++ b/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/support/compilation/TestCompilationArtifact.kt @@ -24,4 +24,11 @@ internal sealed interface TestCompilationArtifact { override val logFile: File get() = executableFile.resolveSibling("${executableFile.name}.log") val testDumpFile: File get() = executableFile.resolveSibling("${executableFile.name}.dump") } + + data class ObjCFramework(private val buildDir: File, val frameworkName: String) : TestCompilationArtifact { + override val logFile: File get() = buildDir.resolve("$frameworkName.log") + val frameworkDir = buildDir.resolve("$frameworkName.framework") + val headersDir: File = frameworkDir.resolve("Headers") + val mainHeader: File = headersDir.resolve("$frameworkName.h") + } } diff --git a/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/support/compilation/TestCompilationFactory.kt b/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/support/compilation/TestCompilationFactory.kt index 266bcc74cd9..334997a755a 100644 --- a/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/support/compilation/TestCompilationFactory.kt +++ b/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/support/compilation/TestCompilationFactory.kt @@ -25,9 +25,11 @@ import java.io.File internal class TestCompilationFactory { private val cachedKlibCompilations = ThreadSafeCache() private val cachedExecutableCompilations = ThreadSafeCache>() + private val cachedObjCFrameworkCompilations = ThreadSafeCache() private data class KlibCacheKey(val sourceModules: Set, val freeCompilerArgs: TestCompilerArgs) private data class ExecutableCacheKey(val sourceModules: Set) + private data class ObjCFrameworkCacheKey(val sourceModules: Set) // A pair of compilations for a KLIB itself and for its static cache that are created together. private data class KlibCompilations(val klib: TestCompilation, val staticCache: TestCompilation?) @@ -80,6 +82,31 @@ internal class TestCompilationFactory { } } + fun testCaseToObjCFrameworkCompilation(testCase: TestCase, settings: Settings): ObjCFrameworkCompilation { + val cacheKey = ObjCFrameworkCacheKey(testCase.rootModules) + cachedObjCFrameworkCompilations[cacheKey]?.let { return it } + + val ( + dependencies: Iterable>, + sourceModules: Set + ) = getDependenciesAndSourceModules(settings, testCase.rootModules, testCase.freeCompilerArgs) { + ProduceStaticCache.No + } + + return cachedObjCFrameworkCompilations.computeIfAbsent(cacheKey) { + ObjCFrameworkCompilation( + settings = settings, + freeCompilerArgs = testCase.freeCompilerArgs, + sourceModules = sourceModules, + dependencies = dependencies, + expectedArtifact = ObjCFramework( + settings.artifactDirForPackageName(testCase.nominalPackageName), + testCase.nominalPackageName.compressedPackageName + ) + ) + } + } + fun testCasesToExecutable(testCases: Collection, settings: Settings): TestCompilation { val rootModules = testCases.flatMapToSet { testCase -> testCase.rootModules } val cacheKey = ExecutableCacheKey(rootModules) @@ -92,28 +119,11 @@ internal class TestCompilationFactory { val extras = testCases.first().extras // Should be identical inside the same test case group. val executableArtifact = Executable(settings.artifactFileForExecutable(rootModules)) - val sourceModulesToCompileExecutable: Set - val dependenciesToCompileExecutable: Iterable> - - when (settings.get()) { - TestMode.ONE_STAGE_MULTI_MODULE -> { - // Collect dependencies of root modules. Compile root modules directly to executable. - sourceModulesToCompileExecutable = rootModules - dependenciesToCompileExecutable = collectDependencies(rootModules, freeCompilerArgs, settings).forOneStageExecutable() - } - TestMode.TWO_STAGE_MULTI_MODULE -> { - // Compile root modules to KLIB. Pass this KLIB as included dependency to executable compilation. - val produceStaticCache = ProduceStaticCache.decideForIncludedKlib(settings, executableArtifact, extras) - val klibCompilations = modulesToKlib(rootModules, freeCompilerArgs, produceStaticCache, settings) - - sourceModulesToCompileExecutable = emptySet() // No sources. - - // Include just compiled KLIB as -Xinclude dependency. - dependenciesToCompileExecutable = collectDependencies(rootModules, freeCompilerArgs, settings).forTwoStageExecutable( - includedKlib = klibCompilations.klib.asKlibDependency(IncludedLibrary), - includedKlibStaticCache = klibCompilations.staticCache?.asStaticCacheDependency() - ) - } + val ( + dependenciesToCompileExecutable: Iterable>, + sourceModulesToCompileExecutable: Set + ) = getDependenciesAndSourceModules(settings, rootModules, freeCompilerArgs) { + ProduceStaticCache.decideForIncludedKlib(settings, executableArtifact, extras) } return cachedExecutableCompilations.computeIfAbsent(cacheKey) { @@ -128,6 +138,35 @@ internal class TestCompilationFactory { } } + private fun getDependenciesAndSourceModules( + settings: Settings, + rootModules: Set, + freeCompilerArgs: TestCompilerArgs, + produceStaticCache: () -> ProduceStaticCache, + ): Pair>, Set> = + when (settings.get()) { + TestMode.ONE_STAGE_MULTI_MODULE -> { + Pair( + // Collect dependencies of root modules. Compile root modules directly to executable. + collectDependencies(rootModules, freeCompilerArgs, settings).forOneStageExecutable(), + rootModules + ) + } + TestMode.TWO_STAGE_MULTI_MODULE -> { + // Compile root modules to KLIB. Pass this KLIB as included dependency to executable compilation. + val klibCompilations = modulesToKlib(rootModules, freeCompilerArgs, produceStaticCache(), settings) + + Pair( + // Include just compiled KLIB as -Xinclude dependency. + collectDependencies(rootModules, freeCompilerArgs, settings).forTwoStageExecutable( + includedKlib = klibCompilations.klib.asKlibDependency(IncludedLibrary), + includedKlibStaticCache = klibCompilations.staticCache?.asStaticCacheDependency() + ), + emptySet() // No sources. + ) + } + } + private fun modulesToKlib( sourceModules: Set, freeCompilerArgs: TestCompilerArgs,