[Native][tests] Add an ability to fail tests on exceeded timeout
This commit is contained in:
@@ -46,7 +46,8 @@ enum class TestProperty(shortName: String) {
|
|||||||
KOTLIN_NATIVE_HOME("nativeHome"),
|
KOTLIN_NATIVE_HOME("nativeHome"),
|
||||||
COMPILER_CLASSPATH("compilerClasspath"),
|
COMPILER_CLASSPATH("compilerClasspath"),
|
||||||
TEST_MODE("mode"),
|
TEST_MODE("mode"),
|
||||||
USE_CACHE("useCache");
|
USE_CACHE("useCache"),
|
||||||
|
EXECUTION_TIMEOUT("executionTimeout");
|
||||||
|
|
||||||
private val propertyName = "kotlin.internal.native.test.$shortName"
|
private val propertyName = "kotlin.internal.native.test.$shortName"
|
||||||
|
|
||||||
@@ -81,6 +82,7 @@ if (kotlinBuildProperties.isKotlinNativeEnabled) {
|
|||||||
// Pass Gradle properties as JVM properties so test process can read them.
|
// Pass Gradle properties as JVM properties so test process can read them.
|
||||||
TestProperty.TEST_MODE.setUpFromGradleProperty(this)
|
TestProperty.TEST_MODE.setUpFromGradleProperty(this)
|
||||||
TestProperty.USE_CACHE.setUpFromGradleProperty(this)
|
TestProperty.USE_CACHE.setUpFromGradleProperty(this)
|
||||||
|
TestProperty.EXECUTION_TIMEOUT.setUpFromGradleProperty(this)
|
||||||
|
|
||||||
useJUnitPlatform()
|
useJUnitPlatform()
|
||||||
}
|
}
|
||||||
|
|||||||
+32
-14
@@ -6,7 +6,10 @@
|
|||||||
package org.jetbrains.kotlin.konan.blackboxtest.support
|
package org.jetbrains.kotlin.konan.blackboxtest.support
|
||||||
|
|
||||||
import org.jetbrains.kotlin.cli.common.ExitCode
|
import org.jetbrains.kotlin.cli.common.ExitCode
|
||||||
|
import org.jetbrains.kotlin.konan.blackboxtest.support.runner.AbstractRunner
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
import kotlin.time.Duration
|
||||||
|
import kotlin.time.DurationUnit
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A piece of information that makes sense for the user, and that can be logged to a file or
|
* A piece of information that makes sense for the user, and that can be logged to a file or
|
||||||
@@ -19,6 +22,12 @@ internal abstract class LoggedData {
|
|||||||
protected abstract fun computeText(): String
|
protected abstract fun computeText(): String
|
||||||
final override fun toString() = text
|
final override fun toString() = text
|
||||||
|
|
||||||
|
fun withErrorMessageHeader(errorMessageHeader: String): String = buildString {
|
||||||
|
appendLine(errorMessageHeader)
|
||||||
|
appendLine()
|
||||||
|
appendLine(this@LoggedData)
|
||||||
|
}
|
||||||
|
|
||||||
class CompilerParameters(
|
class CompilerParameters(
|
||||||
private val compilerArgs: Array<String>,
|
private val compilerArgs: Array<String>,
|
||||||
private val sourceModules: Collection<TestModule>
|
private val sourceModules: Collection<TestModule>
|
||||||
@@ -42,7 +51,7 @@ internal abstract class LoggedData {
|
|||||||
private val exitCode: ExitCode,
|
private val exitCode: ExitCode,
|
||||||
private val compilerOutput: String,
|
private val compilerOutput: String,
|
||||||
private val compilerOutputHasErrors: Boolean,
|
private val compilerOutputHasErrors: Boolean,
|
||||||
private val durationMillis: Long
|
private val duration: Duration
|
||||||
) : LoggedData() {
|
) : LoggedData() {
|
||||||
override fun computeText(): String {
|
override fun computeText(): String {
|
||||||
val problems = listOfNotNull(
|
val problems = listOfNotNull(
|
||||||
@@ -59,7 +68,7 @@ internal abstract class LoggedData {
|
|||||||
|
|
||||||
appendLine("COMPILER CALL:")
|
appendLine("COMPILER CALL:")
|
||||||
appendLine("- Exit code: ${exitCode.code} (${exitCode.name})")
|
appendLine("- Exit code: ${exitCode.code} (${exitCode.name})")
|
||||||
appendDuration(durationMillis)
|
appendDuration(duration)
|
||||||
appendLine()
|
appendLine()
|
||||||
appendLine("========== BEGIN: RAW COMPILER OUTPUT ==========")
|
appendLine("========== BEGIN: RAW COMPILER OUTPUT ==========")
|
||||||
if (compilerOutput.isNotEmpty()) appendLine(compilerOutput.trimEnd())
|
if (compilerOutput.isNotEmpty()) appendLine(compilerOutput.trimEnd())
|
||||||
@@ -100,22 +109,19 @@ internal abstract class LoggedData {
|
|||||||
|
|
||||||
class TestRun(
|
class TestRun(
|
||||||
private val parameters: TestRunParameters,
|
private val parameters: TestRunParameters,
|
||||||
private val exitCode: Int,
|
private val runResult: AbstractRunner.RunResult.Completed
|
||||||
private val stdOut: String,
|
|
||||||
private val stdErr: String,
|
|
||||||
private val durationMillis: Long
|
|
||||||
) : LoggedData() {
|
) : LoggedData() {
|
||||||
override fun computeText() = buildString {
|
override fun computeText() = buildString {
|
||||||
appendLine("TEST RUN:")
|
appendLine("TEST RUN:")
|
||||||
appendLine("- Exit code: $exitCode")
|
appendLine("- Exit code: ${runResult.exitCode}")
|
||||||
appendDuration(durationMillis)
|
appendDuration(runResult.duration)
|
||||||
appendLine()
|
appendLine()
|
||||||
appendLine("========== BEGIN: TEST STDOUT ==========")
|
appendLine("========== BEGIN: TEST STDOUT ==========")
|
||||||
if (stdOut.isNotEmpty()) appendLine(stdOut.trimEnd())
|
if (runResult.stdOut.isNotEmpty()) appendLine(runResult.stdOut.trimEnd())
|
||||||
appendLine("========== END: TEST STDOUT ==========")
|
appendLine("========== END: TEST STDOUT ==========")
|
||||||
appendLine()
|
appendLine()
|
||||||
appendLine("========== BEGIN: TEST STDERR ==========")
|
appendLine("========== BEGIN: TEST STDERR ==========")
|
||||||
if (stdErr.isNotEmpty()) appendLine(stdErr.trimEnd())
|
if (runResult.stdErr.isNotEmpty()) appendLine(runResult.stdErr.trimEnd())
|
||||||
appendLine("========== END: TEST STDERR ==========")
|
appendLine("========== END: TEST STDERR ==========")
|
||||||
appendLine()
|
appendLine()
|
||||||
appendLine(parameters)
|
appendLine(parameters)
|
||||||
@@ -139,15 +145,27 @@ internal abstract class LoggedData {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class TestRunTimeoutExceeded(parameters: TestRunParameters, timeout: Duration) : TimeoutExceeded(parameters, timeout)
|
||||||
|
|
||||||
|
abstract class TimeoutExceeded(
|
||||||
|
private val parameters: LoggedData,
|
||||||
|
private val timeout: Duration
|
||||||
|
) : LoggedData() {
|
||||||
|
override fun computeText() = buildString {
|
||||||
|
appendLine("TIMED OUT:")
|
||||||
|
appendLine("- Max permitted duration: $timeout")
|
||||||
|
appendLine()
|
||||||
|
appendLine(parameters)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
@JvmStatic
|
|
||||||
protected fun StringBuilder.appendList(header: String, list: Iterable<Any?>): StringBuilder {
|
protected fun StringBuilder.appendList(header: String, list: Iterable<Any?>): StringBuilder {
|
||||||
appendLine(header)
|
appendLine(header)
|
||||||
list.forEach(::appendLine)
|
list.forEach(::appendLine)
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
@JvmStatic
|
|
||||||
protected fun StringBuilder.appendArguments(header: String, args: Iterable<String>): StringBuilder {
|
protected fun StringBuilder.appendArguments(header: String, args: Iterable<String>): StringBuilder {
|
||||||
appendLine(header)
|
appendLine(header)
|
||||||
|
|
||||||
@@ -171,7 +189,7 @@ internal abstract class LoggedData {
|
|||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
protected fun StringBuilder.appendDuration(durationMillis: Long): StringBuilder =
|
protected fun StringBuilder.appendDuration(duration: Duration): StringBuilder =
|
||||||
append("- Duration: ").append(String.format("%.2f", durationMillis.toDouble() / 1000)).appendLine(" seconds")
|
append("- Duration: ").appendLine(duration.toString(DurationUnit.SECONDS, 2))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+13
-12
@@ -17,7 +17,9 @@ import org.jetbrains.kotlin.konan.blackboxtest.support.TestModule.Companion.allF
|
|||||||
import org.jetbrains.kotlin.konan.blackboxtest.support.util.*
|
import org.jetbrains.kotlin.konan.blackboxtest.support.util.*
|
||||||
import org.jetbrains.kotlin.test.services.JUnit5Assertions.fail
|
import org.jetbrains.kotlin.test.services.JUnit5Assertions.fail
|
||||||
import java.io.*
|
import java.io.*
|
||||||
import kotlin.system.measureTimeMillis
|
import kotlin.time.Duration
|
||||||
|
import kotlin.time.ExperimentalTime
|
||||||
|
import kotlin.time.measureTime
|
||||||
|
|
||||||
internal class TestCompilationFactory(private val environment: TestEnvironment) {
|
internal class TestCompilationFactory(private val environment: TestEnvironment) {
|
||||||
private val cachedCompilations = ThreadSafeCache<TestCompilationCacheKey, TestCompilation>()
|
private val cachedCompilations = ThreadSafeCache<TestCompilationCacheKey, TestCompilation>()
|
||||||
@@ -175,14 +177,12 @@ internal sealed interface TestCompilationResult {
|
|||||||
is DependencyFailures -> fail { describeDependencyFailures() }
|
is DependencyFailures -> fail { describeDependencyFailures() }
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun Failure.describeFailure() = buildString {
|
private fun Failure.describeFailure() = loggedData.withErrorMessageHeader(
|
||||||
when (this@describeFailure) {
|
when (this@describeFailure) {
|
||||||
is CompilerFailure -> appendLine("Compilation failed.")
|
is CompilerFailure -> "Compilation failed."
|
||||||
is UnexpectedFailure -> appendLine("Compilation failed with unexpected exception.")
|
is UnexpectedFailure -> "Compilation failed with unexpected exception."
|
||||||
}
|
}
|
||||||
appendLine()
|
)
|
||||||
appendLine(loggedData)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun DependencyFailures.describeDependencyFailures() =
|
private fun DependencyFailures.describeDependencyFailures() =
|
||||||
buildString {
|
buildString {
|
||||||
@@ -270,13 +270,13 @@ private class TestCompilationImpl(
|
|||||||
val loggedCompilerParameters = LoggedData.CompilerParameters(compilerArgs, sourceModules)
|
val loggedCompilerParameters = LoggedData.CompilerParameters(compilerArgs, sourceModules)
|
||||||
|
|
||||||
val (loggedCompilerCall: LoggedData, result: TestCompilationResult.ImmediateResult) = try {
|
val (loggedCompilerCall: LoggedData, result: TestCompilationResult.ImmediateResult) = try {
|
||||||
val (exitCode, compilerOutput, compilerOutputHasErrors, durationMillis) = callCompiler(
|
val (exitCode, compilerOutput, compilerOutputHasErrors, duration) = callCompiler(
|
||||||
compilerArgs = compilerArgs,
|
compilerArgs = compilerArgs,
|
||||||
lazyKotlinNativeClassLoader = environment.globalEnvironment.lazyKotlinNativeClassLoader
|
lazyKotlinNativeClassLoader = environment.globalEnvironment.lazyKotlinNativeClassLoader
|
||||||
)
|
)
|
||||||
|
|
||||||
val loggedCompilerCall =
|
val loggedCompilerCall =
|
||||||
LoggedData.CompilerCall(loggedCompilerParameters, exitCode, compilerOutput, compilerOutputHasErrors, durationMillis)
|
LoggedData.CompilerCall(loggedCompilerParameters, exitCode, compilerOutput, compilerOutputHasErrors, duration)
|
||||||
|
|
||||||
val result = if (exitCode != ExitCode.OK || compilerOutputHasErrors)
|
val result = if (exitCode != ExitCode.OK || compilerOutputHasErrors)
|
||||||
TestCompilationResult.CompilerFailure(loggedCompilerCall)
|
TestCompilationResult.CompilerFailure(loggedCompilerCall)
|
||||||
@@ -331,7 +331,8 @@ private fun callCompiler(compilerArgs: Array<String>, lazyKotlinNativeClassLoade
|
|||||||
val compilerXmlOutput: ByteArrayOutputStream
|
val compilerXmlOutput: ByteArrayOutputStream
|
||||||
val exitCode: ExitCode
|
val exitCode: ExitCode
|
||||||
|
|
||||||
val durationMillis = measureTimeMillis {
|
@OptIn(ExperimentalTime::class)
|
||||||
|
val duration = measureTime {
|
||||||
val kotlinNativeClassLoader by lazyKotlinNativeClassLoader
|
val kotlinNativeClassLoader by lazyKotlinNativeClassLoader
|
||||||
|
|
||||||
val servicesClass = Class.forName(Services::class.java.canonicalName, true, kotlinNativeClassLoader)
|
val servicesClass = Class.forName(Services::class.java.canonicalName, true, kotlinNativeClassLoader)
|
||||||
@@ -372,14 +373,14 @@ private fun callCompiler(compilerArgs: Array<String>, lazyKotlinNativeClassLoade
|
|||||||
compilerOutput = outputStream.toString(Charsets.UTF_8.name())
|
compilerOutput = outputStream.toString(Charsets.UTF_8.name())
|
||||||
}
|
}
|
||||||
|
|
||||||
return CompilerCallResult(exitCode, compilerOutput, messageCollector.hasErrors(), durationMillis)
|
return CompilerCallResult(exitCode, compilerOutput, messageCollector.hasErrors(), duration)
|
||||||
}
|
}
|
||||||
|
|
||||||
private data class CompilerCallResult(
|
private data class CompilerCallResult(
|
||||||
val exitCode: ExitCode,
|
val exitCode: ExitCode,
|
||||||
val compilerOutput: String,
|
val compilerOutput: String,
|
||||||
val compilerOutputHasErrors: Boolean,
|
val compilerOutputHasErrors: Boolean,
|
||||||
val durationMillis: Long
|
val duration: Duration
|
||||||
)
|
)
|
||||||
|
|
||||||
private fun getLogFile(expectedArtifactFile: File): File = expectedArtifactFile.resolveSibling(expectedArtifactFile.name + ".log")
|
private fun getLogFile(expectedArtifactFile: File): File = expectedArtifactFile.resolveSibling(expectedArtifactFile.name + ".log")
|
||||||
|
|||||||
+16
@@ -11,6 +11,9 @@ import org.jetbrains.kotlin.konan.blackboxtest.support.util.TestDisposable
|
|||||||
import org.jetbrains.kotlin.test.services.JUnit5Assertions.fail
|
import org.jetbrains.kotlin.test.services.JUnit5Assertions.fail
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.net.URLClassLoader
|
import java.net.URLClassLoader
|
||||||
|
import kotlin.time.Duration
|
||||||
|
import kotlin.time.Duration.Companion.seconds
|
||||||
|
import kotlin.time.Duration.Companion.milliseconds
|
||||||
|
|
||||||
internal class TestEnvironment(
|
internal class TestEnvironment(
|
||||||
val globalEnvironment: GlobalTestEnvironment,
|
val globalEnvironment: GlobalTestEnvironment,
|
||||||
@@ -27,6 +30,7 @@ internal class GlobalTestEnvironment(
|
|||||||
val lazyKotlinNativeClassLoader: Lazy<ClassLoader> = defaultKotlinNativeClassLoader,
|
val lazyKotlinNativeClassLoader: Lazy<ClassLoader> = defaultKotlinNativeClassLoader,
|
||||||
val testMode: TestMode = defaultTestMode,
|
val testMode: TestMode = defaultTestMode,
|
||||||
val cacheSettings: TestCacheSettings = defaultCacheSettings,
|
val cacheSettings: TestCacheSettings = defaultCacheSettings,
|
||||||
|
val executionTimeout: Duration = defaultExecutionTimeout,
|
||||||
val baseBuildDir: File = projectBuildDir
|
val baseBuildDir: File = projectBuildDir
|
||||||
) {
|
) {
|
||||||
val hostTarget: KonanTarget = HostManager.host
|
val hostTarget: KonanTarget = HostManager.host
|
||||||
@@ -73,6 +77,15 @@ internal class GlobalTestEnvironment(
|
|||||||
if (useCache) TestCacheSettings.WithCache else TestCacheSettings.WithoutCache
|
if (useCache) TestCacheSettings.WithCache else TestCacheSettings.WithoutCache
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private val defaultExecutionTimeout: Duration = run {
|
||||||
|
val executionTimeoutValue = System.getProperty(EXECUTION_TIMEOUT)
|
||||||
|
if (executionTimeoutValue != null) {
|
||||||
|
executionTimeoutValue.toLongOrNull()?.milliseconds
|
||||||
|
?: fail { "Invalid value for $EXECUTION_TIMEOUT system property: $executionTimeoutValue" }
|
||||||
|
} else
|
||||||
|
DEFAULT_EXECUTION_TIMEOUT
|
||||||
|
}
|
||||||
|
|
||||||
private val projectBuildDir: File
|
private val projectBuildDir: File
|
||||||
get() = System.getenv(PROJECT_BUILD_DIR)?.let(::File) ?: fail { "Non-specified $PROJECT_BUILD_DIR environment variable" }
|
get() = System.getenv(PROJECT_BUILD_DIR)?.let(::File) ?: fail { "Non-specified $PROJECT_BUILD_DIR environment variable" }
|
||||||
|
|
||||||
@@ -80,7 +93,10 @@ internal class GlobalTestEnvironment(
|
|||||||
private const val COMPILER_CLASSPATH = "kotlin.internal.native.test.compilerClasspath"
|
private const val COMPILER_CLASSPATH = "kotlin.internal.native.test.compilerClasspath"
|
||||||
private const val TEST_MODE = "kotlin.internal.native.test.mode"
|
private const val TEST_MODE = "kotlin.internal.native.test.mode"
|
||||||
private const val USE_CACHE = "kotlin.internal.native.test.useCache"
|
private const val USE_CACHE = "kotlin.internal.native.test.useCache"
|
||||||
|
private const val EXECUTION_TIMEOUT = "kotlin.internal.native.test.executionTimeout"
|
||||||
private const val PROJECT_BUILD_DIR = "PROJECT_BUILD_DIR"
|
private const val PROJECT_BUILD_DIR = "PROJECT_BUILD_DIR"
|
||||||
|
|
||||||
|
private val DEFAULT_EXECUTION_TIMEOUT get() = 10.seconds // Use no backing field to avoid null-initialized value.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -76,7 +76,7 @@ internal class TestRunProvider(
|
|||||||
|
|
||||||
// Currently, only local test runner is supported.
|
// Currently, only local test runner is supported.
|
||||||
fun createRunner(testRun: TestRun): AbstractRunner<*> = when (val target = environment.globalEnvironment.target) {
|
fun createRunner(testRun: TestRun): AbstractRunner<*> = when (val target = environment.globalEnvironment.target) {
|
||||||
environment.globalEnvironment.hostTarget -> LocalTestRunner(testRun)
|
environment.globalEnvironment.hostTarget -> LocalTestRunner(testRun, environment.globalEnvironment.executionTimeout)
|
||||||
else -> fail {
|
else -> fail {
|
||||||
"""
|
"""
|
||||||
Running at non-host target is not supported yet.
|
Running at non-host target is not supported yet.
|
||||||
|
|||||||
+26
-11
@@ -6,32 +6,47 @@
|
|||||||
package org.jetbrains.kotlin.konan.blackboxtest.support.runner
|
package org.jetbrains.kotlin.konan.blackboxtest.support.runner
|
||||||
|
|
||||||
import org.jetbrains.kotlin.konan.blackboxtest.support.TestExecutable
|
import org.jetbrains.kotlin.konan.blackboxtest.support.TestExecutable
|
||||||
|
import org.jetbrains.kotlin.konan.blackboxtest.support.runner.AbstractRunner.AbstractRun
|
||||||
|
import kotlin.time.*
|
||||||
|
|
||||||
internal abstract class AbstractLocalProcessRunner<R> : AbstractRunner<R>() {
|
internal abstract class AbstractLocalProcessRunner<R>(private val executionTimeout: Duration) : 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>
|
||||||
|
|
||||||
protected open fun customizeProcess(process: Process) = Unit
|
protected open fun customizeProcess(process: Process) = Unit
|
||||||
|
|
||||||
|
@OptIn(ExperimentalTime::class)
|
||||||
final override fun buildRun() = AbstractRun {
|
final override fun buildRun() = AbstractRun {
|
||||||
val startTimeMillis = System.currentTimeMillis()
|
val exitCode: Int
|
||||||
|
|
||||||
val process = ProcessBuilder(programArgs).directory(executable.executableFile.parentFile).start()
|
val stdOut: String
|
||||||
customizeProcess(process)
|
val stdErr: String
|
||||||
|
|
||||||
val exitCode = process.waitFor()
|
val duration = measureTime {
|
||||||
val finishTimeMillis = System.currentTimeMillis()
|
val process = ProcessBuilder(programArgs).directory(executable.executableFile.parentFile).start()
|
||||||
|
customizeProcess(process)
|
||||||
|
|
||||||
val stdOut = process.inputStream.bufferedReader().readText()
|
val hasFinishedInTime = process.waitFor(
|
||||||
val stdErr = process.errorStream.bufferedReader().readText()
|
executionTimeout.toLong(DurationUnit.MILLISECONDS),
|
||||||
|
DurationUnit.MILLISECONDS.toTimeUnit()
|
||||||
|
)
|
||||||
|
|
||||||
RunResult(exitCode, finishTimeMillis - startTimeMillis, stdOut, stdErr)
|
if (!hasFinishedInTime)
|
||||||
|
return@AbstractRun RunResult.TimeoutExceeded(executionTimeout)
|
||||||
|
|
||||||
|
exitCode = process.exitValue()
|
||||||
|
|
||||||
|
stdOut = process.inputStream.bufferedReader().readText()
|
||||||
|
stdErr = process.errorStream.bufferedReader().readText()
|
||||||
|
}
|
||||||
|
|
||||||
|
RunResult.Completed(exitCode, duration, stdOut, stdErr)
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract override fun buildResultHandler(runResult: RunResult): ResultHandler // Narrow returned type.
|
abstract override fun buildResultHandler(runResult: RunResult.Completed): ResultHandler // Narrow returned type.
|
||||||
|
|
||||||
abstract inner class ResultHandler(runResult: RunResult) : AbstractRunner<R>.ResultHandler(runResult) {
|
abstract inner class ResultHandler(runResult: RunResult.Completed) : AbstractRunner<R>.ResultHandler(runResult) {
|
||||||
override fun handle(): R {
|
override fun handle(): R {
|
||||||
verifyExpectation(0, runResult.exitCode) { "$visibleProcessName exited with non-zero code." }
|
verifyExpectation(0, runResult.exitCode) { "$visibleProcessName exited with non-zero code." }
|
||||||
|
|
||||||
|
|||||||
+20
-19
@@ -8,17 +8,26 @@ 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.assertEquals
|
import org.jetbrains.kotlin.test.services.JUnit5Assertions.assertEquals
|
||||||
import org.jetbrains.kotlin.test.services.JUnit5Assertions.assertTrue
|
import org.jetbrains.kotlin.test.services.JUnit5Assertions.assertTrue
|
||||||
import java.lang.AssertionError
|
import org.jetbrains.kotlin.test.services.JUnit5Assertions.fail
|
||||||
|
import kotlin.time.Duration
|
||||||
|
|
||||||
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): ResultHandler
|
protected abstract fun buildResultHandler(runResult: RunResult.Completed): ResultHandler
|
||||||
|
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 = buildResultHandler(runResult)
|
val resultHandler = when (val runResult = run.run()) {
|
||||||
|
is RunResult.TimeoutExceeded -> fail {
|
||||||
|
LoggedData.TestRunTimeoutExceeded(getLoggedParameters(), runResult.timeout)
|
||||||
|
.withErrorMessageHeader("Timeout exceeded during test execution.")
|
||||||
|
}
|
||||||
|
is RunResult.Completed -> buildResultHandler(runResult)
|
||||||
|
}
|
||||||
|
|
||||||
resultHandler.handle()
|
resultHandler.handle()
|
||||||
} catch (t: Throwable) {
|
} catch (t: Throwable) {
|
||||||
if (t is AssertionError)
|
if (t is AssertionError)
|
||||||
@@ -33,29 +42,21 @@ internal abstract class AbstractRunner<R> {
|
|||||||
fun run(): RunResult
|
fun run(): RunResult
|
||||||
}
|
}
|
||||||
|
|
||||||
data class RunResult(val exitCode: Int, val durationMillis: Long, val stdOut: String, val stdErr: String)
|
sealed interface RunResult {
|
||||||
|
data class Completed(val exitCode: Int, val duration: Duration, val stdOut: String, val stdErr: String) : RunResult
|
||||||
|
data class TimeoutExceeded(val timeout: Duration) : RunResult
|
||||||
|
}
|
||||||
|
|
||||||
abstract inner class ResultHandler(protected val runResult: RunResult) {
|
abstract inner class ResultHandler(protected val runResult: RunResult.Completed) {
|
||||||
abstract fun getLoggedRun(): LoggedData
|
abstract fun getLoggedRun(): LoggedData
|
||||||
abstract fun handle(): R
|
abstract fun handle(): R
|
||||||
|
|
||||||
val exitCode get() = runResult.exitCode
|
|
||||||
val durationMillis get() = runResult.durationMillis
|
|
||||||
val stdOut get() = runResult.stdOut
|
|
||||||
val stdErr get() = runResult.stdErr
|
|
||||||
|
|
||||||
protected inline fun <T> verifyExpectation(expected: T, actual: T, crossinline errorMessageHeader: () -> String) {
|
protected inline fun <T> verifyExpectation(expected: T, actual: T, crossinline errorMessageHeader: () -> String) {
|
||||||
assertEquals(expected, actual) { formatErrorMessage(errorMessageHeader) }
|
assertEquals(expected, actual) { getLoggedRun().withErrorMessageHeader(errorMessageHeader()) }
|
||||||
}
|
}
|
||||||
|
|
||||||
protected inline fun verifyExpectation(shouldBeTrue: Boolean, crossinline errorMessageHeader: () -> String) {
|
protected inline fun verifyExpectation(shouldBeTrue: Boolean, crossinline errorMessageHeader: () -> String) {
|
||||||
assertTrue(shouldBeTrue) { formatErrorMessage(errorMessageHeader) }
|
assertTrue(shouldBeTrue) { getLoggedRun().withErrorMessageHeader(errorMessageHeader()) }
|
||||||
}
|
|
||||||
|
|
||||||
private inline fun formatErrorMessage(errorMessageHeader: () -> String) = buildString {
|
|
||||||
appendLine(errorMessageHeader())
|
|
||||||
appendLine()
|
|
||||||
appendLine(getLoggedRun())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+19
-19
@@ -8,8 +8,12 @@ package org.jetbrains.kotlin.konan.blackboxtest.support.runner
|
|||||||
import com.intellij.openapi.util.text.StringUtilRt.convertLineSeparators
|
import com.intellij.openapi.util.text.StringUtilRt.convertLineSeparators
|
||||||
import org.jetbrains.kotlin.konan.blackboxtest.support.*
|
import org.jetbrains.kotlin.konan.blackboxtest.support.*
|
||||||
import org.jetbrains.kotlin.test.services.JUnit5Assertions.fail
|
import org.jetbrains.kotlin.test.services.JUnit5Assertions.fail
|
||||||
|
import kotlin.time.Duration
|
||||||
|
|
||||||
internal class LocalTestRunner(private val testRun: TestRun) : AbstractLocalProcessRunner<Unit>() {
|
internal class LocalTestRunner(
|
||||||
|
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
|
||||||
|
|
||||||
@@ -18,13 +22,12 @@ internal class LocalTestRunner(private val testRun: TestRun) : AbstractLocalProc
|
|||||||
testRun.runParameters.forEach { it.applyTo(this) }
|
testRun.runParameters.forEach { it.applyTo(this) }
|
||||||
}
|
}
|
||||||
|
|
||||||
private val loggedParameters: LoggedData.TestRunParameters
|
override fun getLoggedParameters() = LoggedData.TestRunParameters(
|
||||||
get() = LoggedData.TestRunParameters(
|
compilerCall = executable.loggedCompilerCall,
|
||||||
compilerCall = executable.loggedCompilerCall,
|
origin = testRun.origin,
|
||||||
origin = testRun.origin,
|
runArgs = programArgs,
|
||||||
runArgs = programArgs,
|
runParameters = testRun.runParameters
|
||||||
runParameters = testRun.runParameters
|
)
|
||||||
)
|
|
||||||
|
|
||||||
override fun customizeProcess(process: Process) {
|
override fun customizeProcess(process: Process) {
|
||||||
testRun.runParameters.get<TestRunParameter.WithInputData> {
|
testRun.runParameters.get<TestRunParameter.WithInputData> {
|
||||||
@@ -33,18 +36,15 @@ internal class LocalTestRunner(private val testRun: TestRun) : AbstractLocalProc
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun buildResultHandler(runResult: RunResult) = ResultHandler(runResult)
|
override fun buildResultHandler(runResult: RunResult.Completed) = ResultHandler(runResult)
|
||||||
|
|
||||||
override fun handleUnexpectedFailure(t: Throwable) = fail {
|
override fun handleUnexpectedFailure(t: Throwable) = fail {
|
||||||
buildString {
|
LoggedData.TestRunUnexpectedFailure(getLoggedParameters(), t)
|
||||||
appendLine("Test execution failed with unexpected exception.")
|
.withErrorMessageHeader("Test execution failed with unexpected exception.")
|
||||||
appendLine()
|
|
||||||
appendLine(LoggedData.TestRunUnexpectedFailure(loggedParameters, t))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
inner class ResultHandler(runResult: RunResult) : AbstractLocalProcessRunner<Unit>.ResultHandler(runResult) {
|
inner class ResultHandler(runResult: RunResult.Completed) : AbstractLocalProcessRunner<Unit>.ResultHandler(runResult) {
|
||||||
override fun getLoggedRun() = LoggedData.TestRun(loggedParameters, exitCode, stdOut, stdErr, durationMillis)
|
override fun getLoggedRun() = LoggedData.TestRun(getLoggedParameters(), runResult)
|
||||||
|
|
||||||
override fun doHandle() {
|
override fun doHandle() {
|
||||||
if (testRun.runParameters.has<TestRunParameter.WithGTestLogger>()) {
|
if (testRun.runParameters.has<TestRunParameter.WithGTestLogger>()) {
|
||||||
@@ -59,7 +59,7 @@ internal class LocalTestRunner(private val testRun: TestRun) : AbstractLocalProc
|
|||||||
val cleanStdOut = StringBuilder()
|
val cleanStdOut = StringBuilder()
|
||||||
|
|
||||||
var expectStatusLine = false
|
var expectStatusLine = false
|
||||||
stdOut.lines().forEach { line ->
|
runResult.stdOut.lines().forEach { line ->
|
||||||
when {
|
when {
|
||||||
expectStatusLine -> {
|
expectStatusLine -> {
|
||||||
val matcher = GTEST_STATUS_LINE_REGEX.matchEntire(line)
|
val matcher = GTEST_STATUS_LINE_REGEX.matchEntire(line)
|
||||||
@@ -94,10 +94,10 @@ internal class LocalTestRunner(private val testRun: TestRun) : AbstractLocalProc
|
|||||||
val failedTests = (testStatuses - GTEST_STATUS_OK).values.sumOf { it.size }
|
val failedTests = (testStatuses - GTEST_STATUS_OK).values.sumOf { it.size }
|
||||||
verifyExpectation(0, failedTests) { "There are failed tests." }
|
verifyExpectation(0, failedTests) { "There are failed tests." }
|
||||||
|
|
||||||
verifyOutputData(mergedOutput = cleanStdOut.toString() + stdErr)
|
verifyOutputData(mergedOutput = cleanStdOut.toString() + runResult.stdErr)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun verifyPlainTest() = verifyOutputData(mergedOutput = stdOut + stdErr)
|
private fun verifyPlainTest() = verifyOutputData(mergedOutput = runResult.stdOut + runResult.stdErr)
|
||||||
|
|
||||||
private fun verifyOutputData(mergedOutput: String) {
|
private fun verifyOutputData(mergedOutput: String) {
|
||||||
testRun.runParameters.get<TestRunParameter.WithExpectedOutputData> {
|
testRun.runParameters.get<TestRunParameter.WithExpectedOutputData> {
|
||||||
|
|||||||
Reference in New Issue
Block a user