From 0457001a87973a411485eda5517aa047f24b3d09 Mon Sep 17 00:00:00 2001 From: Alexander Shabalin Date: Wed, 24 May 2023 16:20:01 +0200 Subject: [PATCH] [K/N] Apply execution timeout to tests --- .../kotlin/org/jetbrains/kotlin/ExecutorService.kt | 6 ++++++ .../kotlin/org/jetbrains/kotlin/FrameworkTest.kt | 7 +++++-- .../kotlin/org/jetbrains/kotlin/KotlinNativeTest.kt | 7 +++++-- .../kotlin/bitcode/CompileToBitcodePlugin.kt | 4 +++- .../kotlin/org/jetbrains/kotlin/cpp/RunGTest.kt | 12 ++++++++++++ .../jetbrains/kotlin/native/executors/Executor.kt | 13 +++++++++---- 6 files changed, 40 insertions(+), 9 deletions(-) diff --git a/kotlin-native/build-tools/src/main/kotlin/org/jetbrains/kotlin/ExecutorService.kt b/kotlin-native/build-tools/src/main/kotlin/org/jetbrains/kotlin/ExecutorService.kt index 08b57ee6c7e..e4fc4274c05 100644 --- a/kotlin-native/build-tools/src/main/kotlin/org/jetbrains/kotlin/ExecutorService.kt +++ b/kotlin-native/build-tools/src/main/kotlin/org/jetbrains/kotlin/ExecutorService.kt @@ -14,6 +14,7 @@ * limitations under the License. */ +@file:OptIn(kotlin.time.ExperimentalTime::class) package org.jetbrains.kotlin import com.google.gson.annotations.Expose @@ -35,6 +36,10 @@ import java.time.LocalDateTime import java.time.format.DateTimeFormatter import java.util.concurrent.TimeUnit +import kotlin.time.Duration +import kotlin.time.DurationUnit +import kotlin.time.toDuration + /** * A replacement of the standard `exec {}` * @see org.gradle.api.Project.exec @@ -56,6 +61,7 @@ private fun Executor.service(project: Project) = object: ExecutorService { val request = ExecuteRequest( executableAbsolutePath = execSpec.executable, args = execSpec.args, + timeout = 15.toDuration(DurationUnit.MINUTES), ).apply { execSpec.standardInput?.let { stdin = it diff --git a/kotlin-native/build-tools/src/main/kotlin/org/jetbrains/kotlin/FrameworkTest.kt b/kotlin-native/build-tools/src/main/kotlin/org/jetbrains/kotlin/FrameworkTest.kt index 5f0510059ee..6c571624265 100644 --- a/kotlin-native/build-tools/src/main/kotlin/org/jetbrains/kotlin/FrameworkTest.kt +++ b/kotlin-native/build-tools/src/main/kotlin/org/jetbrains/kotlin/FrameworkTest.kt @@ -243,7 +243,10 @@ open class FrameworkTest : DefaultTask(), KonanTestExecutable { |stdout: $stdOut |stderr: $stdErr """.trimMargin()) - check(exitCode == expectedExitStatus ?: 0) { "Execution of $testExecName failed with exit code: $exitCode " } + val timeoutMessage = if (exitCode == -1) { + "WARNING: probably a timeout\n" + } else "" + check(exitCode == expectedExitStatus ?: 0) { "${timeoutMessage}Execution of $testExecName failed with exit code: $exitCode " } } private fun validateBitcodeEmbedding(frameworkBinary: String) { @@ -269,4 +272,4 @@ open class FrameworkTest : DefaultTask(), KonanTestExecutable { } private val xcode by lazy { Xcode.findCurrent() } -} \ No newline at end of file +} diff --git a/kotlin-native/build-tools/src/main/kotlin/org/jetbrains/kotlin/KotlinNativeTest.kt b/kotlin-native/build-tools/src/main/kotlin/org/jetbrains/kotlin/KotlinNativeTest.kt index d7e94262931..71adaf8be72 100644 --- a/kotlin-native/build-tools/src/main/kotlin/org/jetbrains/kotlin/KotlinNativeTest.kt +++ b/kotlin-native/build-tools/src/main/kotlin/org/jetbrains/kotlin/KotlinNativeTest.kt @@ -334,6 +334,9 @@ open class KonanLocalTest : KonanTest() { exitCode + other.exitCode) private fun ProcessOutput.check() { + val timeoutMessage = if (exitCode == -1) { + "WARNING: probably a timeout\n" + } else "" val exitCodeMismatch = !expectedExitStatusChecker(exitCode) if (exitCodeMismatch) { val message = if (expectedExitStatus != null) @@ -342,7 +345,7 @@ open class KonanLocalTest : KonanTest() { "Actual exit status doesn't match with exit status checker: $exitCode" check(expectedFail) { """ - |Test failed. $message + |${timeoutMessage}Test failed. $message |stdout: |$stdOut |stderr: @@ -358,7 +361,7 @@ open class KonanLocalTest : KonanTest() { val message = goldenData?.let { goldenData -> "Expected output: $goldenData, actual output: $output" } ?: "Actual output doesn't match with output checker: $output" - check(expectedFail) { "Test failed. $message" } + check(expectedFail) { "${timeoutMessage}Test failed. $message" } println("Expected failure. $message") } diff --git a/kotlin-native/build-tools/src/main/kotlin/org/jetbrains/kotlin/bitcode/CompileToBitcodePlugin.kt b/kotlin-native/build-tools/src/main/kotlin/org/jetbrains/kotlin/bitcode/CompileToBitcodePlugin.kt index 8d47706009f..efd88a31b37 100644 --- a/kotlin-native/build-tools/src/main/kotlin/org/jetbrains/kotlin/bitcode/CompileToBitcodePlugin.kt +++ b/kotlin-native/build-tools/src/main/kotlin/org/jetbrains/kotlin/bitcode/CompileToBitcodePlugin.kt @@ -30,6 +30,7 @@ import org.jetbrains.kotlin.konan.target.TargetDomainObjectContainer import org.jetbrains.kotlin.konan.target.TargetWithSanitizer import org.jetbrains.kotlin.testing.native.GoogleTestExtension import org.jetbrains.kotlin.utils.capitalized +import java.time.Duration import javax.inject.Inject private fun String.snakeCaseToUpperCamelCase() = split('_').joinToString(separator = "") { it.capitalized } @@ -602,6 +603,7 @@ open class CompileToBitcodeExtension @Inject constructor(val project: Project) : filter.set(project.findProperty("gtest_filter") as? String) tsanSuppressionsFile.set(project.layout.projectDirectory.file("tsan_suppressions.txt")) this.target.set(target) + this.executionTimeout.set(Duration.ofMinutes(30)) // The tests binaries are big. usesService(runGTestSemaphore) } @@ -647,4 +649,4 @@ open class CompileToBitcodePlugin : Plugin { project.apply() project.extensions.create("bitcode", project) } -} \ No newline at end of file +} diff --git a/kotlin-native/build-tools/src/main/kotlin/org/jetbrains/kotlin/cpp/RunGTest.kt b/kotlin-native/build-tools/src/main/kotlin/org/jetbrains/kotlin/cpp/RunGTest.kt index 24eadae0cc8..d327c58b7b4 100644 --- a/kotlin-native/build-tools/src/main/kotlin/org/jetbrains/kotlin/cpp/RunGTest.kt +++ b/kotlin-native/build-tools/src/main/kotlin/org/jetbrains/kotlin/cpp/RunGTest.kt @@ -3,6 +3,7 @@ * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. */ +@file:OptIn(kotlin.time.ExperimentalTime::class) package org.jetbrains.kotlin.cpp import org.gradle.api.DefaultTask @@ -15,7 +16,9 @@ import org.gradle.workers.WorkParameters import org.gradle.workers.WorkerExecutor import org.jetbrains.kotlin.native.executors.* import org.jetbrains.kotlin.konan.target.* +import java.time.Duration import javax.inject.Inject +import kotlin.time.toKotlinDuration private abstract class RunGTestJob : WorkAction { interface Parameters : WorkParameters { @@ -29,6 +32,7 @@ private abstract class RunGTestJob : WorkAction { // TODO: Figure out a way to pass KonanTarget, but it is used as a key into PlatformManager, // so object identity matters, and platform managers are different between project and worker sides. val targetName: Property + val executionTimeout: Property } // The `Executor` is created for every `RunGTest` task execution. It's okay, testing tasks are few-ish and big. @@ -59,6 +63,7 @@ private abstract class RunGTestJob : WorkAction { tsanSuppressionsFile.orNull?.also { this.environment.put("TSAN_OPTIONS", "suppressions=${it.asFile.absolutePath}") } + this.timeout = executionTimeout.get().toKotlinDuration() }).assertSuccess() reportFile.asFile.get().parentFile.mkdirs() @@ -135,6 +140,12 @@ abstract class RunGTest : DefaultTask() { @get:Input abstract val target: Property + /** + * Timeout for the test run. + */ + @get:Input + abstract val executionTimeout: Property + @TaskAction fun run() { val workQueue = workerExecutor.noIsolation() @@ -148,6 +159,7 @@ abstract class RunGTest : DefaultTask() { tsanSuppressionsFile.set(this@RunGTest.tsanSuppressionsFile) platformManager.set(project.extensions.getByType()) targetName.set(this@RunGTest.target.get().name) + executionTimeout.set(this@RunGTest.executionTimeout) } } } diff --git a/native/executors/src/main/kotlin/org/jetbrains/kotlin/native/executors/Executor.kt b/native/executors/src/main/kotlin/org/jetbrains/kotlin/native/executors/Executor.kt index 12102bb2034..5769599f11b 100644 --- a/native/executors/src/main/kotlin/org/jetbrains/kotlin/native/executors/Executor.kt +++ b/native/executors/src/main/kotlin/org/jetbrains/kotlin/native/executors/Executor.kt @@ -7,7 +7,6 @@ package org.jetbrains.kotlin.native.executors import java.io.* import kotlin.time.Duration -import kotlin.time.ExperimentalTime private class CloseProtectedOutputStream(stream : OutputStream) : FilterOutputStream(stream) { override fun close() { @@ -52,7 +51,7 @@ data class ExecuteRequest( /** * Bound execution time of the process. By default it's [Duration.INFINITE] meaning it's unbounded. */ - val timeout: Duration = Duration.INFINITE + var timeout: Duration = Duration.INFINITE ) { /** * Create a copy of this [ExecuteRequest], modify the copy by running [block] on it, and return that copy. @@ -76,7 +75,13 @@ data class ExecuteResponse( * @throws IllegalStateException if [exitCode] is not 0. */ fun assertSuccess(): ExecuteResponse { - check(exitCode == 0) { "Exited with code $exitCode" } + check(exitCode == 0) { + if (exitCode == null) { + "Timed out in $executionTime" + } else { + "Exited with code $exitCode in $executionTime" + } + } return this } } @@ -95,4 +100,4 @@ interface Executor { * Run the process and wait for its completion. */ fun execute(request: ExecuteRequest): ExecuteResponse -} \ No newline at end of file +}