refactored OsCondition behavior with multiple same annotations on the same target

#KT-51553 In Progress
This commit is contained in:
Dmitrii Krasnov
2023-05-11 15:17:44 +02:00
committed by Space Team
parent dd9e127d2c
commit 548ebbaf90
12 changed files with 79 additions and 57 deletions
@@ -18,7 +18,6 @@ import java.lang.annotation.Target;
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@GradleTestVersions
@OsCondition
@AndroidTestVersions
@ParameterizedTest(name = "AGP {1} with {0}: {displayName}")
@ArgumentsSource(GradleAndAgpArgumentsProvider.class)
@@ -17,7 +17,6 @@ import java.lang.annotation.Target;
// Workaround for https://youtrack.jetbrains.com/issue/IDEA-265284
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@OsCondition
@GradleTestVersions
@ParameterizedTest(name = "{0}: {displayName}")
@ArgumentsSource(GradleArgumentsProvider.class)
@@ -18,7 +18,6 @@ import java.lang.annotation.Target;
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@GradleTestVersions
@OsCondition
@JdkVersions
@ParameterizedTest(name = "{1} with {0}: {displayName}")
@ArgumentsSource(GradleAndJdkArgumentsProvider.class)
@@ -18,7 +18,6 @@ package org.jetbrains.kotlin.gradle
import org.gradle.api.JavaVersion
import org.gradle.api.logging.LogLevel
import org.gradle.api.logging.configuration.WarningMode
import org.gradle.testkit.runner.BuildResult
import org.gradle.util.GradleVersion
import org.jetbrains.kotlin.gradle.tasks.USING_JVM_INCREMENTAL_COMPILATION_MESSAGE
@@ -28,7 +27,6 @@ import org.jetbrains.kotlin.gradle.util.checkedReplace
import org.jetbrains.kotlin.gradle.util.testResolveAllConfigurations
import org.junit.jupiter.api.Disabled
import org.junit.jupiter.api.DisplayName
import org.junit.jupiter.api.condition.EnabledOnOs
import org.junit.jupiter.api.condition.OS
import java.nio.file.Files
import java.util.zip.ZipFile
@@ -1030,7 +1028,7 @@ open class Kapt3IT : Kapt3BaseIT() {
@DisplayName("KT-52392: Setup with different windows disks does not fail configuration")
@GradleTest
@EnabledOnOs(OS.WINDOWS)
@OsCondition(supportedOn = [OS.WINDOWS], enabledOnCI = [OS.WINDOWS])
fun testDifferentDisksSetupDoesNotFailConfiguration(gradleVersion: GradleVersion) {
project("simple".withPrefix, gradleVersion) {
fun findAnotherRoot() = ('A'..'Z').first { !projectPath.root.startsWith(it.toString()) }
@@ -10,11 +10,10 @@ import org.jetbrains.kotlin.gradle.testbase.*
import org.jetbrains.kotlin.konan.target.HostManager
import org.jetbrains.kotlin.konan.target.KonanTarget
import org.junit.jupiter.api.DisplayName
import org.junit.jupiter.api.condition.EnabledOnOs
import org.junit.jupiter.api.condition.OS
import kotlin.test.fail
@EnabledOnOs(OS.MAC, disabledReason = "Test requires MacOS host")
@OsCondition(supportedOn = [OS.MAC], enabledOnCI = [OS.MAC])
@DisplayName("Tests for Apple Silicon builds")
@NativeGradlePluginTests
class AppleSiliconIT : KGPBaseTest() {
@@ -9,7 +9,6 @@ import org.gradle.api.logging.LogLevel
import org.gradle.util.GradleVersion
import org.jetbrains.kotlin.gradle.testbase.*
import org.junit.jupiter.api.DisplayName
import org.junit.jupiter.api.condition.EnabledOnOs
import org.junit.jupiter.api.condition.OS
import kotlin.io.path.appendText
@@ -98,7 +97,7 @@ class NativeLibraryDslIT : KGPBaseTest() {
}
}
@EnabledOnOs(OS.MAC)
@OsCondition(supportedOn = [OS.MAC], enabledOnCI = [OS.MAC])
@DisplayName("Links release XCFramework from two gradle modules")
@GradleTest
fun shouldLinkXCFrameworkFromTwoModules(gradleVersion: GradleVersion) {
@@ -8,11 +8,10 @@ package org.jetbrains.kotlin.gradle.native
import org.gradle.util.GradleVersion
import org.jetbrains.kotlin.gradle.testbase.*
import org.junit.jupiter.api.DisplayName
import org.junit.jupiter.api.condition.EnabledOnOs
import org.junit.jupiter.api.condition.OS
import kotlin.io.path.appendText
@EnabledOnOs(OS.MAC)
@OsCondition(supportedOn = [OS.MAC], enabledOnCI = [OS.MAC])
@DisplayName("Tests for K/N with cocoapods")
@NativeGradlePluginTests
class NativeLibraryDslWithCocoapodsIT : KGPBaseTest() {
@@ -12,14 +12,13 @@ import org.jetbrains.kotlin.gradle.utils.Xcode
import org.jetbrains.kotlin.konan.target.KonanTarget
import org.junit.jupiter.api.AfterAll
import org.junit.jupiter.api.DisplayName
import org.junit.jupiter.api.condition.EnabledOnOs
import org.junit.jupiter.api.condition.OS
import java.io.File
import kotlin.test.assertEquals
@DisplayName("tests for the K/N XCode simulator test infrastructure")
@NativeGradlePluginTests
@EnabledOnOs(OS.MAC)
@OsCondition(supportedOn = [OS.MAC], enabledOnCI = [OS.MAC])
class NativeXcodeSimulatorTestsIT : KGPBaseTest() {
private val defaultIosSimulator by lazy {
val xcode = Xcode ?: error("XCode is expected to be defined")
@@ -10,10 +10,9 @@ import org.jetbrains.kotlin.gradle.testbase.*
import org.jetbrains.kotlin.gradle.util.replaceFirst
import org.jetbrains.kotlin.gradle.util.replaceText
import org.junit.jupiter.api.DisplayName
import org.junit.jupiter.api.condition.EnabledOnOs
import org.junit.jupiter.api.condition.OS
@EnabledOnOs(OS.MAC)
@OsCondition(supportedOn = [OS.MAC], enabledOnCI = [OS.MAC])
@DisplayName("Tests for K/N with apple XCFramework")
@NativeGradlePluginTests
class XCFrameworkIT : KGPBaseTest() {
@@ -28,6 +28,7 @@ import java.nio.file.Path
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
@WithMuteInDatabase
@TestDataPath("\$CONTENT_ROOT/resources/testProject")
@OsCondition
abstract class KGPBaseTest {
open val defaultBuildOptions = BuildOptions()
@@ -23,6 +23,35 @@ annotation class GradleTestVersions(
val additionalVersions: Array<String> = []
)
inline fun <reified T : Annotation> findAnnotation(context: ExtensionContext): T {
var nextSuperclass: Class<*>? = context.testClass.get().superclass
val superClassSequence = if (nextSuperclass != null) {
generateSequence {
val currentSuperclass = nextSuperclass
nextSuperclass = nextSuperclass?.superclass
currentSuperclass
}
} else {
emptySequence()
}
return sequenceOf(
context.testMethod.orElse(null),
context.testClass.orElse(null)
)
.filterNotNull()
.plus(superClassSequence)
.mapNotNull { declaration ->
declaration.annotations.firstOrNull { it is T }
}
.firstOrNull() as T?
?: context.testMethod.get().annotations
.mapNotNull { annotation ->
annotation.annotationClass.annotations.firstOrNull { it is T }
}
.first() as T
}
open class GradleArgumentsProvider : ArgumentsProvider {
override fun provideArguments(
context: ExtensionContext
@@ -48,34 +77,6 @@ open class GradleArgumentsProvider : ArgumentsProvider {
.map { Arguments.of(it) }
.asStream()
}
inline fun <reified T : Annotation> findAnnotation(context: ExtensionContext): T {
var nextSuperclass: Class<*>? = context.testClass.get().superclass
val superClassSequence = if (nextSuperclass != null) {
generateSequence {
val currentSuperclass = nextSuperclass
nextSuperclass = nextSuperclass?.superclass
currentSuperclass
}
} else {
emptySequence()
}
return sequenceOf(
context.testMethod.get(),
context.testClass.get()
)
.plus(superClassSequence)
.mapNotNull { declaration ->
declaration.annotations.firstOrNull { it is T }
}
.firstOrNull() as T?
?: context.testMethod.get().annotations
.mapNotNull { annotation ->
annotation.annotationClass.annotations.firstOrNull { it is T }
}
.first() as T
}
}
@Target(AnnotationTarget.FUNCTION, AnnotationTarget.ANNOTATION_CLASS, AnnotationTarget.CLASS)
@@ -10,37 +10,67 @@ import org.junit.jupiter.api.extension.ConditionEvaluationResult
import org.junit.jupiter.api.extension.ExecutionCondition
import org.junit.jupiter.api.extension.ExtendWith
import org.junit.jupiter.api.extension.ExtensionContext
import org.junit.platform.commons.util.AnnotationUtils
import org.junit.platform.commons.logging.LoggerFactory
import java.lang.reflect.AnnotatedElement
import java.util.*
/**
* An annotation that enables tests to be executed on a specific operating system within a specific environment.
*
* @param supportedOn Declares all the systems, on which this test can be executed in general, no matter on which environment.
* @param enabledOnCI Declares only platforms on which this test should be run in the TeamCity builds
* (that helps us not to overload such platforms like Mac OS).
*
* ## Warning
* If this annotation is directly present, indirectly present, or meta-present multiple times on a given
* element, only the first restrictions of each declaration level will be applied,
* but all the restrictions from different levels will be applied.
* For example, if you have this annotation on both the class level and method level,
* the method level annotation can only narrow the scope of the class level annotation.
*
* Exception: When used in a superclass declaration,
* this declaration does not compete with other declaration levels.
* It works only if it is the only declaration present for the superclass.
*/
@Target(AnnotationTarget.FUNCTION, AnnotationTarget.ANNOTATION_CLASS, AnnotationTarget.CLASS)
@Retention(AnnotationRetention.RUNTIME)
@ExtendWith(ExecutionOnOsCondition::class)
annotation class OsCondition(
val supportedOn: Array<OS> = [OS.LINUX, OS.MAC, OS.WINDOWS],
val enabledOnCI: Array<OS> = [OS.LINUX, OS.WINDOWS]
val enabledOnCI: Array<OS> = [OS.LINUX, OS.WINDOWS],
)
internal class ExecutionOnOsCondition : ExecutionCondition {
private val logger = LoggerFactory.getLogger(ExecutionOnOsCondition::class.java)
private val isUnderTeamcity = System.getenv("TEAMCITY_VERSION") != null
private val disabledByDefault = ConditionEvaluationResult.disabled("@OsCondition is not present")
private val enabledOnCurrentOs = "Enabled on operating system: " + System.getProperty("os.name")
private val notSupportedOnCurrentOs = "Test is not supported on operating system: " + System.getProperty("os.name")
private val disabledForCI = "Disabled for operating system: " + System.getProperty("os.name") + " on CI"
override fun evaluateExecutionCondition(context: ExtensionContext): ConditionEvaluationResult {
val annotation = AnnotationUtils.findAnnotation(context.element, OsCondition::class.java)
if (annotation.isPresent) {
val supportedOn = annotation.get().supportedOn
val enabledOnCI = annotation.get().enabledOnCI
val annotation = findAnnotation<OsCondition>(context)
return if (!supportedOn.any { it.isCurrentOs })
ConditionEvaluationResult.disabled(notSupportedOnCurrentOs)
else if (isUnderTeamcity && !enabledOnCI.any { it.isCurrentOs })
ConditionEvaluationResult.disabled(disabledForCI)
else ConditionEvaluationResult.enabled(enabledOnCurrentOs)
val supportedOn = annotation.supportedOn
val enabledOnCI = annotation.enabledOnCI
return if (supportedOn.none { it.isCurrentOs }) {
logger.info { createDisabledMessage(context.element.get(), "local", supportedOn) }
ConditionEvaluationResult.disabled(notSupportedOnCurrentOs)
} else if (isUnderTeamcity && enabledOnCI.none { it.isCurrentOs }) {
logger.info { createDisabledMessage(context.element.get(), "TeamCity", enabledOnCI) }
ConditionEvaluationResult.disabled(disabledForCI)
} else {
ConditionEvaluationResult.enabled(enabledOnCurrentOs)
}
}
return disabledByDefault
private fun createDisabledMessage(annotatedElement: AnnotatedElement, environment: String, allowedOS: Array<OS>): String {
return "$annotatedElement" +
" was disabled in the $environment environment" +
" for the current os=${OS.current()}," +
" because allowed environments are: ${allowedOS.joinToString(separator = ", ") { it.name }}"
}
}