diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/BuildStatisticsWithKtorIT.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/BuildStatisticsWithKtorIT.kt index e02afde17b8..84fafd2714c 100644 --- a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/BuildStatisticsWithKtorIT.kt +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/BuildStatisticsWithKtorIT.kt @@ -6,6 +6,7 @@ package org.jetbrains.kotlin.gradle import com.google.gson.Gson +import com.google.gson.JsonParser import io.ktor.application.* import io.ktor.http.* import io.ktor.request.* @@ -15,28 +16,25 @@ import io.ktor.server.engine.* import io.ktor.server.netty.* import io.ktor.util.collections.* import org.gradle.util.GradleVersion -import org.jetbrains.kotlin.gradle.plugin.stat.CompileStatisticsData -import org.jetbrains.kotlin.gradle.plugin.stat.StatTag +import org.jetbrains.kotlin.gradle.plugin.stat.* import org.jetbrains.kotlin.gradle.report.BuildReportType import org.jetbrains.kotlin.gradle.testbase.* +import org.jetbrains.kotlin.gradle.utils.`is` import org.jetbrains.kotlin.test.util.joinToArrayString import org.junit.jupiter.api.AfterAll import org.junit.jupiter.api.BeforeAll import org.junit.jupiter.api.DisplayName import java.io.IOException -import java.net.HttpURLConnection import java.net.ServerSocket -import java.net.URL import java.util.* import kotlin.io.path.appendText -import kotlin.test.fail @DisplayName("Build statistics") @JvmGradlePluginTests class BuildStatisticsWithKtorIT : KGPBaseTest() { companion object { - fun CompileStatisticsData.validateMandatoryField(kotlinVersion: String, validationData: ValidationData): List { + fun CompileStatisticsData.validateMandatoryField(kotlinVersion: String, validationData: TaskExecutionValidationData): List { val validationErrors = LinkedList() if (taskResult != validationData.taskResult) { validationErrors.add("Unexpected taskResult: $taskResult instead of ${validationData.taskResult}") @@ -58,12 +56,10 @@ class BuildStatisticsWithKtorIT : KGPBaseTest() { validationErrors.add("Does not contains \'$tag\' tag") } } - - return validationErrors } - fun CompileStatisticsData.validateIncrementalData(validationData: ValidationData): List { + fun CompileStatisticsData.validateIncrementalData(validationData: TaskExecutionValidationData): List { if (validationData.nonIncrementalReasons.isNotEmpty()) return emptyList() val validationErrors = LinkedList() if (changes.size != validationData.changedFiles.size) { @@ -75,7 +71,7 @@ class BuildStatisticsWithKtorIT : KGPBaseTest() { return validationErrors } - fun CompileStatisticsData.validateNonIncrementalData(validationData: ValidationData): List { + fun CompileStatisticsData.validateNonIncrementalData(validationData: TaskExecutionValidationData): List { if (validationData.nonIncrementalReasons.isEmpty()) return emptyList() val validationErrors = LinkedList() if (!tags.contains(StatTag.NON_INCREMENTAL.name)) { @@ -84,6 +80,15 @@ class BuildStatisticsWithKtorIT : KGPBaseTest() { return validationErrors } + fun BuildFinishStatisticsData.validate(validationData: BuildExecutionValidationData): List { + if (validationData.tasks.isEmpty()) return emptyList() + val validationErrors = LinkedList() + if (!startParameters.tasks.containsAll(validationData.tasks.toList())) { + validationErrors.add("Different set of executed tasks. Expected ${validationData.tasks.asList()} actual ${startParameters.tasks}") + } + return validationErrors + } + fun getEmptyPort(): ServerSocket { for (port in 8080..8180) { try { @@ -108,7 +113,7 @@ class BuildStatisticsWithKtorIT : KGPBaseTest() { println("start embedded server with data: ${ktorTestDataAnnotations.joinToArrayString()}") server = embeddedServer(Netty, port = port) { - val ktorServerData = ktorTestDataAnnotations.associateBy({ it.projectName }) { it.validationData.toMutableList() } + val ktorServerData = ktorTestDataAnnotations.associateBy { it.projectName } val failedResults = ConcurrentList() suspend fun responseBadRequest(call: ApplicationCall, message: String) { @@ -117,6 +122,62 @@ class BuildStatisticsWithKtorIT : KGPBaseTest() { call.respond(status = HttpStatusCode.BadRequest, message) } + suspend fun validateBuildExecutionData( + statisticsData: BuildFinishStatisticsData, + projectValidation: KtorTestData, + call: ApplicationCall + ) { + statisticsData.validate(projectValidation.buildValidationData).let { + if (it.isNotEmpty()) { + responseBadRequest( + call, + "${projectValidation.projectName}: Fail to validate statistics after build executed: ${it.joinToArrayString()}" + ) + return + } + call.respond(HttpStatusCode.OK) + } + } + + suspend fun validateTaskExecutionData(statData: CompileStatisticsData, projectValidation: KtorTestData, call: ApplicationCall) { + val ktorTestData = projectValidation.validationData.firstOrNull { it.taskName == statData.taskName } + + if (ktorTestData == null) { + responseBadRequest(call, "${projectValidation.projectName}: Unknown validation for task ${statData.taskName}") + return + } + + //validate response + statData.validateMandatoryField(defaultBuildOptions.kotlinVersion, ktorTestData).let { + if (it.isNotEmpty()) { + responseBadRequest( + call, + "${projectValidation.projectName}: Fail to validate mandatory fields: ${it.joinToArrayString()}" + ) + return + } + } + statData.validateIncrementalData(ktorTestData).let { + if (it.isNotEmpty()) { + responseBadRequest( + call, + "${projectValidation.projectName}: Fail to validate incremental fields: ${it.joinToArrayString()}" + ) + return + } + } + statData.validateNonIncrementalData(ktorTestData).let { + if (it.isNotEmpty()) { + responseBadRequest( + call, + "${projectValidation.projectName}: Fail to validate non-incremental fields: ${it.joinToArrayString()}" + ) + return + } + } + call.respond(HttpStatusCode.OK) + } + routing { post("/badRequest") { call.respond(HttpStatusCode.BadRequest, "Some reason") @@ -125,49 +186,29 @@ class BuildStatisticsWithKtorIT : KGPBaseTest() { val body = call.receive() println("TRACE: routing was called: $body") - val statData = Gson().fromJson(body, CompileStatisticsData::class.java) - val projectValidation = ktorServerData[statData.projectName] + val jsonObject = JsonParser.parseString(body).asJsonObject + val projectName = jsonObject["projectName"].asString + + val projectValidation = ktorServerData[projectName] if (projectValidation == null) { - responseBadRequest(call, "Unknown validation for project ${statData.projectName}") + responseBadRequest(call, "Unknown validation for project $projectName") return@post } - val ktorTestData = projectValidation.firstOrNull { it.taskName == statData.taskName } + val type = jsonObject["type"].asString - if (ktorTestData == null) { - responseBadRequest(call, "${statData.projectName}: Unknown validation for task ${statData.taskName}") - return@post + when (BuildDataType.valueOf(type)) { + BuildDataType.TASK_DATA -> validateTaskExecutionData( + Gson().fromJson(body, CompileStatisticsData::class.java), + projectValidation, + call + ) + BuildDataType.BUILD_DATA -> validateBuildExecutionData( + Gson().fromJson(body, BuildFinishStatisticsData::class.java), + projectValidation, + call + ) } - - //validate response - statData.validateMandatoryField(defaultBuildOptions.kotlinVersion, ktorTestData).let { - if (it.isNotEmpty()) { - responseBadRequest( - call, - "${statData.projectName}: Fail to validate mandatory fields: ${it.joinToArrayString()}" - ) - return@post - } - } - statData.validateIncrementalData(ktorTestData).let { - if (it.isNotEmpty()) { - responseBadRequest( - call, - "${statData.projectName}: Fail to validate incremental fields: ${it.joinToArrayString()}" - ) - return@post - } - } - statData.validateNonIncrementalData(ktorTestData).let { - if (it.isNotEmpty()) { - responseBadRequest( - call, - "${statData.projectName}: Fail to validate non-incremental fields: ${it.joinToArrayString()}" - ) - return@post - } - } - call.respond(HttpStatusCode.OK) } get("/results") { if (failedResults.isEmpty()) { @@ -202,9 +243,10 @@ class BuildStatisticsWithKtorIT : KGPBaseTest() { "validateMandatoryField", [ //first run - ValidationData(":lib:compileKotlin", expectedTags = ["NON_INCREMENTAL"], nonIncrementalReasons = ["UNKNOWN_CHANGES_IN_GRADLE_INPUTS"]), - ValidationData(":app:compileKotlin", expectedTags = ["NON_INCREMENTAL"], nonIncrementalReasons = ["UNKNOWN_CHANGES_IN_GRADLE_INPUTS"]) - ] + TaskExecutionValidationData(":lib:compileKotlin", expectedTags = ["NON_INCREMENTAL"], nonIncrementalReasons = ["UNKNOWN_CHANGES_IN_GRADLE_INPUTS"]), + TaskExecutionValidationData(":app:compileKotlin", expectedTags = ["NON_INCREMENTAL"], nonIncrementalReasons = ["UNKNOWN_CHANGES_IN_GRADLE_INPUTS"]) + ], + BuildExecutionValidationData(["assemble"]) ) @DisplayName("Validate mandatory field for http request body") @GradleTest @@ -222,12 +264,13 @@ class BuildStatisticsWithKtorIT : KGPBaseTest() { "validateConfigurationCache", [ //first run - ValidationData(":lib:compileKotlin", expectedTags = ["NON_INCREMENTAL", "CONFIGURATION_CACHE"], nonIncrementalReasons = ["UNKNOWN_CHANGES_IN_GRADLE_INPUTS"]), - ValidationData(":app:compileKotlin", expectedTags = ["NON_INCREMENTAL", "CONFIGURATION_CACHE"], nonIncrementalReasons = ["UNKNOWN_CHANGES_IN_GRADLE_INPUTS"]), + TaskExecutionValidationData(":lib:compileKotlin", expectedTags = ["NON_INCREMENTAL", "CONFIGURATION_CACHE"], nonIncrementalReasons = ["UNKNOWN_CHANGES_IN_GRADLE_INPUTS"]), + TaskExecutionValidationData(":app:compileKotlin", expectedTags = ["NON_INCREMENTAL", "CONFIGURATION_CACHE"], nonIncrementalReasons = ["UNKNOWN_CHANGES_IN_GRADLE_INPUTS"]), //second run - ValidationData(":lib:compileKotlin", expectedTags = ["INCREMENTAL", "CONFIGURATION_CACHE"]), - ValidationData(":app:compileKotlin", expectedTags = ["INCREMENTAL", "CONFIGURATION_CACHE"]), - ] + TaskExecutionValidationData(":lib:compileKotlin", expectedTags = ["INCREMENTAL", "CONFIGURATION_CACHE"]), + TaskExecutionValidationData(":app:compileKotlin", expectedTags = ["INCREMENTAL", "CONFIGURATION_CACHE"]), + ], + BuildExecutionValidationData(["assemble"]) ) @DisplayName("Validate configuration cache tag") @GradleTest @@ -256,10 +299,11 @@ class BuildStatisticsWithKtorIT : KGPBaseTest() { @Retention(AnnotationRetention.RUNTIME) annotation class KtorTestData( val projectName: String, - val validationData: Array, + val validationData: Array, + val buildValidationData: BuildExecutionValidationData = BuildExecutionValidationData() ) -annotation class ValidationData( +annotation class TaskExecutionValidationData( //mandatory fields val taskName: String, val taskResult: String = "SUCCESS", @@ -272,5 +316,12 @@ annotation class ValidationData( val changedFiles: Array = [], ) +annotation class BuildExecutionValidationData( + val tasks: Array = [], + val excludedTasks: Array = [], + val projectProperties: Array = [], + val systemProperties: Array = [], +) + diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/statistics/CompileStatisticsData.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/statistics/CompileStatisticsData.kt index 54d65f5e21c..54d7fcc3afd 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/statistics/CompileStatisticsData.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/statistics/CompileStatisticsData.kt @@ -30,7 +30,8 @@ data class CompileStatisticsData( val nonIncrementalAttributes: Set, //TODO think about it,time in milliseconds val buildTimesMetrics: Map, - val performanceMetrics: Map + val performanceMetrics: Map, + val type: String = BuildDataType.TASK_DATA.name ) { companion object { private val formatter = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss").also { it.timeZone = TimeZone.getTimeZone("UTC")} @@ -48,6 +49,11 @@ enum class StatTag { BUILD_CACHE, } +enum class BuildDataType { + TASK_DATA, + BUILD_DATA +} + //Sensitive data. This object is used directly for statistic via http data class GradleBuildStartParameters( val tasks: List, @@ -58,11 +64,13 @@ data class GradleBuildStartParameters( ) : java.io.Serializable //Sensitive data. This object is used directly for statistic via http -data class BuildFinishData( +data class BuildFinishStatisticsData( + val projectName: String, val startParameters: GradleBuildStartParameters, val buildUuid: String = "Unset", val label: String?, val totalTime: Long, + val type: String = BuildDataType.BUILD_DATA.name ) diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/report/BuildMetricsReporterService.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/report/BuildMetricsReporterService.kt index 85198138a9a..cc95e1a4714 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/report/BuildMetricsReporterService.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/report/BuildMetricsReporterService.kt @@ -115,7 +115,7 @@ abstract class BuildMetricsReporterService : BuildService "$key: $value"}, diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/report/HttpReportService.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/report/HttpReportService.kt index a8765125a1d..09f28c836e9 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/report/HttpReportService.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/report/HttpReportService.kt @@ -15,9 +15,8 @@ import org.gradle.api.services.BuildServiceParameters import org.gradle.tooling.events.FinishEvent import org.gradle.tooling.events.OperationCompletionListener import org.gradle.tooling.events.task.TaskFinishEvent -import org.jetbrains.kotlin.gradle.plugin.stat.BuildFinishData +import org.jetbrains.kotlin.gradle.plugin.stat.BuildFinishStatisticsData import org.jetbrains.kotlin.gradle.plugin.BuildEventsListenerRegistryHolder -import org.jetbrains.kotlin.gradle.plugin.stat.CompileStatisticsData import org.jetbrains.kotlin.gradle.plugin.stat.GradleBuildStartParameters import org.jetbrains.kotlin.gradle.plugin.stat.StatTag import org.jetbrains.kotlin.gradle.plugin.statistics.BuildScanStatisticsListener @@ -159,6 +158,7 @@ abstract class HttpReportService : BuildService, checkResponseAndLog(connection) connection.inputStream.use { it.reader().use { reader -> reader.readText() } } } catch (e: Exception) { + log.debug("Unexpected exception happened ${e.message}: ${e.stackTrace}") checkResponseAndLog(connection) } finally { connection.disconnect() @@ -168,7 +168,8 @@ abstract class HttpReportService : BuildService, } private fun reportBuildFinish() { - val buildFinishData = BuildFinishData( + val buildFinishData = BuildFinishStatisticsData( + projectName = parameters.projectName, startParameters = parameters.startParameters, buildUuid = buildUuid, label = parameters.label,