[Native][tests] Filter test output to suppress TEAMCITY-related messages
This commit is contained in:
+4
-2
@@ -203,11 +203,13 @@ internal abstract class LoggedData {
|
|||||||
appendDuration(runResult.duration)
|
appendDuration(runResult.duration)
|
||||||
appendLine()
|
appendLine()
|
||||||
appendLine("========== BEGIN: STDOUT ==========")
|
appendLine("========== BEGIN: STDOUT ==========")
|
||||||
if (runResult.output.stdOut.isNotEmpty()) appendLine(runResult.output.stdOut.trimEnd())
|
val stdOut = runResult.processOutput.stdOut.filteredOutput
|
||||||
|
if (stdOut.isNotEmpty()) appendLine(stdOut.trimEnd())
|
||||||
appendLine("========== END: STDOUT ==========")
|
appendLine("========== END: STDOUT ==========")
|
||||||
appendLine()
|
appendLine()
|
||||||
appendLine("========== BEGIN: STDERR ==========")
|
appendLine("========== BEGIN: STDERR ==========")
|
||||||
if (runResult.output.stdErr.isNotEmpty()) appendLine(runResult.output.stdErr.trimEnd())
|
val stdErr = runResult.processOutput.stdErr
|
||||||
|
if (stdErr.isNotEmpty()) appendLine(stdErr.trimEnd())
|
||||||
appendLine("========== END: STDERR ==========")
|
appendLine("========== END: STDERR ==========")
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-1
@@ -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.TestOutputFilter
|
||||||
import org.jetbrains.kotlin.konan.blackboxtest.support.util.readOutput
|
import org.jetbrains.kotlin.konan.blackboxtest.support.util.readOutput
|
||||||
import kotlin.time.*
|
import kotlin.time.*
|
||||||
|
|
||||||
@@ -14,6 +15,7 @@ internal abstract class AbstractLocalProcessRunner<R>(private val executionTimeo
|
|||||||
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 abstract val outputFilter: TestOutputFilter
|
||||||
|
|
||||||
protected open fun customizeProcess(process: Process) = Unit
|
protected open fun customizeProcess(process: Process) = Unit
|
||||||
|
|
||||||
@@ -33,7 +35,7 @@ internal abstract class AbstractLocalProcessRunner<R>(private val executionTimeo
|
|||||||
val (process, hasFinishedOnTime) = result
|
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.
|
// 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)
|
val output = process.readOutput(outputFilter, nonBlocking = !hasFinishedOnTime)
|
||||||
|
|
||||||
if (hasFinishedOnTime) {
|
if (hasFinishedOnTime) {
|
||||||
val exitCode: Int = process.exitValue()
|
val exitCode: Int = process.exitValue()
|
||||||
|
|||||||
+3
-1
@@ -8,6 +8,7 @@ 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.konan.blackboxtest.support.TestExecutable
|
import org.jetbrains.kotlin.konan.blackboxtest.support.TestExecutable
|
||||||
import org.jetbrains.kotlin.konan.blackboxtest.support.TestFunction
|
import org.jetbrains.kotlin.konan.blackboxtest.support.TestFunction
|
||||||
|
import org.jetbrains.kotlin.konan.blackboxtest.support.util.TestOutputFilter
|
||||||
import org.jetbrains.kotlin.konan.blackboxtest.support.util.parseGTestListing
|
import org.jetbrains.kotlin.konan.blackboxtest.support.util.parseGTestListing
|
||||||
import org.jetbrains.kotlin.test.services.JUnit5Assertions
|
import org.jetbrains.kotlin.test.services.JUnit5Assertions
|
||||||
import kotlin.time.Duration
|
import kotlin.time.Duration
|
||||||
@@ -18,6 +19,7 @@ internal class LocalTestFunctionExtractor(
|
|||||||
) : AbstractLocalProcessRunner<Collection<TestFunction>>(executionTimeout) {
|
) : AbstractLocalProcessRunner<Collection<TestFunction>>(executionTimeout) {
|
||||||
override val visibleProcessName get() = "Test function extractor"
|
override val visibleProcessName get() = "Test function 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 fun getLoggedParameters() = LoggedData.TestRunParameters(
|
override fun getLoggedParameters() = LoggedData.TestRunParameters(
|
||||||
compilerCall = executable.loggedCompilerCall,
|
compilerCall = executable.loggedCompilerCall,
|
||||||
@@ -37,6 +39,6 @@ internal class LocalTestFunctionExtractor(
|
|||||||
runResult: RunResult.Completed
|
runResult: RunResult.Completed
|
||||||
) : AbstractLocalProcessRunner<Collection<TestFunction>>.ResultHandler(runResult) {
|
) : AbstractLocalProcessRunner<Collection<TestFunction>>.ResultHandler(runResult) {
|
||||||
override fun getLoggedRun() = LoggedData.TestRun(getLoggedParameters(), runResult)
|
override fun getLoggedRun() = LoggedData.TestRun(getLoggedParameters(), runResult)
|
||||||
override fun doHandle() = parseGTestListing(runResult.output.stdOut)
|
override fun doHandle() = parseGTestListing(runResult.processOutput.stdOut.filteredOutput)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+16
-16
@@ -7,8 +7,10 @@ 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.konan.blackboxtest.support.util.TCTestOutputFilter
|
||||||
import org.jetbrains.kotlin.konan.blackboxtest.support.util.TestName
|
import org.jetbrains.kotlin.konan.blackboxtest.support.util.TestName
|
||||||
import org.jetbrains.kotlin.konan.blackboxtest.support.util.parseTCTestReport
|
import org.jetbrains.kotlin.konan.blackboxtest.support.util.TestOutputFilter
|
||||||
|
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
|
import kotlin.time.Duration
|
||||||
@@ -25,6 +27,9 @@ internal class LocalTestRunner(
|
|||||||
testRun.runParameters.forEach { it.applyTo(this) }
|
testRun.runParameters.forEach { it.applyTo(this) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override val outputFilter: TestOutputFilter
|
||||||
|
get() = if (testRun.runParameters.has<TestRunParameter.WithTCTestLogger>()) TCTestOutputFilter else TestOutputFilter.NO_FILTERING
|
||||||
|
|
||||||
override fun getLoggedParameters() = LoggedData.TestRunParameters(
|
override fun getLoggedParameters() = LoggedData.TestRunParameters(
|
||||||
compilerCall = executable.loggedCompilerCall,
|
compilerCall = executable.loggedCompilerCall,
|
||||||
testCaseId = testRun.testCaseId,
|
testCaseId = testRun.testCaseId,
|
||||||
@@ -50,15 +55,14 @@ internal class LocalTestRunner(
|
|||||||
override fun getLoggedRun() = LoggedData.TestRun(getLoggedParameters(), runResult)
|
override fun getLoggedRun() = LoggedData.TestRun(getLoggedParameters(), runResult)
|
||||||
|
|
||||||
override fun doHandle() {
|
override fun doHandle() {
|
||||||
if (testRun.runParameters.has<TestRunParameter.WithTCTestLogger>()) {
|
verifyTestReport(runResult.processOutput.stdOut.testReport)
|
||||||
verifyTCTestLoggerOutput()
|
|
||||||
} else {
|
val mergedOutput = runResult.processOutput.stdOut.filteredOutput + runResult.processOutput.stdErr
|
||||||
verifyPlainOutput()
|
verifyNonTestOutput(mergedOutput)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun verifyTCTestLoggerOutput() {
|
private fun verifyTestReport(testReport: TestReport?) {
|
||||||
val testReport = parseTCTestReport(runResult.output.stdOut)
|
if (testReport == null) return
|
||||||
|
|
||||||
verifyExpectation(!testReport.isEmpty()) { "No tests have been found." }
|
verifyExpectation(!testReport.isEmpty()) { "No tests have been found." }
|
||||||
|
|
||||||
@@ -77,22 +81,18 @@ internal class LocalTestRunner(
|
|||||||
verifyNoSuchTests(testReport.failedTests, "There are failed tests")
|
verifyNoSuchTests(testReport.failedTests, "There are failed tests")
|
||||||
|
|
||||||
assumeFalse(testReport.ignoredTests.isNotEmpty() && testReport.passedTests.isEmpty(), "Test case is disabled")
|
assumeFalse(testReport.ignoredTests.isNotEmpty() && testReport.passedTests.isEmpty(), "Test case is disabled")
|
||||||
|
|
||||||
verifyNonTestOutput { testReport.nonTestOutput + runResult.output.stdErr }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun verifyNoSuchTests(tests: Collection<TestName>, subject: String) = verifyExpectation(tests.isEmpty()) {
|
private fun verifyNoSuchTests(tests: Collection<TestName>, subject: String) = verifyExpectation(tests.isEmpty()) {
|
||||||
buildString {
|
buildString {
|
||||||
append(subject).appendLine(":")
|
append(subject).append(':')
|
||||||
tests.forEach { append(" - ").appendLine(it) }
|
tests.forEach { appendLine().append(" - ").append(it) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun verifyPlainOutput() = verifyNonTestOutput { runResult.output.stdOut + runResult.output.stdErr }
|
private fun verifyNonTestOutput(nonTestOutput: String) {
|
||||||
|
|
||||||
private fun verifyNonTestOutput(nonTestOutput: () -> String) {
|
|
||||||
testRun.runParameters.get<TestRunParameter.WithExpectedOutputData> {
|
testRun.runParameters.get<TestRunParameter.WithExpectedOutputData> {
|
||||||
verifyExpectation(convertLineSeparators(expectedOutputDataFile.readText()), convertLineSeparators(nonTestOutput())) {
|
verifyExpectation(convertLineSeparators(expectedOutputDataFile.readText()), convertLineSeparators(nonTestOutput)) {
|
||||||
"Tested process output mismatch. See \"TEST STDOUT\" and \"EXPECTED OUTPUT DATA FILE\" below."
|
"Tested process output mismatch. See \"TEST STDOUT\" and \"EXPECTED OUTPUT DATA FILE\" below."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+5
-4
@@ -5,25 +5,26 @@
|
|||||||
|
|
||||||
package org.jetbrains.kotlin.konan.blackboxtest.support.runner
|
package org.jetbrains.kotlin.konan.blackboxtest.support.runner
|
||||||
|
|
||||||
|
import org.jetbrains.kotlin.konan.blackboxtest.support.util.TestOutputFilter
|
||||||
import kotlin.time.Duration
|
import kotlin.time.Duration
|
||||||
|
|
||||||
internal sealed interface RunResult {
|
internal sealed interface RunResult {
|
||||||
val exitCode: Int?
|
val exitCode: Int?
|
||||||
val duration: Duration
|
val duration: Duration
|
||||||
val output: ProcessOutput
|
val processOutput: ProcessOutput
|
||||||
|
|
||||||
data class Completed(
|
data class Completed(
|
||||||
override val exitCode: Int,
|
override val exitCode: Int,
|
||||||
override val duration: Duration,
|
override val duration: Duration,
|
||||||
override val output: ProcessOutput
|
override val processOutput: ProcessOutput
|
||||||
) : RunResult
|
) : RunResult
|
||||||
|
|
||||||
data class TimeoutExceeded(
|
data class TimeoutExceeded(
|
||||||
val timeout: Duration,
|
val timeout: Duration,
|
||||||
override val exitCode: Int?,
|
override val exitCode: Int?,
|
||||||
override val duration: Duration,
|
override val duration: Duration,
|
||||||
override val output: ProcessOutput
|
override val processOutput: ProcessOutput
|
||||||
) : RunResult
|
) : RunResult
|
||||||
}
|
}
|
||||||
|
|
||||||
internal class ProcessOutput(val stdOut: String, val stdErr: String)
|
internal class ProcessOutput(val stdOut: TestOutputFilter.FilteredOutput, val stdErr: String)
|
||||||
|
|||||||
+2
-2
@@ -32,12 +32,12 @@ internal fun InputStream.readBytesNonBlocking(): ByteArray {
|
|||||||
return result.toByteArray()
|
return result.toByteArray()
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun Process.readOutput(nonBlocking: Boolean): ProcessOutput {
|
internal fun Process.readOutput(outputFilter: TestOutputFilter, nonBlocking: Boolean): ProcessOutput {
|
||||||
val stdOut = if (nonBlocking) inputStream.readBytesNonBlocking() else inputStream.readBytes()
|
val stdOut = if (nonBlocking) inputStream.readBytesNonBlocking() else inputStream.readBytes()
|
||||||
val stdErr = if (nonBlocking) errorStream.readBytesNonBlocking() else errorStream.readBytes()
|
val stdErr = if (nonBlocking) errorStream.readBytesNonBlocking() else errorStream.readBytes()
|
||||||
|
|
||||||
return ProcessOutput(
|
return ProcessOutput(
|
||||||
stdOut = stdOut.toString(Charsets.UTF_8),
|
stdOut = outputFilter.filter(stdOut.toString(Charsets.UTF_8)),
|
||||||
stdErr = stdErr.toString(Charsets.UTF_8)
|
stdErr = stdErr.toString(Charsets.UTF_8)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
+34
-19
@@ -18,14 +18,25 @@ internal typealias TestName = String
|
|||||||
internal class TestReport(
|
internal class TestReport(
|
||||||
val passedTests: Collection<TestName>,
|
val passedTests: Collection<TestName>,
|
||||||
val failedTests: Collection<TestName>,
|
val failedTests: Collection<TestName>,
|
||||||
val ignoredTests: Collection<TestName>,
|
val ignoredTests: Collection<TestName>
|
||||||
val nonTestOutput: String
|
|
||||||
) {
|
) {
|
||||||
fun isEmpty(): Boolean = passedTests.isEmpty() && failedTests.isEmpty() && ignoredTests.isEmpty()
|
fun isEmpty(): Boolean = passedTests.isEmpty() && failedTests.isEmpty() && ignoredTests.isEmpty()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal interface TestOutputFilter {
|
||||||
|
fun filter(testOutput: String): FilteredOutput
|
||||||
|
|
||||||
|
class FilteredOutput(val filteredOutput: String, val testReport: TestReport?)
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
val NO_FILTERING = object : TestOutputFilter {
|
||||||
|
override fun filter(testOutput: String) = FilteredOutput(testOutput, null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parses TC test reports like this:
|
* Processed TC test reports like this:
|
||||||
*
|
*
|
||||||
* ##teamcity[testSuiteStarted name='sample.test.SampleTestKt' locationHint='ktest:suite://sample.test.SampleTestKt']
|
* ##teamcity[testSuiteStarted name='sample.test.SampleTestKt' locationHint='ktest:suite://sample.test.SampleTestKt']
|
||||||
* ##teamcity[testStarted name='one' locationHint='ktest:test://sample.test.SampleTestKt.one']
|
* ##teamcity[testStarted name='one' locationHint='ktest:test://sample.test.SampleTestKt.one']
|
||||||
@@ -33,24 +44,30 @@ internal class TestReport(
|
|||||||
* ##teamcity[testIgnored name='two']
|
* ##teamcity[testIgnored name='two']
|
||||||
* ##teamcity[testSuiteFinished name='sample.test.SampleTestKt']
|
* ##teamcity[testSuiteFinished name='sample.test.SampleTestKt']
|
||||||
*/
|
*/
|
||||||
internal fun parseTCTestReport(rawTCTestOutput: String): TestReport {
|
internal object TCTestOutputFilter : TestOutputFilter {
|
||||||
val callback = TCTestMessageParserCallback()
|
override fun filter(testOutput: String): TestOutputFilter.FilteredOutput {
|
||||||
ServiceMessagesParser().parse(rawTCTestOutput, callback)
|
val callback = TCTestMessageParserCallback()
|
||||||
|
ServiceMessagesParser().parse(testOutput, callback)
|
||||||
|
callback.finish()
|
||||||
|
|
||||||
assertTrue(callback.errors.isEmpty()) {
|
assertTrue(callback.errors.isEmpty()) {
|
||||||
buildString {
|
buildString {
|
||||||
appendLine("Failed to parse TC test output.")
|
appendLine("Failed to parse TC test output.")
|
||||||
callback.errors.forEach { error ->
|
callback.errors.forEach { error ->
|
||||||
|
appendLine()
|
||||||
|
append("Error: ").appendLine(error)
|
||||||
|
}
|
||||||
appendLine()
|
appendLine()
|
||||||
append("Error: ").appendLine(error)
|
appendLine("Full test output:")
|
||||||
|
append(testOutput)
|
||||||
}
|
}
|
||||||
appendLine()
|
|
||||||
appendLine("Test output:")
|
|
||||||
append(rawTCTestOutput)
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return callback.getReport()
|
return TestOutputFilter.FilteredOutput(
|
||||||
|
filteredOutput = callback.nonTestOutput.toString(),
|
||||||
|
testReport = TestReport(callback.passedTests, callback.failedTests, callback.ignoredTests)
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class TCTestMessageParserCallback : ServiceMessageParserCallback {
|
private class TCTestMessageParserCallback : ServiceMessageParserCallback {
|
||||||
@@ -143,11 +160,9 @@ private class TCTestMessageParserCallback : ServiceMessageParserCallback {
|
|||||||
errors += error()
|
errors += error()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getReport(): TestReport {
|
fun finish() {
|
||||||
// The last test state is "TestStarted" this likely means that the test process terminated during test execution (SIGSEGV, etc).
|
// The last test state is "TestStarted" this likely means that the test process terminated during test execution (SIGSEGV, etc).
|
||||||
state.safeAs<State.TestStarted>()?.let { failedTests += it.testName }
|
state.safeAs<State.TestStarted>()?.let { failedTests += it.testName }
|
||||||
|
|
||||||
return TestReport(passedTests, failedTests, ignoredTests, nonTestOutput.toString())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user