From 0db893102664d563da759b48c919dd25379703f9 Mon Sep 17 00:00:00 2001 From: Sebastian Sellmair Date: Thu, 25 Jan 2024 11:29:38 +0100 Subject: [PATCH] [ObjCExport] Implement 'TodoAnalysisApi' annotation and run AA based tests as part of ':native:objcexport-header-generator:check' ^KT-65281 Fixed ^KT-65108 Fixed --- native/objcexport-header-generator/ReadMe.md | 28 +++++++++++++ .../build.gradle.kts | 10 ++--- .../impl/analysis-api/build.gradle.kts | 5 +-- ...nalysisApiTestExecutionExceptionHandler.kt | 40 +++++++++++++++++++ .../org.junit.jupiter.api.extension.Extension | 3 +- .../impl/k1/build.gradle.kts | 5 +-- .../testUtils/TestDisplayNameGenerator.kt | 33 +++++++++++++++ .../konan/testUtils/TodoAnalysisApi.kt | 8 ++++ .../kotlin/backend/konan/testUtils/isCI.kt | 11 +++++ .../tests/ObjCExportBaseDeclarationsTest.kt | 3 ++ .../ObjCExportForwardDeclarationsTest.kt | 5 +++ .../tests/ObjCExportHeaderGeneratorTest.kt | 16 +++++++- .../testResources/junit-platform.properties | 1 + .../kotlin/objcExportHeaderGeneratorTest.kt | 29 ++++++++++++++ 14 files changed, 180 insertions(+), 17 deletions(-) create mode 100644 native/objcexport-header-generator/impl/analysis-api/test/org/jetbrains/kotlin/objcexport/testUtils/TodoAnalysisApiTestExecutionExceptionHandler.kt create mode 100644 native/objcexport-header-generator/test/org/jetbrains/kotlin/backend/konan/testUtils/TestDisplayNameGenerator.kt create mode 100644 native/objcexport-header-generator/test/org/jetbrains/kotlin/backend/konan/testUtils/TodoAnalysisApi.kt create mode 100644 native/objcexport-header-generator/test/org/jetbrains/kotlin/backend/konan/testUtils/isCI.kt create mode 100644 native/objcexport-header-generator/testResources/junit-platform.properties create mode 100644 repo/gradle-build-conventions/buildsrc-compat/src/main/kotlin/objcExportHeaderGeneratorTest.kt diff --git a/native/objcexport-header-generator/ReadMe.md b/native/objcexport-header-generator/ReadMe.md index 3249b0e7cac..ef787ea8c10 100644 --- a/native/objcexport-header-generator/ReadMe.md +++ b/native/objcexport-header-generator/ReadMe.md @@ -59,3 +59,31 @@ both implementations. ``` Note: Since the Analysis Api implementation is WIP yet, this test can be used for debugging, but is not fully implemented yet. + +### CI setup and 'TodoAnalysisApi' +As explained previously, tests in :native:objcexport-header-generator will be able to run against K1 and the AA implementation. +The CI will now run both cases. However, some tests are not yet expected to pass for the newer AA based implementation. +In this case the test can be marked as 'todo' using the `@TodoAnalysisApi` annotation. + +Example +```kotlin +@Test +@TodoAnalysisApi +fun myTest() { + +} +``` + +This annotation will +- Ignore test the test failure for the AA based implementation on the CI +- Mark the displayName of the test with 'TODO' (e.g. `[AA] myTest // TODO`) + +Note: +- The test will still fail locally (developer setup, not marked as CI). +- If the annotation is still present, but the test is successful on CI, then an error is emitted that reminds you about removing the annotation + +The behaviour of the CI can be replicated by passing a Gradle property like +```text +./gradlew :native:objcexport-header-generator:check -Pci + // ^ +``` \ No newline at end of file diff --git a/native/objcexport-header-generator/build.gradle.kts b/native/objcexport-header-generator/build.gradle.kts index 4ef3befe3dd..c08ee55e751 100644 --- a/native/objcexport-header-generator/build.gradle.kts +++ b/native/objcexport-header-generator/build.gradle.kts @@ -45,21 +45,17 @@ tasks.test.configure { enabled = false } -nativeTest("testK1", tag = null, requirePlatformLibs = true) { - useJUnitPlatform() - enableJunit5ExtensionsAutodetection() +objCExportHeaderGeneratorTest("testK1", testDisplayNameTag = "K1") { classpath += k1TestRuntimeClasspath } -nativeTest("testAnalysisApi", tag = null, requirePlatformLibs = true) { - useJUnitPlatform() - enableJunit5ExtensionsAutodetection() - testClassesDirs += files(sourceSets.test.map { it.output.classesDirs }) +objCExportHeaderGeneratorTest("testAnalysisApi", testDisplayNameTag = "AA") { classpath += analysisApiRuntimeClasspath } tasks.check.configure { dependsOn("testK1") + dependsOn("testAnalysisApi") dependsOn(":native:objcexport-header-generator-k1:check") dependsOn(":native:objcexport-header-generator-analysis-api:check") } diff --git a/native/objcexport-header-generator/impl/analysis-api/build.gradle.kts b/native/objcexport-header-generator/impl/analysis-api/build.gradle.kts index 8f45a6eabb7..979440f0b0e 100644 --- a/native/objcexport-header-generator/impl/analysis-api/build.gradle.kts +++ b/native/objcexport-header-generator/impl/analysis-api/build.gradle.kts @@ -27,7 +27,4 @@ sourceSets { testsJar() -nativeTest("test", tag = null, requirePlatformLibs = true) { - useJUnitPlatform() - enableJunit5ExtensionsAutodetection() -} +objCExportHeaderGeneratorTest("test") diff --git a/native/objcexport-header-generator/impl/analysis-api/test/org/jetbrains/kotlin/objcexport/testUtils/TodoAnalysisApiTestExecutionExceptionHandler.kt b/native/objcexport-header-generator/impl/analysis-api/test/org/jetbrains/kotlin/objcexport/testUtils/TodoAnalysisApiTestExecutionExceptionHandler.kt new file mode 100644 index 00000000000..ffc12dea8fc --- /dev/null +++ b/native/objcexport-header-generator/impl/analysis-api/test/org/jetbrains/kotlin/objcexport/testUtils/TodoAnalysisApiTestExecutionExceptionHandler.kt @@ -0,0 +1,40 @@ +/* + * Copyright 2010-2024 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.objcexport.testUtils + +import org.jetbrains.kotlin.backend.konan.testUtils.TodoAnalysisApi +import org.jetbrains.kotlin.backend.konan.testUtils.isCI +import org.junit.AssumptionViolatedException +import org.junit.jupiter.api.extension.AfterEachCallback +import org.junit.jupiter.api.extension.ExtensionContext +import org.junit.jupiter.api.extension.TestExecutionExceptionHandler +import kotlin.jvm.optionals.getOrNull + +internal class TodoAnalysisApiTestExecutionExceptionHandler : TestExecutionExceptionHandler, AfterEachCallback { + override fun handleTestExecutionException(context: ExtensionContext, throwable: Throwable) { + val element = context.element.getOrNull() ?: return + if (element.isAnnotationPresent(TodoAnalysisApi::class.java)) { + val message = "Test is marked as 'Todo' for Analysis Api" + if (isCI) { + throwable.printStackTrace(System.err) + throw AssumptionViolatedException(message, throwable) + } else { + context.publishReportEntry(message) + System.err.println(message) + } + } + + throw throwable + } + + override fun afterEach(context: ExtensionContext) { + val element = context.element.getOrNull() ?: return + if (element.isAnnotationPresent(TodoAnalysisApi::class.java) && context.executionException.getOrNull() == null) { + val report: (String) -> Unit = if (isCI) ::error else System.err::println + report("Test: ${context.displayName} was marked as 'Todo' but executed successfully") + } + } +} diff --git a/native/objcexport-header-generator/impl/analysis-api/testResources/META-INF/services/org.junit.jupiter.api.extension.Extension b/native/objcexport-header-generator/impl/analysis-api/testResources/META-INF/services/org.junit.jupiter.api.extension.Extension index 9ebb1844e7c..5b99ad8fd78 100644 --- a/native/objcexport-header-generator/impl/analysis-api/testResources/META-INF/services/org.junit.jupiter.api.extension.Extension +++ b/native/objcexport-header-generator/impl/analysis-api/testResources/META-INF/services/org.junit.jupiter.api.extension.Extension @@ -4,4 +4,5 @@ # org.jetbrains.kotlin.objcexport.testUtils.AnalysisApiHeaderGeneratorExtension org.jetbrains.kotlin.objcexport.testUtils.InlineSourceCodeAnalysisExtension -org.jetbrains.kotlin.objcexport.testUtils.AnalysisApiBaseDeclarationsGeneratorExtension \ No newline at end of file +org.jetbrains.kotlin.objcexport.testUtils.AnalysisApiBaseDeclarationsGeneratorExtension +org.jetbrains.kotlin.objcexport.testUtils.TodoAnalysisApiTestExecutionExceptionHandler \ No newline at end of file diff --git a/native/objcexport-header-generator/impl/k1/build.gradle.kts b/native/objcexport-header-generator/impl/k1/build.gradle.kts index cb760a56fff..01889e12150 100644 --- a/native/objcexport-header-generator/impl/k1/build.gradle.kts +++ b/native/objcexport-header-generator/impl/k1/build.gradle.kts @@ -25,7 +25,4 @@ kotlin { testsJar() -nativeTest("test", tag = null, requirePlatformLibs = true) { - useJUnitPlatform() - enableJunit5ExtensionsAutodetection() -} +objCExportHeaderGeneratorTest("test") diff --git a/native/objcexport-header-generator/test/org/jetbrains/kotlin/backend/konan/testUtils/TestDisplayNameGenerator.kt b/native/objcexport-header-generator/test/org/jetbrains/kotlin/backend/konan/testUtils/TestDisplayNameGenerator.kt new file mode 100644 index 00000000000..c1613c7a262 --- /dev/null +++ b/native/objcexport-header-generator/test/org/jetbrains/kotlin/backend/konan/testUtils/TestDisplayNameGenerator.kt @@ -0,0 +1,33 @@ +/* + * Copyright 2010-2024 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.junit.jupiter.api.DisplayNameGenerator +import java.lang.reflect.Method + +class TestDisplayNameGenerator : DisplayNameGenerator { + private val tag = System.getProperty("testDisplayName.tag") + private val default = DisplayNameGenerator.Standard() + + override fun generateDisplayNameForClass(testClass: Class<*>?): String { + return default.generateDisplayNameForClass(testClass) + } + + override fun generateDisplayNameForNestedClass(nestedClass: Class<*>?): String { + return default.generateDisplayNameForNestedClass(nestedClass) + } + + override fun generateDisplayNameForMethod(testClass: Class<*>?, testMethod: Method?): String { + val defaultName = default.generateDisplayNameForMethod(testClass, testMethod) + val isTodo = testMethod?.isAnnotationPresent(TodoAnalysisApi::class.java) ?: false && tag == "AA" + + return buildString { + if (tag != null) append("[$tag] ") + append(defaultName) + if (isTodo) append(" // TODO") + } + } +} diff --git a/native/objcexport-header-generator/test/org/jetbrains/kotlin/backend/konan/testUtils/TodoAnalysisApi.kt b/native/objcexport-header-generator/test/org/jetbrains/kotlin/backend/konan/testUtils/TodoAnalysisApi.kt new file mode 100644 index 00000000000..21a1ff5808f --- /dev/null +++ b/native/objcexport-header-generator/test/org/jetbrains/kotlin/backend/konan/testUtils/TodoAnalysisApi.kt @@ -0,0 +1,8 @@ +/* + * Copyright 2010-2024 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 + +annotation class TodoAnalysisApi diff --git a/native/objcexport-header-generator/test/org/jetbrains/kotlin/backend/konan/testUtils/isCI.kt b/native/objcexport-header-generator/test/org/jetbrains/kotlin/backend/konan/testUtils/isCI.kt new file mode 100644 index 00000000000..97438cbeaeb --- /dev/null +++ b/native/objcexport-header-generator/test/org/jetbrains/kotlin/backend/konan/testUtils/isCI.kt @@ -0,0 +1,11 @@ +/* + * Copyright 2010-2024 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 + +/** + * Indicates if the tests are currently executed on CI like teamcity + */ +val isCI = System.getProperty("is.ci")?.toBoolean() ?: throw RuntimeException("Missing 'is.ci' System property") \ No newline at end of file diff --git a/native/objcexport-header-generator/test/org/jetbrains/kotlin/backend/konan/tests/ObjCExportBaseDeclarationsTest.kt b/native/objcexport-header-generator/test/org/jetbrains/kotlin/backend/konan/tests/ObjCExportBaseDeclarationsTest.kt index 5e3e9a7cf3d..52ae9953a46 100644 --- a/native/objcexport-header-generator/test/org/jetbrains/kotlin/backend/konan/tests/ObjCExportBaseDeclarationsTest.kt +++ b/native/objcexport-header-generator/test/org/jetbrains/kotlin/backend/konan/tests/ObjCExportBaseDeclarationsTest.kt @@ -7,6 +7,7 @@ package org.jetbrains.kotlin.backend.konan.tests import org.jetbrains.kotlin.backend.konan.objcexport.ObjCTopLevel import org.jetbrains.kotlin.backend.konan.objcexport.StubRenderer +import org.jetbrains.kotlin.backend.konan.testUtils.TodoAnalysisApi import org.jetbrains.kotlin.backend.konan.testUtils.baseDeclarationsDir import org.jetbrains.kotlin.test.KotlinTestUtils import org.junit.jupiter.api.Test @@ -25,11 +26,13 @@ class ObjCExportBaseDeclarationsTest( ) { @Test + @TodoAnalysisApi fun `test - noTopLevelPrefix`() { doTest(baseDeclarationsDir.resolve("!noTopLevelPrefix.h"), "") } @Test + @TodoAnalysisApi fun `test - topLevelPrefix`() { doTest(baseDeclarationsDir.resolve("!topLevelPrefix.h"), "MyTopLevelPrefix") } diff --git a/native/objcexport-header-generator/test/org/jetbrains/kotlin/backend/konan/tests/ObjCExportForwardDeclarationsTest.kt b/native/objcexport-header-generator/test/org/jetbrains/kotlin/backend/konan/tests/ObjCExportForwardDeclarationsTest.kt index e8d4943d677..680528fb396 100644 --- a/native/objcexport-header-generator/test/org/jetbrains/kotlin/backend/konan/tests/ObjCExportForwardDeclarationsTest.kt +++ b/native/objcexport-header-generator/test/org/jetbrains/kotlin/backend/konan/tests/ObjCExportForwardDeclarationsTest.kt @@ -6,6 +6,7 @@ package org.jetbrains.kotlin.backend.konan.tests import org.jetbrains.kotlin.backend.konan.testUtils.HeaderGenerator +import org.jetbrains.kotlin.backend.konan.testUtils.TodoAnalysisApi import org.jetbrains.kotlin.backend.konan.testUtils.forwardDeclarationsDir import org.jetbrains.kotlin.test.KotlinTestUtils import org.junit.jupiter.api.Test @@ -17,21 +18,25 @@ class ObjCExportForwardDeclarationsTest( ) { @Test + @TodoAnalysisApi fun `test - function returning interface`() { doTest(forwardDeclarationsDir.resolve("functionReturningInterface")) } @Test + @TodoAnalysisApi fun `test - function returning class`() { doTest(forwardDeclarationsDir.resolve("functionReturningClass")) } @Test + @TodoAnalysisApi fun `test - property returning interface`() { doTest(forwardDeclarationsDir.resolve("propertyReturningInterface")) } @Test + @TodoAnalysisApi fun `test - property returning class`() { doTest(forwardDeclarationsDir.resolve("propertyReturningClass")) } diff --git a/native/objcexport-header-generator/test/org/jetbrains/kotlin/backend/konan/tests/ObjCExportHeaderGeneratorTest.kt b/native/objcexport-header-generator/test/org/jetbrains/kotlin/backend/konan/tests/ObjCExportHeaderGeneratorTest.kt index ccc71e3282c..b7d9bc52f87 100644 --- a/native/objcexport-header-generator/test/org/jetbrains/kotlin/backend/konan/tests/ObjCExportHeaderGeneratorTest.kt +++ b/native/objcexport-header-generator/test/org/jetbrains/kotlin/backend/konan/tests/ObjCExportHeaderGeneratorTest.kt @@ -7,6 +7,7 @@ package org.jetbrains.kotlin.backend.konan.tests import org.jetbrains.kotlin.backend.konan.testUtils.HeaderGenerator import org.jetbrains.kotlin.backend.konan.testUtils.HeaderGenerator.Configuration +import org.jetbrains.kotlin.backend.konan.testUtils.TodoAnalysisApi import org.jetbrains.kotlin.backend.konan.testUtils.headersTestDataDir import org.jetbrains.kotlin.test.KotlinTestUtils import org.junit.jupiter.api.Test @@ -30,7 +31,7 @@ import kotlin.test.fail * 3) Create a test function and call ` doTest(headersTestDataDir.resolve("myTest"))` * 4) The first invocation will fail the test, but generates the header that can be checked in (if sufficient) */ -class ObjCExportHeaderGeneratorTest(val generator: HeaderGenerator) { +class ObjCExportHeaderGeneratorTest(private val generator: HeaderGenerator) { @Test fun `test - simpleClass`() { @@ -43,6 +44,7 @@ class ObjCExportHeaderGeneratorTest(val generator: HeaderGenerator) { } @Test + @TodoAnalysisApi fun `test - simpleEnumClass`() { doTest(headersTestDataDir.resolve("simpleEnumClass")) } @@ -63,16 +65,19 @@ class ObjCExportHeaderGeneratorTest(val generator: HeaderGenerator) { } @Test + @TodoAnalysisApi fun `test - sameClassNameInDifferentPackage`() { doTest(headersTestDataDir.resolve("sameClassNameInDifferentPackage")) } @Test + @TodoAnalysisApi fun `test - nestedClass`() { doTest(headersTestDataDir.resolve("nestedClass")) } @Test + @TodoAnalysisApi fun `test - samePropertyAndFunctionName`() { doTest(headersTestDataDir.resolve("samePropertyAndFunctionName")) } @@ -88,11 +93,13 @@ class ObjCExportHeaderGeneratorTest(val generator: HeaderGenerator) { } @Test + @TodoAnalysisApi fun `test - classWithObjCNameAnnotation`() { doTest(headersTestDataDir.resolve("classWithObjCNameAnnotation")) } @Test + @TodoAnalysisApi fun `test - functionWithObjCNameAnnotation`() { doTest(headersTestDataDir.resolve("functionWithObjCNameAnnotation")) } @@ -108,6 +115,7 @@ class ObjCExportHeaderGeneratorTest(val generator: HeaderGenerator) { } @Test + @TodoAnalysisApi fun `test - functionWithThrowsAnnotation`() { doTest(headersTestDataDir.resolve("functionWithThrowsAnnotation")) } @@ -118,11 +126,13 @@ class ObjCExportHeaderGeneratorTest(val generator: HeaderGenerator) { } @Test + @TodoAnalysisApi fun `test - functionWithErrorTypeAndFrameworkName`() { doTest(headersTestDataDir.resolve("functionWithErrorTypeAndFrameworkName"), Configuration(frameworkName = "shared")) } @Test + @TodoAnalysisApi fun `test - kdocWithBlockTags`() { doTest(headersTestDataDir.resolve("kdocWithBlockTags")) } @@ -143,16 +153,19 @@ class ObjCExportHeaderGeneratorTest(val generator: HeaderGenerator) { } @Test + @TodoAnalysisApi fun `test - parameterWithMustBeDocumentedAnnotation`() { doTest(headersTestDataDir.resolve("parameterWithMustBeDocumentedAnnotation")) } @Test + @TodoAnalysisApi fun `test - receiverWithMustBeDocumentedAnnotation`() { doTest(headersTestDataDir.resolve("receiverWithMustBeDocumentedAnnotation")) } @Test + @TodoAnalysisApi fun `test - dispatchAndExtensionReceiverWithMustBeDocumentedAnnotation`() { doTest(headersTestDataDir.resolve("dispatchAndExtensionReceiverWithMustBeDocumentedAnnotation")) } @@ -168,6 +181,7 @@ class ObjCExportHeaderGeneratorTest(val generator: HeaderGenerator) { } @Test + @TodoAnalysisApi fun `test - topLevelFunctionWithNumberReturn`() { doTest(headersTestDataDir.resolve("topLevelFunctionWithNumberReturn")) } diff --git a/native/objcexport-header-generator/testResources/junit-platform.properties b/native/objcexport-header-generator/testResources/junit-platform.properties new file mode 100644 index 00000000000..d26917a925d --- /dev/null +++ b/native/objcexport-header-generator/testResources/junit-platform.properties @@ -0,0 +1 @@ +junit.jupiter.displayname.generator.default=org.jetbrains.kotlin.backend.konan.testUtils.TestDisplayNameGenerator diff --git a/repo/gradle-build-conventions/buildsrc-compat/src/main/kotlin/objcExportHeaderGeneratorTest.kt b/repo/gradle-build-conventions/buildsrc-compat/src/main/kotlin/objcExportHeaderGeneratorTest.kt new file mode 100644 index 00000000000..b88a644c4e3 --- /dev/null +++ b/repo/gradle-build-conventions/buildsrc-compat/src/main/kotlin/objcExportHeaderGeneratorTest.kt @@ -0,0 +1,29 @@ +import org.gradle.api.Project +import org.gradle.api.tasks.testing.Test + +/* + * Copyright 2010-2024 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. + */ + +/** + * Wrapper for [nativeTest] which helps to apply defaults expected by + * projects under ':native:objcexport-header-generator:*' + */ +fun Project.objCExportHeaderGeneratorTest( + taskName: String, + testDisplayNameTag: String? = null, + configure: Test.() -> Unit = {}, +) = nativeTest( + taskName = taskName, + tag = null, + requirePlatformLibs = false, +) { + useJUnitPlatform() + enableJunit5ExtensionsAutodetection() + systemProperty("is.ci", kotlinBuildProperties.isTeamcityBuild || project.providers.gradleProperty("ci").isPresent) + if (testDisplayNameTag != null) { + systemProperty("testDisplayName.tag", testDisplayNameTag) + } + configure() +}