[ObjCExport] Implement 'TodoAnalysisApi' annotation and run AA based tests as part of ':native:objcexport-header-generator:check'

^KT-65281 Fixed
^KT-65108 Fixed
This commit is contained in:
Sebastian Sellmair
2024-01-25 11:29:38 +01:00
committed by Space Team
parent 55adeba011
commit 0db8931026
14 changed files with 180 additions and 17 deletions
@@ -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
// ^
```
@@ -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")
}
@@ -27,7 +27,4 @@ sourceSets {
testsJar()
nativeTest("test", tag = null, requirePlatformLibs = true) {
useJUnitPlatform()
enableJunit5ExtensionsAutodetection()
}
objCExportHeaderGeneratorTest("test")
@@ -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")
}
}
}
@@ -5,3 +5,4 @@
org.jetbrains.kotlin.objcexport.testUtils.AnalysisApiHeaderGeneratorExtension
org.jetbrains.kotlin.objcexport.testUtils.InlineSourceCodeAnalysisExtension
org.jetbrains.kotlin.objcexport.testUtils.AnalysisApiBaseDeclarationsGeneratorExtension
org.jetbrains.kotlin.objcexport.testUtils.TodoAnalysisApiTestExecutionExceptionHandler
@@ -25,7 +25,4 @@ kotlin {
testsJar()
nativeTest("test", tag = null, requirePlatformLibs = true) {
useJUnitPlatform()
enableJunit5ExtensionsAutodetection()
}
objCExportHeaderGeneratorTest("test")
@@ -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")
}
}
}
@@ -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
@@ -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")
@@ -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")
}
@@ -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"))
}
@@ -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"))
}
@@ -0,0 +1 @@
junit.jupiter.displayname.generator.default=org.jetbrains.kotlin.backend.konan.testUtils.TestDisplayNameGenerator
@@ -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()
}