[Native][tests] Introduce EXPECTED_TIMEOUT_FAILURE directive

It allows to correctly handle tests that were failed by timeout where such failure is the expected behavior.
This commit is contained in:
Dmitriy Dolovov
2022-04-05 23:16:14 +03:00
committed by Space
parent 7cf3ca29af
commit 261e177b39
21 changed files with 216 additions and 120 deletions
@@ -1,4 +1,5 @@
// KIND: STANDALONE_NO_TR // KIND: STANDALONE_NO_TR
// EXPECTED_TIMEOUT_FAILURE
import kotlin.math.E import kotlin.math.E
import kotlin.math.sqrt import kotlin.math.sqrt
@@ -1,4 +1,5 @@
// KIND: STANDALONE_NO_TR // KIND: STANDALONE_NO_TR
// EXPECTED_TIMEOUT_FAILURE
import kotlin.math.E import kotlin.math.E
import kotlin.math.sqrt import kotlin.math.sqrt
@@ -14,10 +14,8 @@ import org.jetbrains.kotlin.konan.blackboxtest.support.compilation.TestCompilati
import org.jetbrains.kotlin.konan.blackboxtest.support.compilation.TestCompilationDependencyType.Library import org.jetbrains.kotlin.konan.blackboxtest.support.compilation.TestCompilationDependencyType.Library
import org.jetbrains.kotlin.konan.blackboxtest.support.compilation.TestCompilationResult.Companion.assertSuccess import org.jetbrains.kotlin.konan.blackboxtest.support.compilation.TestCompilationResult.Companion.assertSuccess
import org.jetbrains.kotlin.konan.blackboxtest.support.runner.TestExecutable import org.jetbrains.kotlin.konan.blackboxtest.support.runner.TestExecutable
import org.jetbrains.kotlin.konan.blackboxtest.support.settings.CacheMode import org.jetbrains.kotlin.konan.blackboxtest.support.runner.TestRunChecks
import org.jetbrains.kotlin.konan.blackboxtest.support.settings.KotlinNativeHome import org.jetbrains.kotlin.konan.blackboxtest.support.settings.*
import org.jetbrains.kotlin.konan.blackboxtest.support.settings.KotlinNativeTargets
import org.jetbrains.kotlin.konan.blackboxtest.support.settings.SimpleTestDirectories
import org.jetbrains.kotlin.konan.blackboxtest.support.util.* import org.jetbrains.kotlin.konan.blackboxtest.support.util.*
import org.junit.jupiter.api.Tag import org.junit.jupiter.api.Tag
import java.io.File import java.io.File
@@ -119,6 +117,7 @@ abstract class AbstractNativeKlibABITest : AbstractNativeSimpleTest() {
freeCompilerArgs = compilerArgs, freeCompilerArgs = compilerArgs,
nominalPackageName = PackageName.EMPTY, nominalPackageName = PackageName.EMPTY,
expectedOutputDataFile = null, expectedOutputDataFile = null,
checks = TestRunChecks.Default(testRunSettings.get<Timeouts>().executionTimeout),
extras = DEFAULT_EXTRAS extras = DEFAULT_EXTRAS
).apply { ).apply {
initialize(null) initialize(null)
@@ -21,9 +21,11 @@ import org.jetbrains.kotlin.konan.blackboxtest.support.compilation.TestCompilati
import org.jetbrains.kotlin.konan.blackboxtest.support.compilation.TestCompilationResult.Companion.assertSuccess import org.jetbrains.kotlin.konan.blackboxtest.support.compilation.TestCompilationResult.Companion.assertSuccess
import org.jetbrains.kotlin.konan.blackboxtest.support.compilation.TestCompilationResult.Success import org.jetbrains.kotlin.konan.blackboxtest.support.compilation.TestCompilationResult.Success
import org.jetbrains.kotlin.konan.blackboxtest.support.runner.TestExecutable import org.jetbrains.kotlin.konan.blackboxtest.support.runner.TestExecutable
import org.jetbrains.kotlin.konan.blackboxtest.support.runner.TestRunChecks
import org.jetbrains.kotlin.konan.blackboxtest.support.runner.TestRunners import org.jetbrains.kotlin.konan.blackboxtest.support.runner.TestRunners
import org.jetbrains.kotlin.konan.blackboxtest.support.settings.KotlinNativeTargets import org.jetbrains.kotlin.konan.blackboxtest.support.settings.KotlinNativeTargets
import org.jetbrains.kotlin.konan.blackboxtest.support.settings.SimpleTestDirectories import org.jetbrains.kotlin.konan.blackboxtest.support.settings.SimpleTestDirectories
import org.jetbrains.kotlin.konan.blackboxtest.support.settings.Timeouts
import org.jetbrains.kotlin.konan.blackboxtest.support.util.DumpedTestListing import org.jetbrains.kotlin.konan.blackboxtest.support.util.DumpedTestListing
import org.jetbrains.kotlin.konan.blackboxtest.support.util.LAUNCHER_MODULE_NAME import org.jetbrains.kotlin.konan.blackboxtest.support.util.LAUNCHER_MODULE_NAME
import org.jetbrains.kotlin.test.TestMetadata import org.jetbrains.kotlin.test.TestMetadata
@@ -111,6 +113,7 @@ class InfrastructureDumpedTestListingTest : AbstractNativeSimpleTest() {
freeCompilerArgs = TestCompilerArgs.EMPTY, freeCompilerArgs = TestCompilerArgs.EMPTY,
nominalPackageName = PackageName.EMPTY, nominalPackageName = PackageName.EMPTY,
expectedOutputDataFile = null, expectedOutputDataFile = null,
checks = TestRunChecks.Default(testRunSettings.get<Timeouts>().executionTimeout),
extras = DEFAULT_EXTRAS extras = DEFAULT_EXTRAS
).apply { ).apply {
initialize(null) initialize(null)
@@ -152,11 +152,16 @@ internal abstract class LoggedData {
class TestRun( class TestRun(
private val parameters: TestRunParameters, private val parameters: TestRunParameters,
private val runResult: RunResult.Completed private val runResult: RunResult
) : LoggedData() { ) : LoggedData() {
override fun computeText() = buildString { override fun computeText() = buildString {
appendLine("TEST RUN:") appendLine("TEST RUN:")
appendCommonRunResult(runResult) appendLine("- Exit code: ${runResult.exitCode ?: "<unknown>"}")
appendDuration(runResult.timeout, runResult.duration, runResult.hasFinishedOnTime)
appendLine()
appendPotentiallyLargeOutput(runResult.processOutput.stdOut.filteredOutput, "STDOUT", truncateLargeOutput = true)
appendLine()
appendPotentiallyLargeOutput(runResult.processOutput.stdErr, "STDERR", truncateLargeOutput = true)
appendLine() appendLine()
appendLine(parameters) appendLine(parameters)
} }
@@ -179,19 +184,6 @@ internal abstract class LoggedData {
} }
} }
class TestRunTimeoutExceeded(
private val parameters: TestRunParameters,
private val runResult: RunResult.TimeoutExceeded
) : LoggedData() {
override fun computeText() = buildString {
appendLine("TIMED OUT:")
appendLine("- Max permitted duration: ${runResult.timeout}")
appendCommonRunResult(runResult)
appendLine()
appendLine(parameters)
}
}
companion object { companion object {
protected fun StringBuilder.appendList(header: String, list: Iterable<Any?>): StringBuilder { protected fun StringBuilder.appendList(header: String, list: Iterable<Any?>): StringBuilder {
appendLine(header) appendLine(header)
@@ -222,17 +214,14 @@ internal abstract class LoggedData {
return this return this
} }
protected fun StringBuilder.appendDuration(duration: Duration): StringBuilder = protected fun StringBuilder.appendDuration(timeout: Duration, duration: Duration, hasFinishedOnTime: Boolean) {
append("- Duration: ").appendLine(duration.toString(DurationUnit.SECONDS, 2)) append("- Max permitted duration: ").appendLine(timeout.toString(DurationUnit.SECONDS, 2))
appendDuration(duration)
appendLine("- Finished on time: $hasFinishedOnTime")
}
protected fun StringBuilder.appendCommonRunResult(runResult: RunResult): StringBuilder { protected fun StringBuilder.appendDuration(duration: Duration) {
appendLine("- Exit code: ${runResult.exitCode ?: "<unknown>"}") append("- Duration: ").appendLine(duration.toString(DurationUnit.SECONDS, 2))
appendDuration(runResult.duration)
appendLine()
appendPotentiallyLargeOutput(runResult.processOutput.stdOut.filteredOutput, "STDOUT", truncateLargeOutput = true)
appendLine()
appendPotentiallyLargeOutput(runResult.processOutput.stdErr, "STDERR", truncateLargeOutput = true)
return this
} }
private fun StringBuilder.appendPotentiallyLargeOutput(output: String, subject: String, truncateLargeOutput: Boolean) { private fun StringBuilder.appendPotentiallyLargeOutput(output: String, subject: String, truncateLargeOutput: Boolean) {
@@ -32,7 +32,6 @@ import kotlin.reflect.KClass
import kotlin.reflect.KParameter import kotlin.reflect.KParameter
import kotlin.reflect.full.findAnnotation import kotlin.reflect.full.findAnnotation
import kotlin.time.Duration.Companion.milliseconds import kotlin.time.Duration.Companion.milliseconds
import kotlin.time.Duration.Companion.seconds
class NativeBlackBoxTestSupport : BeforeEachCallback { class NativeBlackBoxTestSupport : BeforeEachCallback {
/** /**
@@ -218,7 +217,7 @@ private object NativeTestSupport {
val executionTimeout = ClassLevelProperty.EXECUTION_TIMEOUT.readValue( val executionTimeout = ClassLevelProperty.EXECUTION_TIMEOUT.readValue(
enforcedProperties, enforcedProperties,
{ it.toLongOrNull()?.milliseconds }, { it.toLongOrNull()?.milliseconds },
default = 10.seconds default = Timeouts.DEFAULT_EXECUTION_TIMEOUT
) )
return Timeouts(executionTimeout) return Timeouts(executionTimeout)
} }
@@ -9,6 +9,7 @@ package org.jetbrains.kotlin.konan.blackboxtest.support
import org.jetbrains.kotlin.konan.blackboxtest.support.TestCase.WithTestRunnerExtras import org.jetbrains.kotlin.konan.blackboxtest.support.TestCase.WithTestRunnerExtras
import org.jetbrains.kotlin.konan.blackboxtest.support.TestModule.Companion.allDependencies import org.jetbrains.kotlin.konan.blackboxtest.support.TestModule.Companion.allDependencies
import org.jetbrains.kotlin.konan.blackboxtest.support.runner.TestRunChecks
import org.jetbrains.kotlin.konan.blackboxtest.support.util.* import org.jetbrains.kotlin.konan.blackboxtest.support.util.*
import org.jetbrains.kotlin.storage.LockBasedStorageManager import org.jetbrains.kotlin.storage.LockBasedStorageManager
import org.jetbrains.kotlin.test.services.JUnit5Assertions.assertTrue import org.jetbrains.kotlin.test.services.JUnit5Assertions.assertTrue
@@ -169,7 +170,8 @@ internal class TestCase(
val modules: Set<TestModule.Exclusive>, val modules: Set<TestModule.Exclusive>,
val freeCompilerArgs: TestCompilerArgs, val freeCompilerArgs: TestCompilerArgs,
val nominalPackageName: PackageName, val nominalPackageName: PackageName,
val expectedOutputDataFile: File?, val expectedOutputDataFile: File?, // TODO: refactor to TestRunCheck
val checks: TestRunChecks,
val extras: Extras val extras: Extras
) { ) {
sealed interface Extras sealed interface Extras
@@ -10,6 +10,7 @@ import org.jetbrains.kotlin.konan.blackboxtest.support.TestDirectives.FREE_COMPI
import org.jetbrains.kotlin.konan.blackboxtest.support.TestDirectives.INPUT_DATA_FILE import org.jetbrains.kotlin.konan.blackboxtest.support.TestDirectives.INPUT_DATA_FILE
import org.jetbrains.kotlin.konan.blackboxtest.support.TestDirectives.KIND import org.jetbrains.kotlin.konan.blackboxtest.support.TestDirectives.KIND
import org.jetbrains.kotlin.konan.blackboxtest.support.TestDirectives.OUTPUT_DATA_FILE import org.jetbrains.kotlin.konan.blackboxtest.support.TestDirectives.OUTPUT_DATA_FILE
import org.jetbrains.kotlin.konan.blackboxtest.support.TestDirectives.EXPECTED_TIMEOUT_FAILURE
import org.jetbrains.kotlin.konan.blackboxtest.support.TestDirectives.TEST_RUNNER import org.jetbrains.kotlin.konan.blackboxtest.support.TestDirectives.TEST_RUNNER
import org.jetbrains.kotlin.test.directives.model.RegisteredDirectives import org.jetbrains.kotlin.test.directives.model.RegisteredDirectives
import org.jetbrains.kotlin.test.directives.model.SimpleDirectivesContainer import org.jetbrains.kotlin.test.directives.model.SimpleDirectivesContainer
@@ -67,11 +68,35 @@ internal object TestDirectives : SimpleDirectivesContainer() {
val OUTPUT_DATA_FILE by stringDirective( val OUTPUT_DATA_FILE by stringDirective(
description = """ description = """
Specify the file which contains the expected program output. When program finishes its execution the actual output (stdout) Specify the file which contains the expected program output. When program finishes its execution, the actual output (stdout)
will be compared to the contents of this file. will be compared to the contents of this file.
""".trimIndent() """.trimIndent()
) )
// TODO: to be supported later
// val OUTPUT_REGEX by stringDirective(
// description = """
// The regex that the expected program output should match to. When program finishes its execution, the actual output (stdout)
// will be checked against this regex.
// """.trimIndent()
// )
// TODO: to be supported later
// val OUTPUT_INCLUDES by stringDirective(
// description = """
// The text that the expected program output should contain. When program finishes its execution, it will be checked if
// the actual output (stdout) contains this text.
// """.trimIndent()
// )
// TODO: to be supported later
// val OUTPUT_NOT_INCLUDES by stringDirective(
// description = """
// The text that the expected program output should NOT contain. When program finishes its execution, it will be checked if
// the actual output (stdout) does nto contain this text.
// """.trimIndent()
// )
val INPUT_DATA_FILE by stringDirective( val INPUT_DATA_FILE by stringDirective(
description = """ description = """
Specify the file which contains the text to be passed to process' input (stdin). Specify the file which contains the text to be passed to process' input (stdin).
@@ -79,6 +104,19 @@ internal object TestDirectives : SimpleDirectivesContainer() {
""".trimIndent() """.trimIndent()
) )
// TODO: to be supported later
// val EXIT_CODE by stringDirective(
// description = """
// Specify the exit code that the test should finish with. Example: // EXIT_CODE: 42
// To indicate any non-zero exit code use // EXIT_CODE: !0
// Note that this directive makes sense only in combination with // KIND: STANDALONE_NO_TR
// """.trimIndent()
// )
val EXPECTED_TIMEOUT_FAILURE by directive(
description = "Whether the test is expected to fail on timeout"
)
val FREE_COMPILER_ARGS by stringDirective( val FREE_COMPILER_ARGS by stringDirective(
description = "Specify free compiler arguments for Kotlin/Native compiler" description = "Specify free compiler arguments for Kotlin/Native compiler"
) )
@@ -203,6 +241,9 @@ internal fun parseFileName(parsedDirective: RegisteredDirectivesParser.ParsedDir
return fileName return fileName
} }
internal fun parseExpectedTimeoutFailure(registeredDirectives: RegisteredDirectives): Boolean =
EXPECTED_TIMEOUT_FAILURE in registeredDirectives
internal fun parseFreeCompilerArgs(registeredDirectives: RegisteredDirectives, location: Location): TestCompilerArgs { internal fun parseFreeCompilerArgs(registeredDirectives: RegisteredDirectives, location: Location): TestCompilerArgs {
if (FREE_COMPILER_ARGS !in registeredDirectives) if (FREE_COMPILER_ARGS !in registeredDirectives)
return TestCompilerArgs.EMPTY return TestCompilerArgs.EMPTY
@@ -19,10 +19,8 @@ import org.jetbrains.kotlin.config.CommonConfigurationKeys
import org.jetbrains.kotlin.config.CompilerConfiguration import org.jetbrains.kotlin.config.CompilerConfiguration
import org.jetbrains.kotlin.konan.blackboxtest.support.* import org.jetbrains.kotlin.konan.blackboxtest.support.*
import org.jetbrains.kotlin.konan.blackboxtest.support.TestCase.WithTestRunnerExtras import org.jetbrains.kotlin.konan.blackboxtest.support.TestCase.WithTestRunnerExtras
import org.jetbrains.kotlin.konan.blackboxtest.support.settings.ExternalSourceTransformersProvider import org.jetbrains.kotlin.konan.blackboxtest.support.runner.TestRunChecks
import org.jetbrains.kotlin.konan.blackboxtest.support.settings.GeneratedSources import org.jetbrains.kotlin.konan.blackboxtest.support.settings.*
import org.jetbrains.kotlin.konan.blackboxtest.support.settings.Settings
import org.jetbrains.kotlin.konan.blackboxtest.support.settings.TestRoots
import org.jetbrains.kotlin.konan.blackboxtest.support.util.* import org.jetbrains.kotlin.konan.blackboxtest.support.util.*
import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.name.Name
@@ -69,7 +67,8 @@ internal class ExtTestCaseGroupProvider : TestCaseGroupProvider, TestDisposable(
structureFactory = structureFactory, structureFactory = structureFactory,
customSourceTransformers = settings.get<ExternalSourceTransformersProvider>().getSourceTransformers(testDataFile), customSourceTransformers = settings.get<ExternalSourceTransformersProvider>().getSourceTransformers(testDataFile),
testRoots = settings.get(), testRoots = settings.get(),
generatedSources = settings.get() generatedSources = settings.get(),
timeouts = settings.get()
) )
if (extTestDataFile.isRelevant) if (extTestDataFile.isRelevant)
@@ -102,7 +101,8 @@ private class ExtTestDataFile(
structureFactory: ExtTestDataFileStructureFactory, structureFactory: ExtTestDataFileStructureFactory,
customSourceTransformers: ExternalSourceTransformers?, customSourceTransformers: ExternalSourceTransformers?,
testRoots: TestRoots, testRoots: TestRoots,
private val generatedSources: GeneratedSources private val generatedSources: GeneratedSources,
private val timeouts: Timeouts
) { ) {
private val structure by lazy { private val structure by lazy {
val allSourceTransformers: ExternalSourceTransformers = if (customSourceTransformers.isNullOrEmpty()) val allSourceTransformers: ExternalSourceTransformers = if (customSourceTransformers.isNullOrEmpty())
@@ -554,6 +554,7 @@ private class ExtTestDataFile(
freeCompilerArgs = assembleFreeCompilerArgs(), freeCompilerArgs = assembleFreeCompilerArgs(),
nominalPackageName = testDataFileSettings.nominalPackageName, nominalPackageName = testDataFileSettings.nominalPackageName,
expectedOutputDataFile = null, expectedOutputDataFile = null,
checks = TestRunChecks.Default(timeouts.executionTimeout),
extras = WithTestRunnerExtras(runnerType = TestRunnerType.DEFAULT) extras = WithTestRunnerExtras(runnerType = TestRunnerType.DEFAULT)
) )
testCase.initialize(sharedModules::get) testCase.initialize(sharedModules::get)
@@ -7,8 +7,10 @@ package org.jetbrains.kotlin.konan.blackboxtest.support.group
import org.jetbrains.kotlin.konan.blackboxtest.support.* import org.jetbrains.kotlin.konan.blackboxtest.support.*
import org.jetbrains.kotlin.konan.blackboxtest.support.TestCase.WithTestRunnerExtras import org.jetbrains.kotlin.konan.blackboxtest.support.TestCase.WithTestRunnerExtras
import org.jetbrains.kotlin.konan.blackboxtest.support.runner.TestRunChecks
import org.jetbrains.kotlin.konan.blackboxtest.support.settings.KotlinNativeHome import org.jetbrains.kotlin.konan.blackboxtest.support.settings.KotlinNativeHome
import org.jetbrains.kotlin.konan.blackboxtest.support.settings.Settings import org.jetbrains.kotlin.konan.blackboxtest.support.settings.Settings
import org.jetbrains.kotlin.konan.blackboxtest.support.settings.Timeouts
import org.jetbrains.kotlin.konan.blackboxtest.support.util.ThreadSafeCache import org.jetbrains.kotlin.konan.blackboxtest.support.util.ThreadSafeCache
import org.jetbrains.kotlin.konan.blackboxtest.support.util.expandGlobTo import org.jetbrains.kotlin.konan.blackboxtest.support.util.expandGlobTo
import org.jetbrains.kotlin.konan.blackboxtest.support.util.getAbsoluteFile import org.jetbrains.kotlin.konan.blackboxtest.support.util.getAbsoluteFile
@@ -62,6 +64,7 @@ internal class PredefinedTestCaseGroupProvider(annotation: PredefinedTestCases)
.parseCompilerArgs(settings) { "Failed to parse free compiler arguments for test case $testCaseId" }, .parseCompilerArgs(settings) { "Failed to parse free compiler arguments for test case $testCaseId" },
nominalPackageName = PackageName(testCaseId.uniqueName), nominalPackageName = PackageName(testCaseId.uniqueName),
expectedOutputDataFile = null, expectedOutputDataFile = null,
checks = TestRunChecks.Default(settings.get<Timeouts>().executionTimeout),
extras = WithTestRunnerExtras( extras = WithTestRunnerExtras(
runnerType = predefinedTestCase.runnerType, runnerType = predefinedTestCase.runnerType,
ignoredTests = predefinedTestCase.ignoredTests.toSet() ignoredTests = predefinedTestCase.ignoredTests.toSet()
@@ -8,9 +8,12 @@ package org.jetbrains.kotlin.konan.blackboxtest.support.group
import org.jetbrains.kotlin.konan.blackboxtest.support.* import org.jetbrains.kotlin.konan.blackboxtest.support.*
import org.jetbrains.kotlin.konan.blackboxtest.support.TestCase.NoTestRunnerExtras import org.jetbrains.kotlin.konan.blackboxtest.support.TestCase.NoTestRunnerExtras
import org.jetbrains.kotlin.konan.blackboxtest.support.TestCase.WithTestRunnerExtras import org.jetbrains.kotlin.konan.blackboxtest.support.TestCase.WithTestRunnerExtras
import org.jetbrains.kotlin.konan.blackboxtest.support.runner.TestRunCheck
import org.jetbrains.kotlin.konan.blackboxtest.support.runner.TestRunChecks
import org.jetbrains.kotlin.konan.blackboxtest.support.settings.GeneratedSources import org.jetbrains.kotlin.konan.blackboxtest.support.settings.GeneratedSources
import org.jetbrains.kotlin.konan.blackboxtest.support.settings.Settings import org.jetbrains.kotlin.konan.blackboxtest.support.settings.Settings
import org.jetbrains.kotlin.konan.blackboxtest.support.settings.TestRoots import org.jetbrains.kotlin.konan.blackboxtest.support.settings.TestRoots
import org.jetbrains.kotlin.konan.blackboxtest.support.settings.Timeouts
import org.jetbrains.kotlin.konan.blackboxtest.support.util.* import org.jetbrains.kotlin.konan.blackboxtest.support.util.*
import org.jetbrains.kotlin.test.directives.model.Directive import org.jetbrains.kotlin.test.directives.model.Directive
import org.jetbrains.kotlin.test.services.JUnit5Assertions import org.jetbrains.kotlin.test.services.JUnit5Assertions
@@ -180,6 +183,14 @@ internal class StandardTestCaseGroupProvider : TestCaseGroupProvider {
val freeCompilerArgs = parseFreeCompilerArgs(registeredDirectives, location) val freeCompilerArgs = parseFreeCompilerArgs(registeredDirectives, location)
val expectedOutputDataFile = parseOutputDataFile(baseDir = testDataFile.parentFile, registeredDirectives, location) val expectedOutputDataFile = parseOutputDataFile(baseDir = testDataFile.parentFile, registeredDirectives, location)
val testKind = parseTestKind(registeredDirectives, location) val testKind = parseTestKind(registeredDirectives, location)
val expectedTimeoutFailure = parseExpectedTimeoutFailure(registeredDirectives)
val checks = TestRunChecks {
this += if (expectedTimeoutFailure)
TestRunCheck.ExecutionTimeout.ShouldExceed(settings.get<Timeouts>().executionTimeout)
else
TestRunCheck.ExecutionTimeout.ShouldNotExceed(settings.get<Timeouts>().executionTimeout)
}
if (testKind == TestKind.REGULAR) { if (testKind == TestKind.REGULAR) {
// Fix package declarations to avoid unintended conflicts between symbols with the same name in different test cases. // Fix package declarations to avoid unintended conflicts between symbols with the same name in different test cases.
@@ -193,6 +204,7 @@ internal class StandardTestCaseGroupProvider : TestCaseGroupProvider {
freeCompilerArgs = freeCompilerArgs, freeCompilerArgs = freeCompilerArgs,
nominalPackageName = nominalPackageName, nominalPackageName = nominalPackageName,
expectedOutputDataFile = expectedOutputDataFile, expectedOutputDataFile = expectedOutputDataFile,
checks = checks,
extras = if (testKind == TestKind.STANDALONE_NO_TR) extras = if (testKind == TestKind.STANDALONE_NO_TR)
NoTestRunnerExtras( NoTestRunnerExtras(
entryPoint = parseEntryPoint(registeredDirectives, location), entryPoint = parseEntryPoint(registeredDirectives, location),
@@ -12,7 +12,7 @@ import org.jetbrains.kotlin.konan.blackboxtest.support.util.TestOutputFilter
import java.io.ByteArrayOutputStream import java.io.ByteArrayOutputStream
import kotlin.time.* import kotlin.time.*
internal abstract class AbstractLocalProcessRunner<R>(private val executionTimeout: Duration) : AbstractRunner<R>() { internal abstract class AbstractLocalProcessRunner<R>(private val checks: TestRunChecks) : AbstractRunner<R>() {
protected abstract val visibleProcessName: String protected abstract val visibleProcessName: String
protected abstract val executable: TestExecutable protected abstract val executable: TestExecutable
protected abstract val programArgs: List<String> protected abstract val programArgs: List<String>
@@ -26,6 +26,8 @@ internal abstract class AbstractLocalProcessRunner<R>(private val executionTimeo
val unfilteredOutput = UnfilteredProcessOutput() val unfilteredOutput = UnfilteredProcessOutput()
val unfilteredOutputReader: Job val unfilteredOutputReader: Job
val executionTimeout: Duration = checks.executionTimeoutCheck.timeout
val process: Process val process: Process
val hasFinishedOnTime: Boolean val hasFinishedOnTime: Boolean
@@ -41,16 +43,11 @@ internal abstract class AbstractLocalProcessRunner<R>(private val executionTimeo
) )
} }
if (hasFinishedOnTime) { val exitCode: Int? = if (hasFinishedOnTime) {
unfilteredOutputReader.join() // Wait until all output streams are drained. unfilteredOutputReader.join() // Wait until all output streams are drained.
process.exitValue()
RunResult.Completed(
exitCode = process.exitValue(),
duration = duration,
processOutput = unfilteredOutput.toProcessOutput(outputFilter)
)
} else { } else {
val exitCode: Int? = try { // It could happen just by an accident that the process has exited by itself. try { // It could happen just by an accident that the process has exited by itself.
val exitCode = process.exitValue() // Fetch exit code. val exitCode = process.exitValue() // Fetch exit code.
unfilteredOutputReader.join() // Wait until all streams are drained. unfilteredOutputReader.join() // Wait until all streams are drained.
exitCode exitCode
@@ -59,22 +56,37 @@ internal abstract class AbstractLocalProcessRunner<R>(private val executionTimeo
process.destroyForcibly() // kill -9 process.destroyForcibly() // kill -9
null null
} }
RunResult.TimeoutExceeded(
timeout = executionTimeout,
exitCode = exitCode,
duration = duration,
processOutput = unfilteredOutput.toProcessOutput(outputFilter)
)
} }
RunResult(
exitCode = exitCode,
timeout = executionTimeout,
duration = duration,
hasFinishedOnTime = hasFinishedOnTime,
processOutput = unfilteredOutput.toProcessOutput(outputFilter)
)
} }
} }
abstract override fun buildResultHandler(runResult: RunResult.Completed): ResultHandler // Narrow returned type. abstract override fun buildResultHandler(runResult: RunResult): ResultHandler // Narrow returned type.
abstract inner class ResultHandler(runResult: RunResult.Completed) : AbstractRunner<R>.ResultHandler(runResult) { abstract inner class ResultHandler(runResult: RunResult) : AbstractRunner<R>.ResultHandler(runResult) {
override fun handle(): R { override fun handle(): R {
verifyExpectation(runResult.exitCode == 0) { "$visibleProcessName exited with non-zero code." } checks.forEach { check ->
when (check) {
is TestRunCheck.ExecutionTimeout.ShouldNotExceed -> verifyExpectation(runResult.hasFinishedOnTime) {
"Timeout exceeded during test execution."
}
is TestRunCheck.ExecutionTimeout.ShouldExceed -> verifyExpectation(!runResult.hasFinishedOnTime) {
"Test is expected to fail with exceeded timeout, which hasn't happened."
}
}
}
// Don't check exit code if it is unknown.
runResult.exitCode?.let { exitCode ->
verifyExpectation(exitCode == 0) { "$visibleProcessName exited with non-zero code." }
}
return doHandle() return doHandle()
} }
@@ -7,26 +7,18 @@ package org.jetbrains.kotlin.konan.blackboxtest.support.runner
import org.jetbrains.kotlin.konan.blackboxtest.support.LoggedData import org.jetbrains.kotlin.konan.blackboxtest.support.LoggedData
import org.jetbrains.kotlin.test.services.JUnit5Assertions.assertTrue import org.jetbrains.kotlin.test.services.JUnit5Assertions.assertTrue
import org.jetbrains.kotlin.test.services.JUnit5Assertions.fail
import org.opentest4j.TestAbortedException import org.opentest4j.TestAbortedException
internal abstract class AbstractRunner<R> { internal abstract class AbstractRunner<R> {
protected abstract fun buildRun(): AbstractRun protected abstract fun buildRun(): AbstractRun
protected abstract fun buildResultHandler(runResult: RunResult.Completed): ResultHandler protected abstract fun buildResultHandler(runResult: RunResult): ResultHandler
protected abstract fun getLoggedParameters(): LoggedData.TestRunParameters protected abstract fun getLoggedParameters(): LoggedData.TestRunParameters
protected abstract fun handleUnexpectedFailure(t: Throwable): Nothing protected abstract fun handleUnexpectedFailure(t: Throwable): Nothing
fun run(): R = try { fun run(): R = try {
val run = buildRun() val run = buildRun()
val runResult = run.run()
val resultHandler = when (val runResult = run.run()) { val resultHandler = buildResultHandler(runResult)
is RunResult.TimeoutExceeded -> fail {
LoggedData.TestRunTimeoutExceeded(getLoggedParameters(), runResult)
.withErrorMessage("Timeout exceeded during test execution.")
}
is RunResult.Completed -> buildResultHandler(runResult)
}
resultHandler.handle() resultHandler.handle()
} catch (t: Throwable) { } catch (t: Throwable) {
when (t) { when (t) {
@@ -42,7 +34,7 @@ internal abstract class AbstractRunner<R> {
fun run(): RunResult fun run(): RunResult
} }
abstract inner class ResultHandler(protected val runResult: RunResult.Completed) { abstract inner class ResultHandler(protected val runResult: RunResult) {
abstract fun getLoggedRun(): LoggedData abstract fun getLoggedRun(): LoggedData
abstract fun handle(): R abstract fun handle(): R
@@ -9,12 +9,13 @@ import org.jetbrains.kotlin.konan.blackboxtest.support.TestCase
import org.jetbrains.kotlin.konan.blackboxtest.support.TestCase.NoTestRunnerExtras import org.jetbrains.kotlin.konan.blackboxtest.support.TestCase.NoTestRunnerExtras
import org.jetbrains.kotlin.konan.blackboxtest.support.TestKind import org.jetbrains.kotlin.konan.blackboxtest.support.TestKind
import org.jetbrains.kotlin.konan.blackboxtest.support.TestName import org.jetbrains.kotlin.konan.blackboxtest.support.TestName
import org.jetbrains.kotlin.test.services.JUnit5Assertions import org.jetbrains.kotlin.test.services.JUnit5Assertions.assertTrue
import org.jetbrains.kotlin.utils.addIfNotNull
internal open class BaseTestRunProvider { internal open class BaseTestRunProvider {
protected fun createTestRun(testCase: TestCase, executable: TestExecutable, testRunName: String, testName: TestName?): TestRun { protected fun createTestRun(testCase: TestCase, executable: TestExecutable, testRunName: String, testName: TestName?): TestRun {
val runParameters = getTestRunParameters(testCase, testName) val runParameters = getTestRunParameters(testCase, testName)
return TestRun(displayName = testRunName, executable, runParameters, testCase.id) return TestRun(displayName = testRunName, executable, runParameters, testCase.id, testCase.checks)
} }
protected fun createSingleTestRun(testCase: TestCase, executable: TestExecutable): TestRun = createTestRun( protected fun createSingleTestRun(testCase: TestCase, executable: TestExecutable): TestRun = createTestRun(
@@ -24,25 +25,24 @@ internal open class BaseTestRunProvider {
testName = null testName = null
) )
private fun getTestRunParameters(testCase: TestCase, testName: TestName?): List<TestRunParameter> = with(testCase) { private fun getTestRunParameters(testCase: TestCase, testName: TestName?): List<TestRunParameter> = buildList {
when (kind) { addIfNotNull(testCase.expectedOutputDataFile?.let(TestRunParameter::WithExpectedOutputData))
when (testCase.kind) {
TestKind.STANDALONE_NO_TR -> { TestKind.STANDALONE_NO_TR -> {
JUnit5Assertions.assertTrue(testName == null) assertTrue(testName == null)
listOfNotNull( addIfNotNull(testCase.extras<NoTestRunnerExtras>().inputDataFile?.let(TestRunParameter::WithInputData))
extras<NoTestRunnerExtras>().inputDataFile?.let(TestRunParameter::WithInputData), }
expectedOutputDataFile?.let(TestRunParameter::WithExpectedOutputData) TestKind.STANDALONE -> {
add(TestRunParameter.WithTCTestLogger)
addIfNotNull(testName?.let(TestRunParameter::WithTestFilter))
}
TestKind.REGULAR -> {
add(TestRunParameter.WithTCTestLogger)
addIfNotNull(
testName?.let(TestRunParameter::WithTestFilter) ?: TestRunParameter.WithPackageFilter(testCase.nominalPackageName)
) )
} }
TestKind.STANDALONE -> listOfNotNull(
TestRunParameter.WithTCTestLogger,
testName?.let(TestRunParameter::WithTestFilter),
expectedOutputDataFile?.let(TestRunParameter::WithExpectedOutputData)
)
TestKind.REGULAR -> listOfNotNull(
TestRunParameter.WithTCTestLogger,
testName?.let(TestRunParameter::WithTestFilter) ?: TestRunParameter.WithPackageFilter(nominalPackageName),
expectedOutputDataFile?.let(TestRunParameter::WithExpectedOutputData)
)
} }
} }
} }
@@ -10,12 +10,11 @@ import org.jetbrains.kotlin.konan.blackboxtest.support.TestName
import org.jetbrains.kotlin.konan.blackboxtest.support.util.GTestListing import org.jetbrains.kotlin.konan.blackboxtest.support.util.GTestListing
import org.jetbrains.kotlin.konan.blackboxtest.support.util.TestOutputFilter import org.jetbrains.kotlin.konan.blackboxtest.support.util.TestOutputFilter
import org.jetbrains.kotlin.test.services.JUnit5Assertions.fail import org.jetbrains.kotlin.test.services.JUnit5Assertions.fail
import kotlin.time.Duration
internal class LocalTestNameExtractor( internal class LocalTestNameExtractor(
override val executable: TestExecutable, override val executable: TestExecutable,
executionTimeout: Duration checks: TestRunChecks
) : AbstractLocalProcessRunner<Collection<TestName>>(executionTimeout) { ) : AbstractLocalProcessRunner<Collection<TestName>>(checks) {
override val visibleProcessName get() = "Test name extractor" override val visibleProcessName get() = "Test name extractor"
override val programArgs = listOf(executable.executableFile.path, "--ktest_list_tests") override val programArgs = listOf(executable.executableFile.path, "--ktest_list_tests")
override val outputFilter get() = TestOutputFilter.NO_FILTERING override val outputFilter get() = TestOutputFilter.NO_FILTERING
@@ -27,7 +26,7 @@ internal class LocalTestNameExtractor(
runParameters = null runParameters = null
) )
override fun buildResultHandler(runResult: RunResult.Completed) = ResultHandler(runResult) override fun buildResultHandler(runResult: RunResult) = ResultHandler(runResult)
override fun handleUnexpectedFailure(t: Throwable) = fail { override fun handleUnexpectedFailure(t: Throwable) = fail {
LoggedData.TestRunUnexpectedFailure(getLoggedParameters(), t) LoggedData.TestRunUnexpectedFailure(getLoggedParameters(), t)
@@ -35,7 +34,7 @@ internal class LocalTestNameExtractor(
} }
inner class ResultHandler( inner class ResultHandler(
runResult: RunResult.Completed runResult: RunResult
) : AbstractLocalProcessRunner<Collection<TestName>>.ResultHandler(runResult) { ) : AbstractLocalProcessRunner<Collection<TestName>>.ResultHandler(runResult) {
override fun getLoggedRun() = LoggedData.TestRun(getLoggedParameters(), runResult) override fun getLoggedRun() = LoggedData.TestRun(getLoggedParameters(), runResult)
override fun doHandle() = GTestListing.parse(runResult.processOutput.stdOut.filteredOutput) override fun doHandle() = GTestListing.parse(runResult.processOutput.stdOut.filteredOutput)
@@ -12,12 +12,8 @@ import org.jetbrains.kotlin.konan.blackboxtest.support.util.TestOutputFilter
import org.jetbrains.kotlin.konan.blackboxtest.support.util.TestReport import org.jetbrains.kotlin.konan.blackboxtest.support.util.TestReport
import org.jetbrains.kotlin.test.services.JUnit5Assertions.fail import org.jetbrains.kotlin.test.services.JUnit5Assertions.fail
import org.junit.jupiter.api.Assumptions.assumeFalse import org.junit.jupiter.api.Assumptions.assumeFalse
import kotlin.time.Duration
internal class LocalTestRunner( internal class LocalTestRunner(private val testRun: TestRun) : AbstractLocalProcessRunner<Unit>(testRun.checks) {
private val testRun: TestRun,
executionTimeout: Duration
) : AbstractLocalProcessRunner<Unit>(executionTimeout) {
override val visibleProcessName get() = "Tested process" override val visibleProcessName get() = "Tested process"
override val executable get() = testRun.executable override val executable get() = testRun.executable
@@ -43,14 +39,14 @@ internal class LocalTestRunner(
} }
} }
override fun buildResultHandler(runResult: RunResult.Completed) = ResultHandler(runResult) override fun buildResultHandler(runResult: RunResult) = ResultHandler(runResult)
override fun handleUnexpectedFailure(t: Throwable) = fail { override fun handleUnexpectedFailure(t: Throwable) = fail {
LoggedData.TestRunUnexpectedFailure(getLoggedParameters(), t) LoggedData.TestRunUnexpectedFailure(getLoggedParameters(), t)
.withErrorMessage("Test execution failed with unexpected exception.") .withErrorMessage("Test execution failed with unexpected exception.")
} }
inner class ResultHandler(runResult: RunResult.Completed) : AbstractLocalProcessRunner<Unit>.ResultHandler(runResult) { inner class ResultHandler(runResult: RunResult) : AbstractLocalProcessRunner<Unit>.ResultHandler(runResult) {
override fun getLoggedRun() = LoggedData.TestRun(getLoggedParameters(), runResult) override fun getLoggedRun() = LoggedData.TestRun(getLoggedParameters(), runResult)
override fun doHandle() { override fun doHandle() {
@@ -8,23 +8,17 @@ package org.jetbrains.kotlin.konan.blackboxtest.support.runner
import org.jetbrains.kotlin.konan.blackboxtest.support.util.TestOutputFilter import org.jetbrains.kotlin.konan.blackboxtest.support.util.TestOutputFilter
import kotlin.time.Duration import kotlin.time.Duration
internal sealed interface RunResult { internal data class RunResult(
val exitCode: Int? val exitCode: Int?,
val duration: Duration val timeout: Duration,
val duration: Duration,
val hasFinishedOnTime: Boolean,
val processOutput: ProcessOutput val processOutput: ProcessOutput
) {
data class Completed( init {
override val exitCode: Int, // null exit code is possible only when test run hasn't finished on time.
override val duration: Duration, check(exitCode != null || !hasFinishedOnTime)
override val processOutput: ProcessOutput }
) : RunResult
data class TimeoutExceeded(
val timeout: Duration,
override val exitCode: Int?,
override val duration: Duration,
override val processOutput: ProcessOutput
) : RunResult
} }
internal class ProcessOutput(val stdOut: TestOutputFilter.FilteredOutput, val stdErr: String) internal class ProcessOutput(val stdOut: TestOutputFilter.FilteredOutput, val stdErr: String)
@@ -54,7 +54,8 @@ internal class TestRun(
val displayName: String, val displayName: String,
val executable: TestExecutable, val executable: TestExecutable,
val runParameters: List<TestRunParameter>, val runParameters: List<TestRunParameter>,
val testCaseId: TestCaseId val testCaseId: TestCaseId,
val checks: TestRunChecks
) )
internal sealed interface TestRunParameter { internal sealed interface TestRunParameter {
@@ -0,0 +1,46 @@
/*
* Copyright 2010-2022 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.blackboxtest.support.runner
import org.jetbrains.kotlin.konan.blackboxtest.support.settings.Timeouts
import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstance
import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull
import kotlin.time.Duration
internal sealed interface TestRunCheck {
sealed class ExecutionTimeout(val timeout: Duration) : TestRunCheck {
class ShouldNotExceed(timeout: Duration) : ExecutionTimeout(timeout)
class ShouldExceed(timeout: Duration) : ExecutionTimeout(timeout)
}
}
internal class TestRunChecks(builderAction: MutableCollection<TestRunCheck>.() -> Unit) : Iterable<TestRunCheck> {
constructor(vararg checks: TestRunCheck) : this({ addAll(checks) })
private val allChecks = buildList {
builderAction()
addDefaults()
}
val executionTimeoutCheck: TestRunCheck.ExecutionTimeout get() = allChecks.firstIsInstance()
override fun iterator() = allChecks.iterator()
companion object {
// The most frequently used case:
@Suppress("TestFunctionName")
fun Default(timeout: Duration) = TestRunChecks(TestRunCheck.ExecutionTimeout.ShouldNotExceed(timeout))
private fun MutableCollection<TestRunCheck>.addDefaults() {
if (isMissing<TestRunCheck.ExecutionTimeout>()) {
add(TestRunCheck.ExecutionTimeout.ShouldNotExceed(Timeouts.DEFAULT_EXECUTION_TIMEOUT))
}
}
private inline fun <reified T : TestRunCheck> Collection<TestRunCheck>.isMissing(): Boolean =
firstIsInstanceOrNull<T>() == null
}
}
@@ -16,7 +16,7 @@ internal object TestRunners {
fun createProperTestRunner(testRun: TestRun, settings: Settings): AbstractRunner<*> = with(settings) { fun createProperTestRunner(testRun: TestRun, settings: Settings): AbstractRunner<*> = with(settings) {
with(get<KotlinNativeTargets>()) { with(get<KotlinNativeTargets>()) {
if (testTarget == hostTarget) if (testTarget == hostTarget)
LocalTestRunner(testRun, get<Timeouts>().executionTimeout) LocalTestRunner(testRun)
else else
runningAtNonHostTarget() runningAtNonHostTarget()
} }
@@ -26,7 +26,7 @@ internal object TestRunners {
fun extractTestNames(executable: TestExecutable, settings: Settings): Collection<TestName> = with(settings) { fun extractTestNames(executable: TestExecutable, settings: Settings): Collection<TestName> = with(settings) {
with(get<KotlinNativeTargets>()) { with(get<KotlinNativeTargets>()) {
if (testTarget == hostTarget) if (testTarget == hostTarget)
LocalTestNameExtractor(executable, get<Timeouts>().executionTimeout).run() LocalTestNameExtractor(executable, TestRunChecks.Default(get<Timeouts>().executionTimeout)).run()
else else
runningAtNonHostTarget() runningAtNonHostTarget()
} }
@@ -9,6 +9,7 @@ import org.jetbrains.kotlin.konan.target.KonanTarget
import java.io.File import java.io.File
import java.util.* import java.util.*
import kotlin.time.Duration import kotlin.time.Duration
import kotlin.time.Duration.Companion.seconds
/** /**
* The tested and the host Kotlin/Native targets. * The tested and the host Kotlin/Native targets.
@@ -113,7 +114,11 @@ internal class BaseDirs(val testBuildDir: File)
/** /**
* Timeouts. * Timeouts.
*/ */
internal class Timeouts(val executionTimeout: Duration) internal class Timeouts(val executionTimeout: Duration) {
companion object {
val DEFAULT_EXECUTION_TIMEOUT: Duration get() = 15.seconds
}
}
/** /**
* Used cache mode. * Used cache mode.