[Native][tests] Log test process stdout/stderr when failed on timeout
This commit is contained in:
+20
-16
@@ -113,16 +113,7 @@ internal abstract class LoggedData {
|
|||||||
) : LoggedData() {
|
) : LoggedData() {
|
||||||
override fun computeText() = buildString {
|
override fun computeText() = buildString {
|
||||||
appendLine("TEST RUN:")
|
appendLine("TEST RUN:")
|
||||||
appendLine("- Exit code: ${runResult.exitCode}")
|
appendCommonRunResult(runResult)
|
||||||
appendDuration(runResult.duration)
|
|
||||||
appendLine()
|
|
||||||
appendLine("========== BEGIN: TEST STDOUT ==========")
|
|
||||||
if (runResult.stdOut.isNotEmpty()) appendLine(runResult.stdOut.trimEnd())
|
|
||||||
appendLine("========== END: TEST STDOUT ==========")
|
|
||||||
appendLine()
|
|
||||||
appendLine("========== BEGIN: TEST STDERR ==========")
|
|
||||||
if (runResult.stdErr.isNotEmpty()) appendLine(runResult.stdErr.trimEnd())
|
|
||||||
appendLine("========== END: TEST STDERR ==========")
|
|
||||||
appendLine()
|
appendLine()
|
||||||
appendLine(parameters)
|
appendLine(parameters)
|
||||||
}
|
}
|
||||||
@@ -145,15 +136,14 @@ internal abstract class LoggedData {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class TestRunTimeoutExceeded(parameters: TestRunParameters, timeout: Duration) : TimeoutExceeded(parameters, timeout)
|
class TestRunTimeoutExceeded(
|
||||||
|
private val parameters: TestRunParameters,
|
||||||
abstract class TimeoutExceeded(
|
private val runResult: RunResult.TimeoutExceeded
|
||||||
private val parameters: LoggedData,
|
|
||||||
private val timeout: Duration
|
|
||||||
) : LoggedData() {
|
) : LoggedData() {
|
||||||
override fun computeText() = buildString {
|
override fun computeText() = buildString {
|
||||||
appendLine("TIMED OUT:")
|
appendLine("TIMED OUT:")
|
||||||
appendLine("- Max permitted duration: $timeout")
|
appendLine("- Max permitted duration: ${runResult.timeout}")
|
||||||
|
appendCommonRunResult(runResult)
|
||||||
appendLine()
|
appendLine()
|
||||||
appendLine(parameters)
|
appendLine(parameters)
|
||||||
}
|
}
|
||||||
@@ -191,5 +181,19 @@ internal abstract class LoggedData {
|
|||||||
|
|
||||||
protected fun StringBuilder.appendDuration(duration: Duration): StringBuilder =
|
protected fun StringBuilder.appendDuration(duration: Duration): StringBuilder =
|
||||||
append("- Duration: ").appendLine(duration.toString(DurationUnit.SECONDS, 2))
|
append("- Duration: ").appendLine(duration.toString(DurationUnit.SECONDS, 2))
|
||||||
|
|
||||||
|
protected fun StringBuilder.appendCommonRunResult(runResult: RunResult): StringBuilder {
|
||||||
|
appendLine("- Exit code: ${runResult.exitCode ?: "<unknown>"}")
|
||||||
|
appendDuration(runResult.duration)
|
||||||
|
appendLine()
|
||||||
|
appendLine("========== BEGIN: TEST STDOUT ==========")
|
||||||
|
if (runResult.output.stdOut.isNotEmpty()) appendLine(runResult.output.stdOut.trimEnd())
|
||||||
|
appendLine("========== END: TEST STDOUT ==========")
|
||||||
|
appendLine()
|
||||||
|
appendLine("========== BEGIN: TEST STDERR ==========")
|
||||||
|
if (runResult.output.stdErr.isNotEmpty()) appendLine(runResult.output.stdErr.trimEnd())
|
||||||
|
appendLine("========== END: TEST STDERR ==========")
|
||||||
|
return this
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+25
-16
@@ -7,6 +7,7 @@ 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 org.jetbrains.kotlin.konan.blackboxtest.support.runner.AbstractRunner.AbstractRun
|
||||||
|
import org.jetbrains.kotlin.konan.blackboxtest.support.util.readOutput
|
||||||
import kotlin.time.*
|
import kotlin.time.*
|
||||||
|
|
||||||
internal abstract class AbstractLocalProcessRunner<R>(private val executionTimeout: Duration) : AbstractRunner<R>() {
|
internal abstract class AbstractLocalProcessRunner<R>(private val executionTimeout: Duration) : AbstractRunner<R>() {
|
||||||
@@ -18,32 +19,40 @@ internal abstract class AbstractLocalProcessRunner<R>(private val executionTimeo
|
|||||||
|
|
||||||
@OptIn(ExperimentalTime::class)
|
@OptIn(ExperimentalTime::class)
|
||||||
final override fun buildRun() = AbstractRun {
|
final override fun buildRun() = AbstractRun {
|
||||||
val exitCode: Int
|
val (result, duration) = measureTimedValue {
|
||||||
|
|
||||||
val stdOut: String
|
|
||||||
val stdErr: String
|
|
||||||
|
|
||||||
val duration = measureTime {
|
|
||||||
val process = ProcessBuilder(programArgs).directory(executable.executableFile.parentFile).start()
|
val process = ProcessBuilder(programArgs).directory(executable.executableFile.parentFile).start()
|
||||||
customizeProcess(process)
|
customizeProcess(process)
|
||||||
|
|
||||||
val hasFinishedInTime = process.waitFor(
|
val hasFinishedOnTime = process.waitFor(
|
||||||
executionTimeout.toLong(DurationUnit.MILLISECONDS),
|
executionTimeout.toLong(DurationUnit.MILLISECONDS),
|
||||||
DurationUnit.MILLISECONDS.toTimeUnit()
|
DurationUnit.MILLISECONDS.toTimeUnit()
|
||||||
)
|
)
|
||||||
|
|
||||||
if (!hasFinishedInTime) {
|
process to hasFinishedOnTime
|
||||||
process.destroy()
|
}
|
||||||
return@AbstractRun RunResult.TimeoutExceeded(executionTimeout)
|
val (process, hasFinishedOnTime) = result
|
||||||
|
|
||||||
|
// Don't use blocking read from stdout/stderr on non-finished process. If the process is hanging this would result in hanging test.
|
||||||
|
val output = process.readOutput(nonBlocking = !hasFinishedOnTime)
|
||||||
|
|
||||||
|
if (hasFinishedOnTime) {
|
||||||
|
val exitCode: Int = process.exitValue()
|
||||||
|
|
||||||
|
RunResult.Completed(exitCode, duration, output)
|
||||||
|
} else {
|
||||||
|
process.destroy() // Initiate destroy of non-finished process.
|
||||||
|
Thread.sleep(5) // And give it a white to become actually destroyed.
|
||||||
|
|
||||||
|
val exitCode: Int? = try {
|
||||||
|
// If we are lucky enough, the process is destroyed to this moment. And it's possible to fetch exit code.
|
||||||
|
process.exitValue()
|
||||||
|
} catch (_: IllegalThreadStateException) {
|
||||||
|
// Still not destroyed. Let's go further.
|
||||||
|
null
|
||||||
}
|
}
|
||||||
|
|
||||||
exitCode = process.exitValue()
|
RunResult.TimeoutExceeded(executionTimeout, exitCode, duration, output)
|
||||||
|
|
||||||
stdOut = process.inputStream.bufferedReader().readText()
|
|
||||||
stdErr = process.errorStream.bufferedReader().readText()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
RunResult.Completed(exitCode, duration, stdOut, stdErr)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract override fun buildResultHandler(runResult: RunResult.Completed): ResultHandler // Narrow returned type.
|
abstract override fun buildResultHandler(runResult: RunResult.Completed): ResultHandler // Narrow returned type.
|
||||||
|
|||||||
+1
-1
@@ -21,7 +21,7 @@ internal abstract class AbstractRunner<R> {
|
|||||||
|
|
||||||
val resultHandler = when (val runResult = run.run()) {
|
val resultHandler = when (val runResult = run.run()) {
|
||||||
is RunResult.TimeoutExceeded -> fail {
|
is RunResult.TimeoutExceeded -> fail {
|
||||||
LoggedData.TestRunTimeoutExceeded(getLoggedParameters(), runResult.timeout)
|
LoggedData.TestRunTimeoutExceeded(getLoggedParameters(), runResult)
|
||||||
.withErrorMessageHeader("Timeout exceeded during test execution.")
|
.withErrorMessageHeader("Timeout exceeded during test execution.")
|
||||||
}
|
}
|
||||||
is RunResult.Completed -> buildResultHandler(runResult)
|
is RunResult.Completed -> buildResultHandler(runResult)
|
||||||
|
|||||||
+3
-3
@@ -59,7 +59,7 @@ internal class LocalTestRunner(
|
|||||||
val cleanStdOut = StringBuilder()
|
val cleanStdOut = StringBuilder()
|
||||||
|
|
||||||
var expectStatusLine = false
|
var expectStatusLine = false
|
||||||
runResult.stdOut.lines().forEach { line ->
|
runResult.output.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(
|
|||||||
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() + runResult.stdErr)
|
verifyOutputData(mergedOutput = cleanStdOut.toString() + runResult.output.stdErr)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun verifyPlainTest() = verifyOutputData(mergedOutput = runResult.stdOut + runResult.stdErr)
|
private fun verifyPlainTest() = verifyOutputData(mergedOutput = runResult.output.stdOut + runResult.output.stdErr)
|
||||||
|
|
||||||
private fun verifyOutputData(mergedOutput: String) {
|
private fun verifyOutputData(mergedOutput: String) {
|
||||||
testRun.runParameters.get<TestRunParameter.WithExpectedOutputData> {
|
testRun.runParameters.get<TestRunParameter.WithExpectedOutputData> {
|
||||||
|
|||||||
+19
-3
@@ -7,7 +7,23 @@ package org.jetbrains.kotlin.konan.blackboxtest.support.runner
|
|||||||
|
|
||||||
import kotlin.time.Duration
|
import kotlin.time.Duration
|
||||||
|
|
||||||
sealed interface RunResult {
|
internal sealed interface RunResult {
|
||||||
data class Completed(val exitCode: Int, val duration: Duration, val stdOut: String, val stdErr: String) : RunResult
|
val exitCode: Int?
|
||||||
data class TimeoutExceeded(val timeout: Duration) : RunResult
|
val duration: Duration
|
||||||
|
val output: ProcessOutput
|
||||||
|
|
||||||
|
data class Completed(
|
||||||
|
override val exitCode: Int,
|
||||||
|
override val duration: Duration,
|
||||||
|
override val output: ProcessOutput
|
||||||
|
) : RunResult
|
||||||
|
|
||||||
|
data class TimeoutExceeded(
|
||||||
|
val timeout: Duration,
|
||||||
|
override val exitCode: Int?,
|
||||||
|
override val duration: Duration,
|
||||||
|
override val output: ProcessOutput
|
||||||
|
) : RunResult
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal class ProcessOutput(val stdOut: String, val stdErr: String)
|
||||||
|
|||||||
@@ -0,0 +1,43 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2021 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.util
|
||||||
|
|
||||||
|
import org.jetbrains.kotlin.konan.blackboxtest.support.runner.ProcessOutput
|
||||||
|
import java.io.ByteArrayOutputStream
|
||||||
|
import java.io.InputStream
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read bytes from the given [InputStream] without blocking.
|
||||||
|
*
|
||||||
|
* Note: This function does not guarantee that the whole [InputStream] contents is read. It only
|
||||||
|
* guarantees that the bytes currently available in [InputStream] are read and no I/O blocks happen.
|
||||||
|
*/
|
||||||
|
internal fun InputStream.readBytesNonBlocking(): ByteArray {
|
||||||
|
val result = ByteArrayOutputStream()
|
||||||
|
val buffer = ByteArray(128)
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
val availableBytes = available()
|
||||||
|
if (availableBytes == 0) break
|
||||||
|
|
||||||
|
val readBytes = read(buffer)
|
||||||
|
if (readBytes == 0) break
|
||||||
|
|
||||||
|
result.write(buffer, 0, readBytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
return result.toByteArray()
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun Process.readOutput(nonBlocking: Boolean): ProcessOutput {
|
||||||
|
val stdOut = if (nonBlocking) inputStream.readBytesNonBlocking() else inputStream.readBytes()
|
||||||
|
val stdErr = if (nonBlocking) errorStream.readBytesNonBlocking() else errorStream.readBytes()
|
||||||
|
|
||||||
|
return ProcessOutput(
|
||||||
|
stdOut = stdOut.toString(Charsets.UTF_8),
|
||||||
|
stdErr = stdErr.toString(Charsets.UTF_8)
|
||||||
|
)
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user