[K/N] Apply execution timeout to tests

This commit is contained in:
Alexander Shabalin
2023-05-24 16:20:01 +02:00
committed by Space Team
parent 89be94f123
commit 0457001a87
6 changed files with 40 additions and 9 deletions
@@ -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
@@ -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() }
}
}
@@ -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")
}
@@ -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> {
project.apply<GitClangFormatPlugin>()
project.extensions.create<CompileToBitcodeExtension>("bitcode", project)
}
}
}
@@ -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<RunGTestJob.Parameters> {
interface Parameters : WorkParameters {
@@ -29,6 +32,7 @@ private abstract class RunGTestJob : WorkAction<RunGTestJob.Parameters> {
// 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<String>
val executionTimeout: Property<Duration>
}
// 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<RunGTestJob.Parameters> {
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<KonanTarget>
/**
* Timeout for the test run.
*/
@get:Input
abstract val executionTimeout: Property<Duration>
@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<PlatformManager>())
targetName.set(this@RunGTest.target.get().name)
executionTimeout.set(this@RunGTest.executionTimeout)
}
}
}
@@ -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
}
}