Files
kotlin-fork/native/kotlin-test-native-xctest/src/nativeMain/kotlin/configuration.kt
T
Pavel Punegov 6b409d87f5 [K/N][test] Support library for running Kotlin/Native tests with XCTest
This change adds a library with cinterop that has XCTest wrapper around
Kotlin/Native tests (that are @kotlin.test.Test marked methods).
This library can be compiled with either test code using the option
`-produce test_bundle` to make a loadable test bundle or used inside
the existing ObjC/Swift tests if compiled to a framework.

The basic idea is to make XCTest be able to resolve separate test cases
and correctly show them in test reports. This was achieved by wrapping
test cases with dynamically created invocation methods. Test listeners
are integrated with XCTest Observation to make it possible to have 
the same ability to report with GTest or TeamCity logging.

Gradle build files use MPP Gradle plugin and use a bootstrap version
of K/N. Property `kotlin.native.home` was moved to the kotlin-native
subproject to not override this project's K/N distribution, that is
being used by the KGP with the same property.

This is a part of ^KT-58928


Merge-request: KT-MR-13268
Merged-by: Pavel Punegov <Pavel.Punegov@jetbrains.com>
2024-01-18 15:04:37 +00:00

110 lines
3.9 KiB
Kotlin

/*
* 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.
*/
@file:Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER")
import kotlinx.cinterop.*
import kotlin.native.internal.test.*
import platform.Foundation.NSInvocation
import platform.Foundation.NSBundle
import platform.Foundation.NSStringFromSelector
import platform.XCTest.*
import platform.objc.*
// Top level suite name used to hold all Native tests
internal const val TOP_LEVEL_SUITE = "Kotlin/Native test suite"
// Name of the key that contains arguments used to set [TestSettings]
private const val TEST_ARGUMENTS_KEY = "KotlinNativeTestArgs"
/**
* Stores current settings with the filtered test suites, loggers, and listeners.
* Test settings should be initialized by the setup method.
*/
private lateinit var testSettings: TestSettings
/**
* This is an entry-point of XCTestSuites and XCTestCases generation.
* Function returns the XCTest's top level TestSuite that holds all the test cases
* with K/N tests.
* This test suite can be run by either native launcher compiled to bundle or
* by the other test suite (e.g. compiled as a framework).
*/
@Suppress("unused")
@kotlin.native.internal.ExportForCppRuntime("Konan_create_testSuite")
internal fun setupXCTestSuite(): XCTestSuite {
val nativeTestSuite = XCTestSuite.testSuiteWithName(TOP_LEVEL_SUITE)
// Initialize settings with the given args
val args = testArguments(TEST_ARGUMENTS_KEY)
testSettings = TestProcessor(GeneratedSuites.suites, args).process()
check(::testSettings.isInitialized) {
"Test settings wasn't set. Check provided arguments and test suites"
}
// Set test observer that will log test execution
XCTestObservationCenter.sharedTestObservationCenter.addTestObserver(NativeTestObserver(testSettings))
if (testSettings.runTests == true) {
// Generate and add tests to the main suite
testSettings.testSuites.generate().forEach {
nativeTestSuite.addTest(it)
}
// Tests created (self-check)
@Suppress("UNCHECKED_CAST")
check(testSettings.testSuites.size == (nativeTestSuite.tests as List<XCTest>).size) {
"The amount of generated XCTest suites should be equal to Kotlin test suites"
}
}
return nativeTestSuite
}
/**
* Gets test arguments from the Info.plist using the provided key to create test settings.
*
* @param key a key used in the `Info.plist` file to pass test arguments
*/
private fun testArguments(key: String): Array<String> {
// As we don't know which bundle we are, iterate through all of them
val plistTestArgs = NSBundle.allBundles
.mapNotNull {
(it as? NSBundle)?.infoDictionary?.get(key)
}.singleOrNull() as? String
return plistTestArgs?.split(" ")
?.toTypedArray()
?: emptyArray<String>()
}
internal val testMethodsNames: List<String>
get() = testSettings.testSuites.toList()
.flatMap { testSuite ->
testSuite.testCases.values.map { it.fullName }
}
internal val TestCase.fullName get() = "${suite.name}.$name"
private fun Collection<TestSuite>.generate(): List<XCTestSuite> {
val testInvocations = XCTestCaseWrapper.testInvocations()
return this.map { suite ->
val xcSuite = XCTestSuiteWrapper(suite)
suite.testCases.values.map { testCase ->
// Produce test case wrapper from the test invocation
testInvocations.filter {
it.selectorString() == testCase.fullName
}.map { invocation ->
XCTestCaseWrapper(invocation, testCase)
}.single()
}.forEach {
// add test to its test suite wrappper
xcSuite.addTest(it)
}
xcSuite
}
}
private fun NSInvocation.selectorString() = NSStringFromSelector(selector)