[Gradle] Improve diagnostics-related test assertions

- Allow matching substring for 'assertNoDiagnostic' as well
- Better handling of cases when multiple diagnostics with the same ID
are reported (properly look for one 'withSubstring' if provided)
This commit is contained in:
Dmitry Savvinov
2023-05-03 16:57:21 +01:00
parent 1ab1dccea1
commit 1a5cbe1245
2 changed files with 61 additions and 21 deletions
@@ -9,7 +9,6 @@ import org.gradle.testkit.runner.BuildResult
import org.jetbrains.kotlin.gradle.BaseGradleIT
import org.jetbrains.kotlin.gradle.internals.VERBOSE_DIAGNOSTIC_SEPARATOR
import org.jetbrains.kotlin.gradle.plugin.diagnostics.ToolingDiagnosticFactory
import kotlin.test.assertNotNull
import kotlin.test.assertNull
import kotlin.test.assertTrue
@@ -17,31 +16,54 @@ fun BaseGradleIT.CompiledProject.assertHasDiagnostic(diagnosticFactory: ToolingD
output.assertHasDiagnostic(diagnosticFactory, withSubstring)
}
fun BaseGradleIT.CompiledProject.assertNoDiagnostic(diagnosticFactory: ToolingDiagnosticFactory) {
output.assertNoDiagnostic(diagnosticFactory)
fun BaseGradleIT.CompiledProject.assertNoDiagnostic(diagnosticFactory: ToolingDiagnosticFactory, withSubstring: String? = null) {
output.assertNoDiagnostic(diagnosticFactory, withSubstring)
}
fun BuildResult.assertHasDiagnostic(diagnosticFactory: ToolingDiagnosticFactory, withSubstring: String? = null) {
output.assertHasDiagnostic(diagnosticFactory, withSubstring)
}
fun BuildResult.assertNoDiagnostic(diagnosticFactory: ToolingDiagnosticFactory, withSubstring: String? = null) {
output.assertNoDiagnostic(diagnosticFactory, withSubstring)
}
fun String.assertHasDiagnostic(diagnosticFactory: ToolingDiagnosticFactory, withSubstring: String? = null) {
val diagnosticMessage = extractVerboselyRenderedDiagnostic(diagnosticFactory, this)
assertNotNull(diagnosticMessage) { "Diagnostic with id=${diagnosticFactory.id} not found" }
val diagnosticsMessages = extractVerboselyRenderedDiagnostics(diagnosticFactory, this)
assertTrue(diagnosticsMessages.isNotEmpty(), "Diagnostic with id=${diagnosticFactory.id} not found. Full text output:\n\n" + this)
if (withSubstring != null) {
assertTrue(
withSubstring in diagnosticMessage,
diagnosticsMessages.any { withSubstring in it },
"Diagnostic ${diagnosticFactory.id} doesn't have expected substring $withSubstring. " +
"Actual diagnostic message:\n" +
diagnosticMessage
"Actual diagnostic messages with that ID:\n" +
diagnosticsMessages.joinToString(separator = "\n") +
"\nFull text output:\n\n" +
this
)
}
}
fun String.assertNoDiagnostic(diagnosticFactory: ToolingDiagnosticFactory) {
val diagnosticMessage = extractVerboselyRenderedDiagnostic(diagnosticFactory, this)
assertNull(
diagnosticMessage,
"Diagnostic with id=${diagnosticFactory.id} was expected to be absent, but was reported. " +
"Actual diagnostic message: \n" +
diagnosticMessage
)
fun String.assertNoDiagnostic(diagnosticFactory: ToolingDiagnosticFactory, withSubstring: String? = null) {
val diagnosticMessages = extractVerboselyRenderedDiagnostics(diagnosticFactory, this)
if (withSubstring != null) {
val matchedWithSubstring = diagnosticMessages.find { withSubstring in it }
assertNull(
matchedWithSubstring,
"Diagnostic with id=${diagnosticFactory.id} and substring '${withSubstring}' was expected to be absent, but was reported. " +
"Actual diagnostic message: \n" +
matchedWithSubstring +
"\nFull text output:\n\n" +
this
)
} else {
assertTrue(
diagnosticMessages.isEmpty(),
"Expected no diagnostics with id=${diagnosticFactory.id}, but some were reported:\n" +
diagnosticMessages.joinToString(separator = "\n") +
"\nFull text output:\n\n" +
this
)
}
}
/**
@@ -112,8 +134,23 @@ private val DIAGNOSTIC_START_REGEX = """\s*[we]:\s*\[[^\[]*].*""".toRegex()
Multiline
Text"
*/
private fun extractVerboselyRenderedDiagnostic(diagnostic: ToolingDiagnosticFactory, fromText: String): String? {
val diagnosticStartIndex = fromText.indexOf("[${diagnostic.id}")
private fun extractVerboselyRenderedDiagnostics(diagnostic: ToolingDiagnosticFactory, fromText: String): List<String> {
var parsedPrefix = 0
return generateSequence {
extractNextVerboselyRenderedDiagnosticAndIndex(diagnostic, fromText, startIndex = parsedPrefix)
?.also { (_, newPrefix) -> parsedPrefix = newPrefix }
?.first
}.toList()
}
// Returns diagnostic substring + index of the first symbol after the diagnostic message
private fun extractNextVerboselyRenderedDiagnosticAndIndex(
diagnostic: ToolingDiagnosticFactory,
fromText: String,
startIndex: Int,
): Pair<String, Int>? {
val diagnosticStartIndex = fromText.indexOf("[${diagnostic.id}", startIndex)
if (diagnosticStartIndex == -1) return null
val diagnosticHeaderEnd = fromText.indexOf("]", startIndex = diagnosticStartIndex)
@@ -121,7 +158,10 @@ private fun extractVerboselyRenderedDiagnostic(diagnostic: ToolingDiagnosticFact
val diagnosticSeparatorStartIndex = fromText.indexOf(VERBOSE_DIAGNOSTIC_SEPARATOR, startIndex = diagnosticStartIndex)
// NB: substring's endIndex is exclusive, which gives us exactly the message
return fromText.substring(diagnosticMessageStart, diagnosticSeparatorStartIndex).trim { it.isWhitespace() || it == '\n' }
val diagnosticMessage = fromText.substring(diagnosticMessageStart, diagnosticSeparatorStartIndex)
.trim { it.isWhitespace() || it == '\n' }
val indexOfFirstSymbolAfterSeparator = diagnosticSeparatorStartIndex + VERBOSE_DIAGNOSTIC_SEPARATOR.length
return diagnosticMessage to indexOfFirstSymbolAfterSeparator
}
private const val CONFIGURE_PROJECT_PREFIX = "> Configure project"
@@ -5,6 +5,7 @@
package org.jetbrains.kotlin.gradle.plugin.diagnostics
import org.gradle.api.InvalidUserCodeException
import org.gradle.api.logging.Logger
import org.jetbrains.kotlin.gradle.plugin.diagnostics.ToolingDiagnostic.Severity.*
@@ -24,8 +25,7 @@ internal fun renderReportedDiagnostic(
ERROR -> logger.error("e: ${diagnostic.render(isVerbose)}\n")
FATAL ->
error("Internal error: FATAL diagnostics throw an exception immediately in KotlinToolingDiagnosticsCollector")
FATAL -> throw InvalidUserCodeException(diagnostic.render(isVerbose))
}
}