[Native][tests] Filter test output to suppress TEAMCITY-related messages

This commit is contained in:
Dmitriy Dolovov
2021-12-03 00:06:10 +03:00
parent 09dbd43b97
commit bb01c4cbbb
7 changed files with 67 additions and 45 deletions
@@ -203,11 +203,13 @@ internal abstract class LoggedData {
appendDuration(runResult.duration)
appendLine()
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()
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 ==========")
return this
}
@@ -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.runner.AbstractRunner.AbstractRun
import org.jetbrains.kotlin.konan.blackboxtest.support.util.TestOutputFilter
import org.jetbrains.kotlin.konan.blackboxtest.support.util.readOutput
import kotlin.time.*
@@ -14,6 +15,7 @@ internal abstract class AbstractLocalProcessRunner<R>(private val executionTimeo
protected abstract val visibleProcessName: String
protected abstract val executable: TestExecutable
protected abstract val programArgs: List<String>
protected abstract val outputFilter: TestOutputFilter
protected open fun customizeProcess(process: Process) = Unit
@@ -33,7 +35,7 @@ internal abstract class AbstractLocalProcessRunner<R>(private val executionTimeo
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)
val output = process.readOutput(outputFilter, nonBlocking = !hasFinishedOnTime)
if (hasFinishedOnTime) {
val exitCode: Int = process.exitValue()
@@ -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.TestExecutable
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.test.services.JUnit5Assertions
import kotlin.time.Duration
@@ -18,6 +19,7 @@ internal class LocalTestFunctionExtractor(
) : AbstractLocalProcessRunner<Collection<TestFunction>>(executionTimeout) {
override val visibleProcessName get() = "Test function extractor"
override val programArgs = listOf(executable.executableFile.path, "--ktest_list_tests")
override val outputFilter get() = TestOutputFilter.NO_FILTERING
override fun getLoggedParameters() = LoggedData.TestRunParameters(
compilerCall = executable.loggedCompilerCall,
@@ -37,6 +39,6 @@ internal class LocalTestFunctionExtractor(
runResult: RunResult.Completed
) : AbstractLocalProcessRunner<Collection<TestFunction>>.ResultHandler(runResult) {
override fun getLoggedRun() = LoggedData.TestRun(getLoggedParameters(), runResult)
override fun doHandle() = parseGTestListing(runResult.output.stdOut)
override fun doHandle() = parseGTestListing(runResult.processOutput.stdOut.filteredOutput)
}
}
@@ -7,8 +7,10 @@ package org.jetbrains.kotlin.konan.blackboxtest.support.runner
import com.intellij.openapi.util.text.StringUtilRt.convertLineSeparators
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.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.junit.jupiter.api.Assumptions.assumeFalse
import kotlin.time.Duration
@@ -25,6 +27,9 @@ internal class LocalTestRunner(
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(
compilerCall = executable.loggedCompilerCall,
testCaseId = testRun.testCaseId,
@@ -50,15 +55,14 @@ internal class LocalTestRunner(
override fun getLoggedRun() = LoggedData.TestRun(getLoggedParameters(), runResult)
override fun doHandle() {
if (testRun.runParameters.has<TestRunParameter.WithTCTestLogger>()) {
verifyTCTestLoggerOutput()
} else {
verifyPlainOutput()
}
verifyTestReport(runResult.processOutput.stdOut.testReport)
val mergedOutput = runResult.processOutput.stdOut.filteredOutput + runResult.processOutput.stdErr
verifyNonTestOutput(mergedOutput)
}
private fun verifyTCTestLoggerOutput() {
val testReport = parseTCTestReport(runResult.output.stdOut)
private fun verifyTestReport(testReport: TestReport?) {
if (testReport == null) return
verifyExpectation(!testReport.isEmpty()) { "No tests have been found." }
@@ -77,22 +81,18 @@ internal class LocalTestRunner(
verifyNoSuchTests(testReport.failedTests, "There are failed tests")
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()) {
buildString {
append(subject).appendLine(":")
tests.forEach { append(" - ").appendLine(it) }
append(subject).append(':')
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> {
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."
}
}
@@ -5,25 +5,26 @@
package org.jetbrains.kotlin.konan.blackboxtest.support.runner
import org.jetbrains.kotlin.konan.blackboxtest.support.util.TestOutputFilter
import kotlin.time.Duration
internal sealed interface RunResult {
val exitCode: Int?
val duration: Duration
val output: ProcessOutput
val processOutput: ProcessOutput
data class Completed(
override val exitCode: Int,
override val duration: Duration,
override val output: ProcessOutput
override val processOutput: ProcessOutput
) : RunResult
data class TimeoutExceeded(
val timeout: Duration,
override val exitCode: Int?,
override val duration: Duration,
override val output: ProcessOutput
override val processOutput: ProcessOutput
) : RunResult
}
internal class ProcessOutput(val stdOut: String, val stdErr: String)
internal class ProcessOutput(val stdOut: TestOutputFilter.FilteredOutput, val stdErr: String)
@@ -32,12 +32,12 @@ internal fun InputStream.readBytesNonBlocking(): ByteArray {
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 stdErr = if (nonBlocking) errorStream.readBytesNonBlocking() else errorStream.readBytes()
return ProcessOutput(
stdOut = stdOut.toString(Charsets.UTF_8),
stdOut = outputFilter.filter(stdOut.toString(Charsets.UTF_8)),
stdErr = stdErr.toString(Charsets.UTF_8)
)
}
@@ -18,14 +18,25 @@ internal typealias TestName = String
internal class TestReport(
val passedTests: Collection<TestName>,
val failedTests: Collection<TestName>,
val ignoredTests: Collection<TestName>,
val nonTestOutput: String
val ignoredTests: Collection<TestName>
) {
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[testStarted name='one' locationHint='ktest:test://sample.test.SampleTestKt.one']
@@ -33,24 +44,30 @@ internal class TestReport(
* ##teamcity[testIgnored name='two']
* ##teamcity[testSuiteFinished name='sample.test.SampleTestKt']
*/
internal fun parseTCTestReport(rawTCTestOutput: String): TestReport {
val callback = TCTestMessageParserCallback()
ServiceMessagesParser().parse(rawTCTestOutput, callback)
internal object TCTestOutputFilter : TestOutputFilter {
override fun filter(testOutput: String): TestOutputFilter.FilteredOutput {
val callback = TCTestMessageParserCallback()
ServiceMessagesParser().parse(testOutput, callback)
callback.finish()
assertTrue(callback.errors.isEmpty()) {
buildString {
appendLine("Failed to parse TC test output.")
callback.errors.forEach { error ->
assertTrue(callback.errors.isEmpty()) {
buildString {
appendLine("Failed to parse TC test output.")
callback.errors.forEach { error ->
appendLine()
append("Error: ").appendLine(error)
}
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 {
@@ -143,11 +160,9 @@ private class TCTestMessageParserCallback : ServiceMessageParserCallback {
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).
state.safeAs<State.TestStarted>()?.let { failedTests += it.testName }
return TestReport(passedTests, failedTests, ignoredTests, nonTestOutput.toString())
}
}