diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/testbase/diagnosticsAssertions.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/testbase/diagnosticsAssertions.kt index 7037df74518..b8723c410d4 100644 --- a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/testbase/diagnosticsAssertions.kt +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/testbase/diagnosticsAssertions.kt @@ -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 { + 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? { + 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" diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/diagnostics/renderReportedDiagnostics.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/diagnostics/renderReportedDiagnostics.kt index 7ec351f70df..6220dde6b1c 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/diagnostics/renderReportedDiagnostics.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/diagnostics/renderReportedDiagnostics.kt @@ -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)) } }