diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/BuildStatisticsIT.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/BuildStatisticsIT.kt new file mode 100644 index 00000000000..7a78818f870 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/BuildStatisticsIT.kt @@ -0,0 +1,29 @@ +/* + * Copyright 2010-2022 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.gradle + +import org.gradle.api.logging.LogLevel +import org.gradle.util.GradleVersion +import org.jetbrains.kotlin.gradle.report.BuildReportType +import org.jetbrains.kotlin.gradle.testbase.* +import org.junit.jupiter.api.DisplayName + +@DisplayName("Build statistics") +@SimpleGradlePluginTests +class BuildStatisticsIT : KGPBaseTest() { + + @DisplayName("Http build report url problems are logged only ones") + @GradleTest + fun testHttpServiceWithInvalidUrl(gradleVersion: GradleVersion) { + project("incrementalMultiproject", gradleVersion) { + enableStatisticReports(BuildReportType.HTTP, "invalid/url") + build("assemble") { + assertOutputContainsExactTimes("Unable to open connection to") + } + } + } + +} \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/testbase/outputAssertions.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/testbase/outputAssertions.kt index b5911124605..6abbabeb6d1 100644 --- a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/testbase/outputAssertions.kt +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/testbase/outputAssertions.kt @@ -19,6 +19,30 @@ fun BuildResult.assertOutputContains( } } +/** + * Asserts Gradle output contains [expectedSubString] string exact times. + */ +fun BuildResult.assertOutputContainsExactTimes( + expectedSubString: String, + expectedRepetitionTimes: Int = 1 +) { + var currentOffset = 0 + var count = 0 + var nextIndex = output.indexOf(expectedSubString, currentOffset) + + while (nextIndex != -1 && count < expectedRepetitionTimes + 1) { + count++ + currentOffset = nextIndex + expectedSubString.length + nextIndex = output.indexOf(expectedSubString, currentOffset) + } + + assert(count == expectedRepetitionTimes) { + printBuildOutput() + "Build output contains \"$expectedSubString\" $count times" + } +} + + /** * Asserts Gradle output does not contain [notExpectedSubString] string. * diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/testbase/testDsl.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/testbase/testDsl.kt index ff32c3d57fe..25757a2cd97 100644 --- a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/testbase/testDsl.kt +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/testbase/testDsl.kt @@ -12,6 +12,7 @@ import org.gradle.util.GradleVersion import org.jetbrains.kotlin.gradle.BaseGradleIT.Companion.acceptAndroidSdkLicenses import org.jetbrains.kotlin.gradle.model.ModelContainer import org.jetbrains.kotlin.gradle.model.ModelFetcherBuildAction +import org.jetbrains.kotlin.gradle.report.BuildReportType import org.jetbrains.kotlin.test.util.KtTestUtil import java.io.File import java.nio.file.* @@ -179,6 +180,21 @@ fun TestProject.enableLocalBuildCache( ) } +fun TestProject.enableStatisticReports( + type: BuildReportType, + url: String? +) { + gradleProperties.append( + "\nkotlin.build.report.output=${type.name}\n" + ) + + url?.also { + gradleProperties.append( + "\nkotlin.build.report.http.url=$url\n" + ) + } +} + open class GradleProject( val projectName: String, val projectPath: Path diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/report/HttpReportService.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/report/HttpReportService.kt index d408018d711..a7928a84833 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/report/HttpReportService.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/report/HttpReportService.kt @@ -16,6 +16,7 @@ import org.gradle.tooling.events.OperationCompletionListener import org.gradle.tooling.events.task.TaskFinishEvent import org.jetbrains.kotlin.gradle.plugin.stat.CompileStatisticsData import org.jetbrains.kotlin.gradle.plugin.statistics.KotlinBuildStatListener.Companion.prepareData +import java.io.IOException import java.net.HttpURLConnection import java.net.URL import java.util.* @@ -38,6 +39,10 @@ abstract class HttpReportService : BuildService, private val log = Logging.getLogger(this.javaClass) + // @Volatile for one thread executor it does not need + private var requestPreviousFailed = false + private var invalidUrl = false + override fun onFinish(event: FinishEvent?) { if (event is TaskFinishEvent) { val data = prepareData(event, parameters.projectName, parameters.uuid, parameters.label, parameters.kotlinVersion) @@ -74,12 +79,22 @@ abstract class HttpReportService : BuildService, fun report(data: CompileStatisticsData) { val elapsedTime = measureTimeMillis { - val connection = URL(parameters.httpSettings.url).openConnection() as HttpURLConnection + if (invalidUrl) { + return + } + val connection = try { + URL(parameters.httpSettings.url).openConnection() as HttpURLConnection + } catch (e: IOException) { + log.warn("Unable to open connection to ${parameters.httpSettings.url}: ${e.message}") + invalidUrl = true + return + } try { if (parameters.httpSettings.user != null && parameters.httpSettings.password != null) { val auth = Base64.getEncoder() - .encode("${parameters.httpSettings.user}:${parameters.httpSettings.password}".toByteArray()).toString(Charsets.UTF_8) + .encode("${parameters.httpSettings.user}:${parameters.httpSettings.password}".toByteArray()) + .toString(Charsets.UTF_8) connection.addRequestProperty("Authorization", "Basic $auth") } connection.addRequestProperty("Content-Type", "application/json") @@ -103,7 +118,13 @@ abstract class HttpReportService : BuildService, private fun checkResponseAndLog(connection: HttpURLConnection) { val isResponseBad = connection.responseCode !in 200..299 if (isResponseBad) { - log.warn("Failed to send statistic to ${connection.url}: ${connection.responseMessage}") + val message = "Failed to send statistic to ${connection.url} with ${connection.responseCode}: ${connection.responseMessage}" + if (!requestPreviousFailed) { + log.warn(message) + } else { + log.debug(message) + } + requestPreviousFailed = true } }