diff --git a/native/native.tests/tests/org/jetbrains/kotlin/konan/test/blackbox/support/group/ExtTestCaseGroupProvider.kt b/native/native.tests/tests/org/jetbrains/kotlin/konan/test/blackbox/support/group/ExtTestCaseGroupProvider.kt index 06e27baf174..499f32ef5a9 100644 --- a/native/native.tests/tests/org/jetbrains/kotlin/konan/test/blackbox/support/group/ExtTestCaseGroupProvider.kt +++ b/native/native.tests/tests/org/jetbrains/kotlin/konan/test/blackbox/support/group/ExtTestCaseGroupProvider.kt @@ -20,14 +20,9 @@ import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment import org.jetbrains.kotlin.config.CommonConfigurationKeys import org.jetbrains.kotlin.config.CompilerConfiguration import org.jetbrains.kotlin.config.LanguageFeature -import org.jetbrains.kotlin.konan.target.Architecture -import org.jetbrains.kotlin.konan.target.Family import org.jetbrains.kotlin.konan.test.blackbox.support.* import org.jetbrains.kotlin.konan.test.blackbox.support.TestCase.WithTestRunnerExtras import org.jetbrains.kotlin.konan.test.blackbox.support.TestDirectives.ASSERTIONS_MODE -import org.jetbrains.kotlin.konan.test.blackbox.support.TestDirectives.DISABLE_NATIVE -import org.jetbrains.kotlin.konan.test.blackbox.support.TestDirectives.DISABLE_NATIVE_K1 -import org.jetbrains.kotlin.konan.test.blackbox.support.TestDirectives.DISABLE_NATIVE_K2 import org.jetbrains.kotlin.konan.test.blackbox.support.TestDirectives.FILECHECK_STAGE import org.jetbrains.kotlin.konan.test.blackbox.support.TestDirectives.FREE_CINTEROP_ARGS import org.jetbrains.kotlin.konan.test.blackbox.support.TestDirectives.FREE_COMPILER_ARGS @@ -48,10 +43,6 @@ import org.jetbrains.kotlin.resolve.ImportPath import org.jetbrains.kotlin.resolve.checkers.OptInNames import org.jetbrains.kotlin.test.* import org.jetbrains.kotlin.test.InTextDirectivesUtils.* -import org.jetbrains.kotlin.test.directives.CodegenTestDirectives.IGNORE_BACKEND -import org.jetbrains.kotlin.test.directives.CodegenTestDirectives.IGNORE_BACKEND_K1 -import org.jetbrains.kotlin.test.directives.CodegenTestDirectives.IGNORE_BACKEND_K2 -import org.jetbrains.kotlin.test.directives.model.StringDirective import org.jetbrains.kotlin.test.services.JUnit5Assertions.assertTrue import org.jetbrains.kotlin.test.services.JUnit5Assertions.assertFalse import org.jetbrains.kotlin.test.services.JUnit5Assertions.fail @@ -918,108 +909,6 @@ internal fun Settings.isIgnoredTarget(testDataFile: File): Boolean { } } -internal fun Settings.isIgnoredTarget(directives: Directives): Boolean { - return isIgnoredWithIGNORE_BACKEND(directives) || - isIgnoredWithIGNORE_NATIVE(directives) -} - -// Mimics `InTextDirectivesUtils.isIgnoredTarget(NATIVE, file)` but does not require file contents, but only already parsed directives. -private fun Settings.isIgnoredWithIGNORE_BACKEND(directives: Directives): Boolean { - val containsNativeOrAny: (List) -> Boolean = { TargetBackend.NATIVE.name in it || TargetBackend.ANY.name in it } - - if (directives.listValues(IGNORE_BACKEND.name)?.let(containsNativeOrAny) == true) - return true - when (get()) { - PipelineType.K1 -> - if (directives.listValues(IGNORE_BACKEND_K1.name)?.let(containsNativeOrAny) == true) - return true - PipelineType.K2 -> - if (directives.listValues(IGNORE_BACKEND_K2.name)?.let(containsNativeOrAny) == true) - return true - else -> {} - } - return false -} - -private val TARGET_FAMILY = "targetFamily" -private val TARGET_ARCHITECTURE = "targetArchitecture" -private val IS_APPLE_TARGET = "isAppleTarget" -private val CACHE_MODE_NAMES = CacheMode.Alias.entries.map { it.name } -private val TEST_MODE_NAMES = TestMode.entries.map { it.name } -private val OPTIMIZATION_MODE_NAMES = OptimizationMode.entries.map { it.name } -private val GC_TYPE_NAMES = GCType.entries.map { it.name } -private val GC_SCHEDULER_NAMES = GCScheduler.entries.map { it.name } -private val THREAD_STATE_CHECKER_NAMES = ThreadStateChecker.entries.map { it.name } -private val FAMILY_NAMES = Family.entries.map { it.name } -private val ARCHITECTURE_NAMES = Architecture.entries.map { it.name } -private val BOOLEAN_NAMES = listOf(true.toString(), false.toString()) - -private fun Settings.isDisabledNative(directives: Directives) = - evaluate(getDirectiveValues(directives, DISABLE_NATIVE, DISABLE_NATIVE_K1, DISABLE_NATIVE_K2)) - -private fun Settings.isIgnoredWithIGNORE_NATIVE(directives: Directives) = - evaluate(getDirectiveValues(directives, IGNORE_NATIVE, IGNORE_NATIVE_K1, IGNORE_NATIVE_K2)) - -// Evaluation of conjunction of boolean expressions like `property1=value1 && property2=value2`. -// Any null element makes whole result as `true`. -private fun Settings.evaluate(directiveValues: List): Boolean { - directiveValues.forEach { - if (it == null) - return true // Directive without value is treated as unconditional - val split = it.split("&&") - val booleanList = split.map { - val matchResult = "(.+)=(.+)".toRegex().find(it.trim()) - ?: throw AssertionError("Invalid format for IGNORE_NATIVE* directive ($it). Must be =") - val propName = matchResult.groups[1]?.value - val (actualValue, supportedValues) = when (propName) { - ClassLevelProperty.CACHE_MODE.shortName -> get().alias.name to CACHE_MODE_NAMES - ClassLevelProperty.TEST_MODE.shortName -> get().name to TEST_MODE_NAMES - ClassLevelProperty.OPTIMIZATION_MODE.shortName -> get().name to OPTIMIZATION_MODE_NAMES - ClassLevelProperty.TEST_TARGET.shortName -> get().testTarget.name to null - ClassLevelProperty.GC_TYPE.shortName -> get().name to GC_TYPE_NAMES - ClassLevelProperty.GC_SCHEDULER.shortName -> get().name to GC_SCHEDULER_NAMES - ClassLevelProperty.USE_THREAD_STATE_CHECKER.shortName -> get().name to THREAD_STATE_CHECKER_NAMES - TARGET_FAMILY -> get().testTarget.family.name to FAMILY_NAMES - TARGET_ARCHITECTURE -> get().testTarget.architecture.name to ARCHITECTURE_NAMES - IS_APPLE_TARGET -> get().testTarget.family.isAppleFamily.toString() to BOOLEAN_NAMES - else -> throw AssertionError("ClassLevelProperty name: $propName is not yet supported in IGNORE_NATIVE* test directives.") - } - val valueFromTestDirective = matchResult.groups[2]?.value!! - supportedValues?.let { - if (actualValue !in it) - throw AssertionError("Internal error: Test run value $propName=$actualValue is not in expected supported values: $it") - if (valueFromTestDirective !in it) - throw AssertionError("Test directive `IGNORE_NATIVE*: $propName=$valueFromTestDirective` has unsupported value. Supported are: $it") - } - actualValue == valueFromTestDirective - } - val matches = booleanList.reduce { a, b -> a && b } - if (matches) - return true - } - return false -} - -// Returns list of relevant directive values. -// Null is added to result list in case the directive given without value. -private fun Settings.getDirectiveValues( - directives: Directives, - directiveAllPipelineTypes: StringDirective, - directiveK1: StringDirective, - directiveK2: StringDirective -): List = buildList { - fun extract(directives: Directives, directive: StringDirective) { - if (directives.contains(directive.name)) - directives.listValues(directive.name)?.let { addAll(it) } ?: add(null) - } - extract(directives, directiveAllPipelineTypes) - when (get()) { - PipelineType.K1 -> extract(directives, directiveK1) - PipelineType.K2 -> extract(directives, directiveK2) - else -> {} - } -} - private val KtFile.packageFqNameForKLib: FqName get() = when (name.substringAfterLast(".")) { "kt" -> packageFqName diff --git a/native/native.tests/tests/org/jetbrains/kotlin/konan/test/blackbox/support/group/IgnoreDisableHelper.kt b/native/native.tests/tests/org/jetbrains/kotlin/konan/test/blackbox/support/group/IgnoreDisableHelper.kt new file mode 100644 index 00000000000..b4f2f20a147 --- /dev/null +++ b/native/native.tests/tests/org/jetbrains/kotlin/konan/test/blackbox/support/group/IgnoreDisableHelper.kt @@ -0,0 +1,173 @@ +/* + * 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.konan.test.blackbox.support.group + +import org.jetbrains.kotlin.konan.target.Architecture +import org.jetbrains.kotlin.konan.target.Family +import org.jetbrains.kotlin.konan.test.blackbox.support.ClassLevelProperty +import org.jetbrains.kotlin.konan.test.blackbox.support.TestDirectives +import org.jetbrains.kotlin.konan.test.blackbox.support.settings.CacheMode +import org.jetbrains.kotlin.konan.test.blackbox.support.settings.GCScheduler +import org.jetbrains.kotlin.konan.test.blackbox.support.settings.GCType +import org.jetbrains.kotlin.konan.test.blackbox.support.settings.KotlinNativeTargets +import org.jetbrains.kotlin.konan.test.blackbox.support.settings.OptimizationMode +import org.jetbrains.kotlin.konan.test.blackbox.support.settings.PipelineType +import org.jetbrains.kotlin.konan.test.blackbox.support.settings.Settings +import org.jetbrains.kotlin.konan.test.blackbox.support.settings.TestMode +import org.jetbrains.kotlin.konan.test.blackbox.support.settings.ThreadStateChecker +import org.jetbrains.kotlin.test.Directives +import org.jetbrains.kotlin.test.TargetBackend +import org.jetbrains.kotlin.test.directives.CodegenTestDirectives +import org.jetbrains.kotlin.test.directives.model.RegisteredDirectives +import org.jetbrains.kotlin.test.directives.model.StringDirective +import org.jetbrains.kotlin.test.directives.model.ValueDirective + +private val TARGET_FAMILY = "targetFamily" +private val TARGET_ARCHITECTURE = "targetArchitecture" +private val IS_APPLE_TARGET = "isAppleTarget" +private val CACHE_MODE_NAMES = CacheMode.Alias.entries.map { it.name } +private val TEST_MODE_NAMES = TestMode.entries.map { it.name } +private val OPTIMIZATION_MODE_NAMES = OptimizationMode.entries.map { it.name } +private val GC_TYPE_NAMES = GCType.entries.map { it.name } +private val GC_SCHEDULER_NAMES = GCScheduler.entries.map { it.name } +private val THREAD_STATE_CHECKER_NAMES = ThreadStateChecker.entries.map { it.name } +private val FAMILY_NAMES = Family.entries.map { it.name } +private val ARCHITECTURE_NAMES = Architecture.entries.map { it.name } +private val BOOLEAN_NAMES = listOf(true.toString(), false.toString()) + +// Note: this method would accept DISABLED_NATIVE without parameters as an unconditional test exclusion: don't even try to compile +internal fun Settings.isDisabledNative(directives: Directives) = + evaluate( + getDirectiveValues( + TestDirectives.DISABLE_NATIVE, TestDirectives.DISABLE_NATIVE_K1, TestDirectives.DISABLE_NATIVE_K2, + { directives.contains(it.name) }, + { directives.listValues(it.name) }, + ) + ) + +// Note: this method would ignore DISABLED_NATIVE without parameters, since it would be not a StringDirective, but new SimpleDirective +internal fun Settings.isDisabledNative(registeredDirectives: RegisteredDirectives) = + evaluate( + getDirectiveValues( + TestDirectives.DISABLE_NATIVE, TestDirectives.DISABLE_NATIVE_K1, TestDirectives.DISABLE_NATIVE_K2, + { registeredDirectives.contains(it) }, + { registeredDirectives.get(it) }, + ) + ) + +// Note: this method would treat IGNORE_NATIVE without parameters as an unconditional "test must fail on any config". Same as // IGNORE_BACKEND: NATIVE +internal fun Settings.isIgnoredWithIGNORE_NATIVE(directives: Directives) = + evaluate( + getDirectiveValues( + TestDirectives.IGNORE_NATIVE, TestDirectives.IGNORE_NATIVE_K1, TestDirectives.IGNORE_NATIVE_K2, + { directives.contains(it.name) }, + { directives.listValues(it.name) }, + ) + ) + +// Note: this method would ignore IGNORE_NATIVE without parameters, since it would be not a StringDirective, but new SimpleDirective +internal fun Settings.isIgnoredWithIGNORE_NATIVE(registeredDirectives: RegisteredDirectives) = + evaluate( + getDirectiveValues( + TestDirectives.IGNORE_NATIVE, TestDirectives.IGNORE_NATIVE_K1, TestDirectives.IGNORE_NATIVE_K2, + { registeredDirectives.contains(it) }, + { registeredDirectives.get(it) }, + ) + ) + +// Note: this method would treat IGNORE_NATIVE without parameters as an unconditional "test must fail on any config". Same as // IGNORE_BACKEND: NATIVE +internal fun Settings.isIgnoredTarget(directives: Directives): Boolean { + return isIgnoredWithIGNORE_NATIVE(directives) || isIgnoredWithIGNORE_BACKEND { valueDirective -> + directives.listValues(valueDirective.name)?.map { backendName -> + enumValues().first { it.name == backendName } + } + } +} + +// Note: this method would ignore IGNORE_NATIVE without parameters, since it would be not a StringDirective, but new SimpleDirective +internal fun Settings.isIgnoredTarget(registeredDirectives: RegisteredDirectives): Boolean { + return isIgnoredWithIGNORE_NATIVE(registeredDirectives) || isIgnoredWithIGNORE_BACKEND { registeredDirectives.get(it) } +} + +// Mimics `InTextDirectivesUtils.isIgnoredTarget(NATIVE, file)` but does not require file contents, but only already parsed directives. +private fun Settings.isIgnoredWithIGNORE_BACKEND(listValues: (ValueDirective) -> List?): Boolean { + val containsNativeOrAny: (List) -> Boolean = { TargetBackend.NATIVE in it || TargetBackend.ANY in it } + + if (listValues(CodegenTestDirectives.IGNORE_BACKEND)?.let(containsNativeOrAny) == true) + return true + when (get()) { + PipelineType.K1 -> + if (listValues(CodegenTestDirectives.IGNORE_BACKEND_K1)?.let(containsNativeOrAny) == true) + return true + PipelineType.K2 -> + if (listValues(CodegenTestDirectives.IGNORE_BACKEND_K2)?.let(containsNativeOrAny) == true) + return true + else -> {} + } + return false +} + + +// Evaluation of conjunction of boolean expressions like `property1=value1 && property2=value2`. +// Any null element makes whole result as `true`. +internal fun Settings.evaluate(directiveValues: List): Boolean { + directiveValues.forEach { + if (it == null) + return true // Directive without value is treated as unconditional + val split = it.split("&&") + val booleanList = split.map { + val matchResult = "(.+)=(.+)".toRegex().find(it.trim()) + ?: throw AssertionError("Invalid format for IGNORE_NATIVE* directive ($it). Must be =") + val propName = matchResult.groups[1]?.value + val (actualValue, supportedValues) = when (propName) { + ClassLevelProperty.CACHE_MODE.shortName -> get().alias.name to CACHE_MODE_NAMES + ClassLevelProperty.TEST_MODE.shortName -> get().name to TEST_MODE_NAMES + ClassLevelProperty.OPTIMIZATION_MODE.shortName -> get().name to OPTIMIZATION_MODE_NAMES + ClassLevelProperty.TEST_TARGET.shortName -> get().testTarget.name to null + ClassLevelProperty.GC_TYPE.shortName -> get().name to GC_TYPE_NAMES + ClassLevelProperty.GC_SCHEDULER.shortName -> get().name to GC_SCHEDULER_NAMES + ClassLevelProperty.USE_THREAD_STATE_CHECKER.shortName -> get().name to THREAD_STATE_CHECKER_NAMES + TARGET_FAMILY -> get().testTarget.family.name to FAMILY_NAMES + TARGET_ARCHITECTURE -> get().testTarget.architecture.name to ARCHITECTURE_NAMES + IS_APPLE_TARGET -> get().testTarget.family.isAppleFamily.toString() to BOOLEAN_NAMES + else -> throw AssertionError("ClassLevelProperty name: $propName is not yet supported in IGNORE_NATIVE* test directives.") + } + val valueFromTestDirective = matchResult.groups[2]?.value!! + supportedValues?.let { + if (actualValue !in it) + throw AssertionError("Internal error: Test run value $propName=$actualValue is not in expected supported values: $it") + if (valueFromTestDirective !in it) + throw AssertionError("Test directive `IGNORE_NATIVE*: $propName=$valueFromTestDirective` has unsupported value. Supported are: $it") + } + actualValue == valueFromTestDirective + } + val matches = booleanList.reduce { a, b -> a && b } + if (matches) + return true + } + return false +} + +// Returns list of relevant directive values. +// Null is added to result list in case the directive given without value. +internal fun Settings.getDirectiveValues( + directiveAllPipelineTypes: StringDirective, + directiveK1: StringDirective, + directiveK2: StringDirective, + isSpecified: (StringDirective) -> Boolean, + listValues: (StringDirective) -> List?, +): List = buildList { + fun extract(directive: StringDirective) { + if (isSpecified(directive)) + listValues(directive)?.let { addAll(it) } ?: add(null) + } + extract(directiveAllPipelineTypes) + when (get()) { + PipelineType.K1 -> extract(directiveK1) + PipelineType.K2 -> extract(directiveK2) + else -> {} + } +} diff --git a/native/native.tests/tests/org/jetbrains/kotlin/konan/test/blackbox/support/group/StandardTestCaseGroupProvider.kt b/native/native.tests/tests/org/jetbrains/kotlin/konan/test/blackbox/support/group/StandardTestCaseGroupProvider.kt index e57d0f528bf..cecf78d66ef 100644 --- a/native/native.tests/tests/org/jetbrains/kotlin/konan/test/blackbox/support/group/StandardTestCaseGroupProvider.kt +++ b/native/native.tests/tests/org/jetbrains/kotlin/konan/test/blackbox/support/group/StandardTestCaseGroupProvider.kt @@ -46,7 +46,10 @@ internal class StandardTestCaseGroupProvider : TestCaseGroupProvider { val disabledTestCaseIds = hashSetOf() excludedTestDataFiles.mapTo(disabledTestCaseIds, TestCaseId::TestDataFile) - val testCases = includedTestDataFiles.map { testDataFile -> createTestCase(testDataFile, settings) } + val testCases = includedTestDataFiles.mapNotNull { testDataFile -> createTestCase(testDataFile, settings).also { + if (it == null) disabledTestCaseIds += TestCaseId.TestDataFile(testDataFile) + } + } val lldbTestCases = testCases.filter { it.kind == TestKind.STANDALONE_LLDB } if (lldbTestCases.isNotEmpty() @@ -61,7 +64,7 @@ internal class StandardTestCaseGroupProvider : TestCaseGroupProvider { } } - private fun createTestCase(testDataFile: File, settings: Settings): TestCase { + private fun createTestCase(testDataFile: File, settings: Settings): TestCase? { val generatedSourcesDir = computeGeneratedSourcesDir( testDataBaseDir = settings.get().baseDir, testDataFile = testDataFile, @@ -194,6 +197,9 @@ internal class StandardTestCaseGroupProvider : TestCaseGroupProvider { val registeredDirectives = directivesParser.build() + if (settings.isDisabledNative(registeredDirectives)) + return null + val freeCompilerArgs = parseFreeCompilerArgs(registeredDirectives, location) val expectedTimeoutFailure = parseExpectedTimeoutFailure(registeredDirectives) @@ -216,6 +222,7 @@ internal class StandardTestCaseGroupProvider : TestCaseGroupProvider { modules = testModules.values.toSet(), freeCompilerArgs = freeCompilerArgs, nominalPackageName = nominalPackageName, + expectedFailure = settings.isIgnoredTarget(registeredDirectives), checks = TestRunChecks( computeExecutionTimeoutCheck(settings, expectedTimeoutFailure), computeExitCodeCheck(testKind, registeredDirectives, location),