From 173b102db7bd5f25c1749f5290a08913ddeb4add Mon Sep 17 00:00:00 2001 From: iryabov Date: Thu, 12 Feb 2026 10:46:08 +0100 Subject: [PATCH 1/6] feat: add impacted tests to build diff report service EPMDJ-11198 --- admin-app/src/main/resources/application.conf | 4 ++ .../config/MetricsServiceUiLinksConfig.kt | 21 +++++---- .../metrics/repository/MetricsRepository.kt | 2 +- .../repository/impl/MetricsRepositoryImpl.kt | 45 +++++++++++-------- .../service/impl/MetricsServiceImpl.kt | 30 ++++++++++--- .../admin/metrics/BuildDiffReportApiTest.kt | 27 +++++------ 6 files changed, 81 insertions(+), 48 deletions(-) diff --git a/admin-app/src/main/resources/application.conf b/admin-app/src/main/resources/application.conf index 6f45a4545..3b3d0c5d5 100644 --- a/admin-app/src/main/resources/application.conf +++ b/admin-app/src/main/resources/application.conf @@ -77,6 +77,10 @@ drill { baseUrl = ${?DRILL_METRICS_UI_BASE_URL} buildTestingReportPath = "/dashboard/2" buildTestingReportPath = ${?DRILL_METRICS_UI_BUILD_TESTING_REPORT_PATH } + buildRisksReportPath = "/dashboard/5" + buildRisksReportPath = ${?DRILL_METRICS_UI_BUILD_RISKS_REPORT_PATH } + impactedTestsReportPath = "/dashboard/13" + impactedTestsReportPath = ${?DRILL_METRICS_UI_BUILD_RISKS_REPORT_PATH } } } testRecommendations { diff --git a/admin-metrics/src/main/kotlin/com/epam/drill/admin/metrics/config/MetricsServiceUiLinksConfig.kt b/admin-metrics/src/main/kotlin/com/epam/drill/admin/metrics/config/MetricsServiceUiLinksConfig.kt index 11920cb50..37b539158 100644 --- a/admin-metrics/src/main/kotlin/com/epam/drill/admin/metrics/config/MetricsServiceUiLinksConfig.kt +++ b/admin-metrics/src/main/kotlin/com/epam/drill/admin/metrics/config/MetricsServiceUiLinksConfig.kt @@ -18,12 +18,17 @@ package com.epam.drill.admin.metrics.config import io.ktor.server.config.* -class MetricsServiceUiLinksConfig( - val baseUrl: String?, - val buildTestingReportPath: String? -) { - constructor(config: ApplicationConfig) : this( - baseUrl = config.propertyOrNull("baseUrl")?.getString(), - buildTestingReportPath = config.propertyOrNull("buildTestingReportPath")?.getString() - ) +class MetricsServiceUiLinksConfig(private val config: ApplicationConfig) { + + val baseUrl : String? + get() = config.propertyOrNull("baseUrl")?.getString() + + val buildTestingReportPath : String? + get() = config.propertyOrNull("buildTestingReportPath")?.getString() + + val buildRisksReportPath : String? + get() = config.propertyOrNull("buildRisksReportPath")?.getString() + + val impactedTestsReportPath : String? + get() = config.propertyOrNull("impactedTestsReportPath")?.getString() } \ No newline at end of file diff --git a/admin-metrics/src/main/kotlin/com/epam/drill/admin/metrics/repository/MetricsRepository.kt b/admin-metrics/src/main/kotlin/com/epam/drill/admin/metrics/repository/MetricsRepository.kt index 4cbab9626..628a0fc3b 100644 --- a/admin-metrics/src/main/kotlin/com/epam/drill/admin/metrics/repository/MetricsRepository.kt +++ b/admin-metrics/src/main/kotlin/com/epam/drill/admin/metrics/repository/MetricsRepository.kt @@ -75,7 +75,7 @@ interface MetricsRepository { suspend fun getBuildDiffReport( buildId: String, baselineBuildId: String - ): Map + ): Map suspend fun getRecommendedTests( targetBuildId: String, diff --git a/admin-metrics/src/main/kotlin/com/epam/drill/admin/metrics/repository/impl/MetricsRepositoryImpl.kt b/admin-metrics/src/main/kotlin/com/epam/drill/admin/metrics/repository/impl/MetricsRepositoryImpl.kt index 58be4aaac..b3d987055 100644 --- a/admin-metrics/src/main/kotlin/com/epam/drill/admin/metrics/repository/impl/MetricsRepositoryImpl.kt +++ b/admin-metrics/src/main/kotlin/com/epam/drill/admin/metrics/repository/impl/MetricsRepositoryImpl.kt @@ -250,9 +250,10 @@ class MetricsRepositoryImpl : MetricsRepository { override suspend fun getBuildDiffReport( buildId: String, baselineBuildId: String - ) = transaction { - executeQueryReturnMap( - """ + ): Map = transaction { + val result = executeQueryReturnMap { + append( + """ WITH Changes AS ( SELECT @@ -266,7 +267,11 @@ class MetricsRepositoryImpl : MetricsRepository { include_deleted => true, include_equal => true ) m - ), + ), + """.trimIndent(), buildId, baselineBuildId + ) + append( + """ Coverage AS ( SELECT isolated_probes_coverage_ratio, @@ -280,28 +285,32 @@ class MetricsRepositoryImpl : MetricsRepository { input_baseline_build_id => ? ) ), - RecommendedTests AS ( - SELECT count(*) AS tests_to_run - FROM metrics.get_recommended_tests_v2( + """.trimIndent(), buildId, baselineBuildId + ) + append( + """ + ImpactedTests AS ( + SELECT count(*) AS impacted_tests + FROM metrics.get_impacted_tests_v2( input_build_id => ?, - input_test_impact_statuses => '{IMPACTED}' + input_baseline_build_id => ? ) ) - SELECT + """.trimIndent(), buildId, baselineBuildId + ) + append( + """ + SELECT (SELECT added FROM Changes) as changes_new_methods, (SELECT modified FROM Changes) as changes_modified_methods, (SELECT deleted FROM Changes) as changes_deleted_methods, - (SELECT added + modified FROM Changes) as total_changes, (SELECT aggregated_tested_methods FROM Coverage) as tested_changes, (SELECT aggregated_probes_coverage_ratio FROM Coverage) as coverage, - (SELECT tests_to_run FROM RecommendedTests) as recommended_tests - """.trimIndent(), - buildId, - baselineBuildId, - buildId, - baselineBuildId, - buildId - ).first() as Map + (SELECT impacted_tests FROM ImpactedTests) as impacted_tests + """.trimIndent() + ) + } + result.firstOrNull() ?: emptyMap() } diff --git a/admin-metrics/src/main/kotlin/com/epam/drill/admin/metrics/service/impl/MetricsServiceImpl.kt b/admin-metrics/src/main/kotlin/com/epam/drill/admin/metrics/service/impl/MetricsServiceImpl.kt index ee9a8e1b0..d767f0544 100644 --- a/admin-metrics/src/main/kotlin/com/epam/drill/admin/metrics/service/impl/MetricsServiceImpl.kt +++ b/admin-metrics/src/main/kotlin/com/epam/drill/admin/metrics/service/impl/MetricsServiceImpl.kt @@ -208,6 +208,8 @@ class MetricsServiceImpl( val baseUrl = metricsServiceUiLinksConfig.baseUrl val buildTestingReportPath = metricsServiceUiLinksConfig.buildTestingReportPath + val buildRisksReportPath = metricsServiceUiLinksConfig.buildRisksReportPath + val impactedTestsReportPath = metricsServiceUiLinksConfig.impactedTestsReportPath mapOf( "inputParameters" to mapOf( "groupId" to groupId, @@ -226,12 +228,30 @@ class MetricsServiceImpl( "metrics" to metrics, "links" to baseUrl?.run { mapOf( - "changes" to null, - "recommended_tests" to null, + "changes" to buildRisksReportPath?.run { + getUriString( + baseUrl = baseUrl, + path = buildRisksReportPath, + queryParams = mapOf( + "build" to buildId, + "baseline_build" to baselineBuildId, + ) + ) + }, + "impacted_tests" to impactedTestsReportPath?.run { + getUriString( + baseUrl = baseUrl, + path = this, + queryParams = mapOf( + "build" to buildId, + "baseline_build" to baselineBuildId, + ) + ) + }, "build" to buildTestingReportPath?.run { getUriString( baseUrl = baseUrl, - path = buildTestingReportPath, + path = this, queryParams = mapOf( "build" to buildId, ) @@ -240,7 +260,7 @@ class MetricsServiceImpl( "baseline_build" to buildTestingReportPath?.run { getUriString( baseUrl = baseUrl, - path = buildTestingReportPath, + path = this, queryParams = mapOf( "build" to baselineBuildId, ) @@ -249,7 +269,7 @@ class MetricsServiceImpl( "full_report" to buildTestingReportPath?.run { getUriString( baseUrl = baseUrl, - path = buildTestingReportPath, + path = this, queryParams = mapOf( "build" to buildId, "baseline_build" to baselineBuildId diff --git a/admin-metrics/src/test/kotlin/com/epam/drill/admin/metrics/BuildDiffReportApiTest.kt b/admin-metrics/src/test/kotlin/com/epam/drill/admin/metrics/BuildDiffReportApiTest.kt index 13aca0d83..7d5d908db 100644 --- a/admin-metrics/src/test/kotlin/com/epam/drill/admin/metrics/BuildDiffReportApiTest.kt +++ b/admin-metrics/src/test/kotlin/com/epam/drill/admin/metrics/BuildDiffReportApiTest.kt @@ -50,7 +50,7 @@ class BuildDiffReportApiTest : DatabaseTests({ }.returnsSingle("$.data.metrics") { metrics -> assertEquals(1, metrics["changes_new_methods"]) assertEquals(1, metrics["changes_modified_methods"]) - assertEquals(2, metrics["total_changes"]) + assertEquals(1, metrics["changes_deleted_methods"]) } } } @@ -109,30 +109,25 @@ class BuildDiffReportApiTest : DatabaseTests({ } @Test - fun `given tests on different builds, build-diff-report service should calculate recommended tests to run`() { + fun `given tests which cover changed methods, build-diff-report service should calculate impacted tests`() { havingData { - build1 has listOf(method1) + build1 has listOf(method1, method2) test1 covers method1 with probesOf(1, 1) on build1 + test2 covers method2 with probesOf(1, 1, 1) on build1 - build2 has listOf(method1, method2, method3) - test2 covers method2 with probesOf(1, 0, 0) on build2 - test2 covers method3 with probesOf(1) on build2 - - build3 hasModified method3 comparedTo build2 - test3 covers method3 with probesOf(1) on build3 + build2 hasModified method1 comparedTo build1 }.expectThat { client.get("/metrics/build-diff-report") { parameter("groupId", testGroup) parameter("appId", testApp) - parameter("buildVersion", build3.buildVersion) + parameter("buildVersion", build2.buildVersion) parameter("baselineBuildVersion", build1.buildVersion) }.returnsSingle("$.data.metrics") { metrics -> - // coverage in method1 is not considered because method1 was not changed, - // test1 is not recommended to run because it covers only method1, - // test2 is recommended to run because it covers method3, but method3 was changed in build3, - // test3 is not recommended to run because it has already been run on build3, - // so total recommended tests is 1 - assertEquals(1, metrics["recommended_tests"]) + // coverage in method2 is not considered because method2 was not changed, + // test2 is not impacted because method2 was not changed in build2, + // test1 is impacted because it covers method1, but method1 was changed in build2, + // so total number of impacted tests is 1 + assertEquals(1, metrics["impacted_tests"]) } } } From ba4daa91db723a77def79397150bd35e8558e0c3 Mon Sep 17 00:00:00 2001 From: iryabov Date: Thu, 12 Feb 2026 13:11:44 +0100 Subject: [PATCH 2/6] feat: update tested changes calculation in build diff report service --- admin-app/src/main/resources/application.conf | 6 ++--- .../config/MetricsServiceUiLinksConfig.kt | 4 ++-- .../repository/impl/MetricsRepositoryImpl.kt | 20 ++++++++++++++-- .../service/impl/MetricsServiceImpl.kt | 2 +- .../admin/metrics/BuildDiffReportApiTest.kt | 23 +++++++++++++++++++ 5 files changed, 47 insertions(+), 8 deletions(-) diff --git a/admin-app/src/main/resources/application.conf b/admin-app/src/main/resources/application.conf index 3b3d0c5d5..340ebbf2a 100644 --- a/admin-app/src/main/resources/application.conf +++ b/admin-app/src/main/resources/application.conf @@ -77,10 +77,10 @@ drill { baseUrl = ${?DRILL_METRICS_UI_BASE_URL} buildTestingReportPath = "/dashboard/2" buildTestingReportPath = ${?DRILL_METRICS_UI_BUILD_TESTING_REPORT_PATH } - buildRisksReportPath = "/dashboard/5" - buildRisksReportPath = ${?DRILL_METRICS_UI_BUILD_RISKS_REPORT_PATH } + buildChangesReportPath = "/dashboard/5" + buildChangesReportPath = ${?DRILL_METRICS_UI_BUILD_CHANGES_REPORT_PATH } impactedTestsReportPath = "/dashboard/13" - impactedTestsReportPath = ${?DRILL_METRICS_UI_BUILD_RISKS_REPORT_PATH } + impactedTestsReportPath = ${?DRILL_METRICS_UI_IMPACTED_TESTS_REPORT_PATH } } } testRecommendations { diff --git a/admin-metrics/src/main/kotlin/com/epam/drill/admin/metrics/config/MetricsServiceUiLinksConfig.kt b/admin-metrics/src/main/kotlin/com/epam/drill/admin/metrics/config/MetricsServiceUiLinksConfig.kt index 37b539158..7de6631be 100644 --- a/admin-metrics/src/main/kotlin/com/epam/drill/admin/metrics/config/MetricsServiceUiLinksConfig.kt +++ b/admin-metrics/src/main/kotlin/com/epam/drill/admin/metrics/config/MetricsServiceUiLinksConfig.kt @@ -26,8 +26,8 @@ class MetricsServiceUiLinksConfig(private val config: ApplicationConfig) { val buildTestingReportPath : String? get() = config.propertyOrNull("buildTestingReportPath")?.getString() - val buildRisksReportPath : String? - get() = config.propertyOrNull("buildRisksReportPath")?.getString() + val buildChangesReportPath : String? + get() = config.propertyOrNull("buildChangesReportPath")?.getString() val impactedTestsReportPath : String? get() = config.propertyOrNull("impactedTestsReportPath")?.getString() diff --git a/admin-metrics/src/main/kotlin/com/epam/drill/admin/metrics/repository/impl/MetricsRepositoryImpl.kt b/admin-metrics/src/main/kotlin/com/epam/drill/admin/metrics/repository/impl/MetricsRepositoryImpl.kt index b3d987055..fb37f47dc 100644 --- a/admin-metrics/src/main/kotlin/com/epam/drill/admin/metrics/repository/impl/MetricsRepositoryImpl.kt +++ b/admin-metrics/src/main/kotlin/com/epam/drill/admin/metrics/repository/impl/MetricsRepositoryImpl.kt @@ -270,6 +270,21 @@ class MetricsRepositoryImpl : MetricsRepository { ), """.trimIndent(), buildId, baselineBuildId ) + append( + """ + TestedChanges AS ( + SELECT + change_type, + COUNT(*) AS tested_methods + FROM metrics.get_changes_with_coverage( + input_build_id => ?, + input_baseline_build_id => ? + ) + WHERE aggregated_covered_probes > 0 + GROUP BY change_type + ), + """.trimIndent(), buildId, baselineBuildId + ) append( """ Coverage AS ( @@ -304,8 +319,9 @@ class MetricsRepositoryImpl : MetricsRepository { (SELECT added FROM Changes) as changes_new_methods, (SELECT modified FROM Changes) as changes_modified_methods, (SELECT deleted FROM Changes) as changes_deleted_methods, - (SELECT aggregated_tested_methods FROM Coverage) as tested_changes, - (SELECT aggregated_probes_coverage_ratio FROM Coverage) as coverage, + COALESCE((SELECT tested_methods FROM TestedChanges WHERE change_type = 'new'), 0) as tested_new_methods, + COALESCE((SELECT tested_methods FROM TestedChanges WHERE change_type = 'modified'), 0) as tested_modified_methods, + (SELECT aggregated_probes_coverage_ratio FROM Coverage) as coverage, (SELECT impacted_tests FROM ImpactedTests) as impacted_tests """.trimIndent() ) diff --git a/admin-metrics/src/main/kotlin/com/epam/drill/admin/metrics/service/impl/MetricsServiceImpl.kt b/admin-metrics/src/main/kotlin/com/epam/drill/admin/metrics/service/impl/MetricsServiceImpl.kt index d767f0544..df14af265 100644 --- a/admin-metrics/src/main/kotlin/com/epam/drill/admin/metrics/service/impl/MetricsServiceImpl.kt +++ b/admin-metrics/src/main/kotlin/com/epam/drill/admin/metrics/service/impl/MetricsServiceImpl.kt @@ -208,7 +208,7 @@ class MetricsServiceImpl( val baseUrl = metricsServiceUiLinksConfig.baseUrl val buildTestingReportPath = metricsServiceUiLinksConfig.buildTestingReportPath - val buildRisksReportPath = metricsServiceUiLinksConfig.buildRisksReportPath + val buildRisksReportPath = metricsServiceUiLinksConfig.buildChangesReportPath val impactedTestsReportPath = metricsServiceUiLinksConfig.impactedTestsReportPath mapOf( "inputParameters" to mapOf( diff --git a/admin-metrics/src/test/kotlin/com/epam/drill/admin/metrics/BuildDiffReportApiTest.kt b/admin-metrics/src/test/kotlin/com/epam/drill/admin/metrics/BuildDiffReportApiTest.kt index 7d5d908db..1156b7b6e 100644 --- a/admin-metrics/src/test/kotlin/com/epam/drill/admin/metrics/BuildDiffReportApiTest.kt +++ b/admin-metrics/src/test/kotlin/com/epam/drill/admin/metrics/BuildDiffReportApiTest.kt @@ -55,6 +55,29 @@ class BuildDiffReportApiTest : DatabaseTests({ } } + @Test + fun `given tested changes, build-diff-report service should calculate total tested changes by change type`() { + havingData { + build1 has listOf(method1, method2) + build2 hasModified method2 comparedTo build1 + build2 hasNew method3 comparedTo build1 + build2 hasNew method4 comparedTo build1 + test1 covers method1 with probesOf(1, 1) on build2 + test2 covers method2 with probesOf(1, 1, 1) on build2 + test3 covers method3 with probesOf(1) on build2 + }.expectThat { + client.get("/metrics/build-diff-report") { + parameter("groupId", testGroup) + parameter("appId", testApp) + parameter("buildVersion", build2.buildVersion) + parameter("baselineBuildVersion", build1.buildVersion) + }.returnsSingle("$.data.metrics") { metrics -> + assertEquals(1, metrics["tested_new_methods"]) + assertEquals(1, metrics["tested_modified_methods"]) + } + } + } + @Test fun `given tests on target build, build-diff-report service should calculate isolated coverage`() = havingData { build1 has listOf(method1, method2) From 5ec0d17012b4e05bdd28fe16a2d11fc3c95460ed Mon Sep 17 00:00:00 2001 From: iryabov Date: Fri, 13 Feb 2026 10:27:43 +0100 Subject: [PATCH 3/6] build: update TestContainers version --- admin-app/build.gradle.kts | 3 ++- admin-test/build.gradle.kts | 2 ++ gradle.properties | 3 ++- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/admin-app/build.gradle.kts b/admin-app/build.gradle.kts index b120a0f38..e38290f9e 100644 --- a/admin-app/build.gradle.kts +++ b/admin-app/build.gradle.kts @@ -24,6 +24,7 @@ val microutilsLoggingVersion: String by parent!!.extra val zaxxerHikaricpVersion: String by parent!!.extra val postgresSqlVersion: String by parent!!.extra val testContainersVersion: String by parent!!.extra +val junitJupiterVersion: String by parent!!.extra val quartzVersion: String by parent!!.extra val logbackVersion: String by parent!!.extra @@ -67,7 +68,7 @@ dependencies { implementation(project(":admin-metrics")) testImplementation(kotlin("test-junit5")) - testImplementation("org.junit.jupiter:junit-jupiter:5.5.2") + testImplementation("org.junit.jupiter:junit-jupiter:$junitJupiterVersion") testImplementation("io.ktor:ktor-server-test-host:$ktorVersion") testImplementation("io.ktor:ktor-client-mock:$ktorVersion") testImplementation("org.testcontainers:testcontainers:$testContainersVersion") diff --git a/admin-test/build.gradle.kts b/admin-test/build.gradle.kts index 2efa0387b..c9d2d8b88 100644 --- a/admin-test/build.gradle.kts +++ b/admin-test/build.gradle.kts @@ -20,6 +20,7 @@ val mockitoKotlinVersion: String by parent!!.extra val exposedVersion: String by parent!!.extra val flywaydbVersion: String by parent!!.extra val testContainersVersion: String by parent!!.extra +val junitJupiterVersion: String by parent!!.extra val postgresSqlVersion: String by parent!!.extra val zaxxerHikaricpVersion: String by parent!!.extra val quartzVersion: String by parent!!.extra @@ -61,6 +62,7 @@ dependencies { implementation("org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion") implementation("org.testcontainers:testcontainers:$testContainersVersion") implementation("org.testcontainers:junit-jupiter:$testContainersVersion") + implementation("org.junit.jupiter:junit-jupiter:$junitJupiterVersion") implementation("org.testcontainers:postgresql:$testContainersVersion") implementation("com.zaxxer:HikariCP:$zaxxerHikaricpVersion") implementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.5.2") diff --git a/gradle.properties b/gradle.properties index 07c5f20d7..4134aff76 100644 --- a/gradle.properties +++ b/gradle.properties @@ -9,7 +9,8 @@ ktorVersion = 2.3.11 kodeinVersion = 7.21.1 microutilsLoggingVersion = 3.0.5 postgresSqlVersion = 42.3.8 -testContainersVersion = 1.16.2 +testContainersVersion = 1.21.4 +junitJupiterVersion = 5.8.1 zaxxerHikaricpVersion = 4.0.3 shadowPluginVersion = 7.1.0 flywaydbVersion = 8.4.1 From b416c0963fe2c2ea6f4491db00a00373b2fd04b2 Mon Sep 17 00:00:00 2001 From: iryabov Date: Mon, 16 Feb 2026 08:53:59 +0100 Subject: [PATCH 4/6] feat: change coverage calculation for getBuildDiffReport feat: add coverage threshold parameter for getBuildDiffReport EPMDJ-11198 --- .../metrics/repository/MetricsRepository.kt | 3 ++- .../repository/impl/MetricsRepositoryImpl.kt | 25 ++++++++----------- .../drill/admin/metrics/route/MetricRoutes.kt | 2 +- .../service/impl/MetricsServiceImpl.kt | 3 ++- .../metrics/db/migration/R__3_Functions.sql | 7 ++++++ .../admin/metrics/BuildDiffReportApiTest.kt | 7 +++--- 6 files changed, 27 insertions(+), 20 deletions(-) diff --git a/admin-metrics/src/main/kotlin/com/epam/drill/admin/metrics/repository/MetricsRepository.kt b/admin-metrics/src/main/kotlin/com/epam/drill/admin/metrics/repository/MetricsRepository.kt index 628a0fc3b..0fd544e79 100644 --- a/admin-metrics/src/main/kotlin/com/epam/drill/admin/metrics/repository/MetricsRepository.kt +++ b/admin-metrics/src/main/kotlin/com/epam/drill/admin/metrics/repository/MetricsRepository.kt @@ -74,7 +74,8 @@ interface MetricsRepository { suspend fun getBuildDiffReport( buildId: String, - baselineBuildId: String + baselineBuildId: String, + coverageThreshold: Double, ): Map suspend fun getRecommendedTests( diff --git a/admin-metrics/src/main/kotlin/com/epam/drill/admin/metrics/repository/impl/MetricsRepositoryImpl.kt b/admin-metrics/src/main/kotlin/com/epam/drill/admin/metrics/repository/impl/MetricsRepositoryImpl.kt index fb37f47dc..4326660bb 100644 --- a/admin-metrics/src/main/kotlin/com/epam/drill/admin/metrics/repository/impl/MetricsRepositoryImpl.kt +++ b/admin-metrics/src/main/kotlin/com/epam/drill/admin/metrics/repository/impl/MetricsRepositoryImpl.kt @@ -249,23 +249,22 @@ class MetricsRepositoryImpl : MetricsRepository { override suspend fun getBuildDiffReport( buildId: String, - baselineBuildId: String + baselineBuildId: String, + coverageThreshold: Double, ): Map = transaction { val result = executeQueryReturnMap { append( """ WITH Changes AS ( - SELECT - COUNT(CASE WHEN change_type = 'equal' THEN 1 END) AS equal, + SELECT COUNT(CASE WHEN change_type = 'modified' THEN 1 END) AS modified, COUNT(CASE WHEN change_type = 'new' THEN 1 END) AS added, COUNT(CASE WHEN change_type = 'deleted' THEN 1 END) AS deleted FROM metrics.get_changes( input_build_id => ?, input_baseline_build_id => ?, - include_deleted => true, - include_equal => true + include_deleted => true ) m ), """.trimIndent(), buildId, baselineBuildId @@ -278,23 +277,20 @@ class MetricsRepositoryImpl : MetricsRepository { COUNT(*) AS tested_methods FROM metrics.get_changes_with_coverage( input_build_id => ?, - input_baseline_build_id => ? + input_baseline_build_id => ?, + input_coverage_test_results => array['PASSED'] ) - WHERE aggregated_covered_probes > 0 + WHERE aggregated_covered_probes > ? GROUP BY change_type ), - """.trimIndent(), buildId, baselineBuildId + """.trimIndent(), buildId, baselineBuildId, coverageThreshold ) append( """ Coverage AS ( SELECT isolated_probes_coverage_ratio, - aggregated_probes_coverage_ratio, - isolated_tested_methods, - isolated_missed_methods, - aggregated_tested_methods, - aggregated_missed_methods + aggregated_probes_coverage_ratio FROM metrics.get_builds_with_coverage( input_build_id => ?, input_baseline_build_id => ? @@ -321,7 +317,8 @@ class MetricsRepositoryImpl : MetricsRepository { (SELECT deleted FROM Changes) as changes_deleted_methods, COALESCE((SELECT tested_methods FROM TestedChanges WHERE change_type = 'new'), 0) as tested_new_methods, COALESCE((SELECT tested_methods FROM TestedChanges WHERE change_type = 'modified'), 0) as tested_modified_methods, - (SELECT aggregated_probes_coverage_ratio FROM Coverage) as coverage, + (SELECT isolated_probes_coverage_ratio FROM Coverage) as coverage, + (SELECT aggregated_probes_coverage_ratio FROM Coverage) as aggregated_coverage, (SELECT impacted_tests FROM ImpactedTests) as impacted_tests """.trimIndent() ) diff --git a/admin-metrics/src/main/kotlin/com/epam/drill/admin/metrics/route/MetricRoutes.kt b/admin-metrics/src/main/kotlin/com/epam/drill/admin/metrics/route/MetricRoutes.kt index 577e322b6..37f7d0e3d 100644 --- a/admin-metrics/src/main/kotlin/com/epam/drill/admin/metrics/route/MetricRoutes.kt +++ b/admin-metrics/src/main/kotlin/com/epam/drill/admin/metrics/route/MetricRoutes.kt @@ -110,7 +110,7 @@ class Metrics { val baselineInstanceId: String? = null, val baselineCommitSha: String? = null, val baselineBuildVersion: String? = null, - val coverageThreshold: Double = 1.0, // TODO Float should be enough + val coverageThreshold: Double = 0.0, ) @Resource("/recommended-tests") diff --git a/admin-metrics/src/main/kotlin/com/epam/drill/admin/metrics/service/impl/MetricsServiceImpl.kt b/admin-metrics/src/main/kotlin/com/epam/drill/admin/metrics/service/impl/MetricsServiceImpl.kt index df14af265..523d556b0 100644 --- a/admin-metrics/src/main/kotlin/com/epam/drill/admin/metrics/service/impl/MetricsServiceImpl.kt +++ b/admin-metrics/src/main/kotlin/com/epam/drill/admin/metrics/service/impl/MetricsServiceImpl.kt @@ -203,7 +203,8 @@ class MetricsServiceImpl( val metrics = metricsRepository.getBuildDiffReport( buildId, - baselineBuildId + baselineBuildId, + coverageThreshold ) val baseUrl = metricsServiceUiLinksConfig.baseUrl diff --git a/admin-metrics/src/main/resources/metrics/db/migration/R__3_Functions.sql b/admin-metrics/src/main/resources/metrics/db/migration/R__3_Functions.sql index 6c86fc99e..213bfb312 100644 --- a/admin-metrics/src/main/resources/metrics/db/migration/R__3_Functions.sql +++ b/admin-metrics/src/main/resources/metrics/db/migration/R__3_Functions.sql @@ -71,6 +71,7 @@ $$ LANGUAGE plpgsql STABLE PARALLEL SAFE; -- @param input_coverage_branches: Array of branches to filter coverage -- @param input_coverage_test_tags: Array of test tags to filter coverage -- @param input_coverage_test_task_ids: Array of test task IDs to filter coverage +-- @param input_coverage_test_results: Array of test results to filter coverage -- @param input_coverage_period_from: Optional timestamp to filter coverage by creation date -- @param is_smart_coverage_before_build: Boolean value indicating whether smart coverage should only be considered up to the build date -- @returns TABLE: A table containing methods with coverage information @@ -89,6 +90,7 @@ CREATE OR REPLACE FUNCTION metrics.get_methods_with_coverage( input_coverage_branches VARCHAR[] DEFAULT NULL, input_coverage_test_tags VARCHAR[] DEFAULT NULL, input_coverage_test_task_ids VARCHAR[] DEFAULT NULL, + input_coverage_test_results VARCHAR[] DEFAULT NULL, input_coverage_period_from TIMESTAMP DEFAULT NULL, include_smart_coverage BOOLEAN DEFAULT TRUE, @@ -145,6 +147,7 @@ BEGIN AND (input_coverage_app_env_ids IS NULL OR ic.app_env_id = ANY(input_coverage_app_env_ids::VARCHAR[])) AND (input_coverage_test_task_ids IS NULL OR ic.test_task_id = ANY(input_coverage_test_task_ids::VARCHAR[])) AND (input_coverage_test_tags IS NULL OR ic.test_tag = ANY(input_coverage_test_tags::VARCHAR[])) + AND (input_coverage_test_results IS NULL OR ic.test_result = ANY(input_coverage_test_results::VARCHAR[])) AND (input_coverage_period_from IS NULL OR ic.created_at_day >= input_coverage_period_from) LEFT JOIN metrics.method_daily_coverage sc ON include_smart_coverage IS true AND sc.group_id = bm.group_id AND sc.app_id = bm.app_id AND sc.method_id = bm.method_id -- Filters by smart coverage @@ -153,6 +156,7 @@ BEGIN AND (input_coverage_app_env_ids IS NULL OR sc.app_env_id = ANY(input_coverage_app_env_ids::VARCHAR[])) AND (input_coverage_test_task_ids IS NULL OR sc.test_task_id = ANY(input_coverage_test_task_ids::VARCHAR[])) AND (input_coverage_test_tags IS NULL OR sc.test_tag = ANY(input_coverage_test_tags::VARCHAR[])) + AND (input_coverage_test_results IS NULL OR sc.test_result = ANY(input_coverage_test_results::VARCHAR[])) AND (input_coverage_period_from IS NULL OR sc.created_at_day >= input_coverage_period_from) WHERE bm.group_id = _group_id AND bm.app_id = _app_id @@ -420,6 +424,7 @@ $$ LANGUAGE plpgsql STABLE PARALLEL SAFE; -- @param input_coverage_branches: Array of branches to filter coverage -- @param input_coverage_test_tags: Array of test tags to filter coverage -- @param input_coverage_test_task_ids: Array of test task IDs to filter coverage +-- @param input_coverage_test_results: Array of test results to filter coverage -- @param input_coverage_period_from: Optional timestamp to filter coverage by creation date -- @param is_smart_coverage_before_build: Boolean value indicating whether smart coverage should only be considered up to the build date -- @returns TABLE: A table containing changes in methods with coverage information between the two builds @@ -437,6 +442,7 @@ CREATE OR REPLACE FUNCTION metrics.get_changes_with_coverage( input_coverage_branches VARCHAR[] DEFAULT NULL, input_coverage_test_tags VARCHAR[] DEFAULT NULL, input_coverage_test_task_ids VARCHAR[] DEFAULT NULL, + input_coverage_test_results VARCHAR[] DEFAULT NULL, input_coverage_period_from TIMESTAMP DEFAULT NULL, include_smart_coverage BOOLEAN DEFAULT TRUE, @@ -503,6 +509,7 @@ BEGIN input_coverage_branches => input_coverage_branches, input_coverage_test_tags => input_coverage_test_tags, input_coverage_test_task_ids => input_coverage_test_task_ids, + input_coverage_test_results => input_coverage_test_results, input_coverage_period_from => input_coverage_period_from, include_smart_coverage => include_smart_coverage, diff --git a/admin-metrics/src/test/kotlin/com/epam/drill/admin/metrics/BuildDiffReportApiTest.kt b/admin-metrics/src/test/kotlin/com/epam/drill/admin/metrics/BuildDiffReportApiTest.kt index 1156b7b6e..b7221777d 100644 --- a/admin-metrics/src/test/kotlin/com/epam/drill/admin/metrics/BuildDiffReportApiTest.kt +++ b/admin-metrics/src/test/kotlin/com/epam/drill/admin/metrics/BuildDiffReportApiTest.kt @@ -102,7 +102,7 @@ class BuildDiffReportApiTest : DatabaseTests({ } @Test - fun `given tests on different builds, build-diff-report service should calculate aggregated coverage`() = + fun `given tests on different builds, build-diff-report service should calculate coverage`() = havingData { build1 has listOf(method1) test1 covers method1 with probesOf(1, 1) on build1 @@ -126,8 +126,9 @@ class BuildDiffReportApiTest : DatabaseTests({ // test3 covers method2 by 1 of 3 probes but different probes compared to test2, // coverage in method1 is not considered because method1 was not changed, // coverage collected by test2 in method3 is not considered because method3 was changed, - // so total coverage is 2 of 4 probes - assertEquals(0.5, metrics["coverage"]) + // so total aggregated coverage is 2 of 4 probes + assertEquals(0.5, metrics["aggregated_coverage"]) + assertEquals(0.25, metrics["coverage"]) } } From 69e0295eb03baf834120058277f4d9b62333a0de Mon Sep 17 00:00:00 2001 From: iryabov Date: Mon, 16 Feb 2026 13:54:30 +0100 Subject: [PATCH 5/6] feat: add calculation of passed and failed impacted tests in build diff report --- .../repository/impl/MetricsRepositoryImpl.kt | 39 ++++++++++++++++--- .../admin/metrics/BuildDiffReportApiTest.kt | 1 + .../drill/admin/metrics/DataIngestClient.kt | 8 ++-- 3 files changed, 40 insertions(+), 8 deletions(-) diff --git a/admin-metrics/src/main/kotlin/com/epam/drill/admin/metrics/repository/impl/MetricsRepositoryImpl.kt b/admin-metrics/src/main/kotlin/com/epam/drill/admin/metrics/repository/impl/MetricsRepositoryImpl.kt index 4326660bb..b57e53a37 100644 --- a/admin-metrics/src/main/kotlin/com/epam/drill/admin/metrics/repository/impl/MetricsRepositoryImpl.kt +++ b/admin-metrics/src/main/kotlin/com/epam/drill/admin/metrics/repository/impl/MetricsRepositoryImpl.kt @@ -298,17 +298,44 @@ class MetricsRepositoryImpl : MetricsRepository { ), """.trimIndent(), buildId, baselineBuildId ) + append( + """ + TestLaunches AS ( + SELECT + tl.test_definition_id, + MIN(tl.test_result) AS test_result + FROM metrics.test_launches tl + JOIN metrics.test_sessions ts ON ts.test_session_id = tl.test_session_id AND ts.group_id = tl.group_id + JOIN metrics.test_session_builds tsb ON tsb.test_session_id = ts.test_session_id AND tsb.group_id = ts.group_id + WHERE tsb.build_id = ? + AND tl.test_result IN ('PASSED', 'FAILED') + GROUP BY tl.test_definition_id + ), + """.trimIndent(), buildId + ) append( """ ImpactedTests AS ( - SELECT count(*) AS impacted_tests + SELECT + test_definition_id, + group_id FROM metrics.get_impacted_tests_v2( - input_build_id => ?, - input_baseline_build_id => ? + input_build_id => ?, + input_baseline_build_id => ? ) - ) + ), """.trimIndent(), buildId, baselineBuildId ) + append(""" + ImpactedTestsWithResults AS ( + SELECT + COUNT(*) AS impacted_tests, + SUM(CASE WHEN test_result = 'PASSED' THEN 1 ELSE 0 END) AS passed_impacted_tests, + SUM(CASE WHEN test_result = 'FAILED' THEN 1 ELSE 0 END) AS failed_impacted_tests + FROM ImpactedTests it + LEFT JOIN TestLaunches tl ON tl.test_definition_id = it.test_definition_id + ) + """.trimIndent()) append( """ SELECT @@ -319,7 +346,9 @@ class MetricsRepositoryImpl : MetricsRepository { COALESCE((SELECT tested_methods FROM TestedChanges WHERE change_type = 'modified'), 0) as tested_modified_methods, (SELECT isolated_probes_coverage_ratio FROM Coverage) as coverage, (SELECT aggregated_probes_coverage_ratio FROM Coverage) as aggregated_coverage, - (SELECT impacted_tests FROM ImpactedTests) as impacted_tests + (SELECT impacted_tests FROM ImpactedTestsWithResults) AS impacted_tests, + (SELECT passed_impacted_tests FROM ImpactedTestsWithResults) AS passed_impacted_tests, + (SELECT failed_impacted_tests FROM ImpactedTestsWithResults) AS failed_impacted_tests """.trimIndent() ) } diff --git a/admin-metrics/src/test/kotlin/com/epam/drill/admin/metrics/BuildDiffReportApiTest.kt b/admin-metrics/src/test/kotlin/com/epam/drill/admin/metrics/BuildDiffReportApiTest.kt index b7221777d..cb94855e9 100644 --- a/admin-metrics/src/test/kotlin/com/epam/drill/admin/metrics/BuildDiffReportApiTest.kt +++ b/admin-metrics/src/test/kotlin/com/epam/drill/admin/metrics/BuildDiffReportApiTest.kt @@ -152,6 +152,7 @@ class BuildDiffReportApiTest : DatabaseTests({ // test1 is impacted because it covers method1, but method1 was changed in build2, // so total number of impacted tests is 1 assertEquals(1, metrics["impacted_tests"]) + assertEquals(0, metrics["passed_impacted_tests"]) } } } diff --git a/admin-metrics/src/test/kotlin/com/epam/drill/admin/metrics/DataIngestClient.kt b/admin-metrics/src/test/kotlin/com/epam/drill/admin/metrics/DataIngestClient.kt index 2d857b0fd..dc6ef6b8a 100644 --- a/admin-metrics/src/test/kotlin/com/epam/drill/admin/metrics/DataIngestClient.kt +++ b/admin-metrics/src/test/kotlin/com/epam/drill/admin/metrics/DataIngestClient.kt @@ -45,7 +45,9 @@ suspend fun HttpClient.launchTest( session: SessionPayload, test: TestDetails, instance: InstancePayload, - coverage: Array> + coverage: Array>, + result: TestResult = TestResult.PASSED, + duration: Int = 10, ) { val testLaunchId = "test-launch-${counter.incrementAndGet()}" putTestSession(session) @@ -57,8 +59,8 @@ suspend fun HttpClient.launchTest( TestLaunchInfo( testLaunchId = testLaunchId, testDefinitionId = test.definitionId, - result = TestResult.PASSED, - duration = 10, + result = result, + duration = duration, details = test ) ) From 0cf2e14867c54e2927145f4476f67de2428013cf Mon Sep 17 00:00:00 2001 From: iryabov Date: Tue, 17 Feb 2026 15:00:48 +0100 Subject: [PATCH 6/6] test: add test for correlating impacted tests in build-diff-report service --- .../admin/metrics/BuildDiffReportApiTest.kt | 25 +++++++++++++++ .../epam/drill/admin/metrics/TestDataDsl.kt | 31 +++++++++++++++++-- 2 files changed, 53 insertions(+), 3 deletions(-) diff --git a/admin-metrics/src/test/kotlin/com/epam/drill/admin/metrics/BuildDiffReportApiTest.kt b/admin-metrics/src/test/kotlin/com/epam/drill/admin/metrics/BuildDiffReportApiTest.kt index faf5e47ea..77f36864f 100644 --- a/admin-metrics/src/test/kotlin/com/epam/drill/admin/metrics/BuildDiffReportApiTest.kt +++ b/admin-metrics/src/test/kotlin/com/epam/drill/admin/metrics/BuildDiffReportApiTest.kt @@ -154,6 +154,31 @@ class BuildDiffReportApiTest : DatabaseTests({ } } + @Test + fun `given tests in current build, build-diff-report service should correlate passed and failed tests with impacted tests`() { + havingData { + build1 has listOf(method1, method2) + test1 of session1 covers method1 with probesOf(1, 1) on build1 + test2 of session1 covers method2 with probesOf(1, 1, 1) on build1 + + build2 hasModified method1 comparedTo build1 + build2 hasModified method2 comparedTo build1 + test1 of session2 covers method1 with probesOf(1, 1) on build2 + test2 of session2 failsOn method2 with probesOf(1, 0, 0) on build2 + }.expectThat { + client.get("/metrics/build-diff-report") { + parameter("groupId", testGroup) + parameter("appId", testApp) + parameter("buildVersion", build2.buildVersion) + parameter("baselineBuildVersion", build1.buildVersion) + }.returnsSingle("$.data.metrics") { metrics -> + assertEquals(2, metrics["impacted_tests"]) + assertEquals(1, metrics["passed_impacted_tests"]) + assertEquals(1, metrics["failed_impacted_tests"]) + } + } + } + @Test fun `given non-existent baseline, build-diff-report service should return 422 status`() { havingData { diff --git a/admin-metrics/src/test/kotlin/com/epam/drill/admin/metrics/TestDataDsl.kt b/admin-metrics/src/test/kotlin/com/epam/drill/admin/metrics/TestDataDsl.kt index 4c2b95923..deff795da 100644 --- a/admin-metrics/src/test/kotlin/com/epam/drill/admin/metrics/TestDataDsl.kt +++ b/admin-metrics/src/test/kotlin/com/epam/drill/admin/metrics/TestDataDsl.kt @@ -28,8 +28,10 @@ import com.epam.drill.admin.test.drillClient import com.epam.drill.admin.writer.rawdata.config.rawDataServicesDIModule import com.epam.drill.admin.writer.rawdata.route.dataIngestRoutes import com.epam.drill.admin.writer.rawdata.route.payload.InstancePayload +import com.epam.drill.admin.writer.rawdata.route.payload.SessionPayload import com.epam.drill.admin.writer.rawdata.route.payload.SingleMethodPayload import com.epam.drill.admin.writer.rawdata.route.payload.TestDetails +import com.epam.drill.admin.writer.rawdata.route.payload.TestResult import io.ktor.client.HttpClient import io.ktor.client.request.HttpRequestBuilder import kotlinx.coroutines.runBlocking @@ -61,8 +63,15 @@ fun havingData(testsData: suspend TestDataDsl.() -> Unit): HttpClient { } } +class TestSessionMap( + val test: TestDetails, + val session: SessionPayload, +) + class TestCoverageMap( val test: TestDetails, + val session: SessionPayload, + val result: TestResult, val method: SingleMethodPayload, val probes: IntArray ) @@ -110,12 +119,28 @@ class TestDataDsl(val client: HttpClient) { suspend infix fun InstancePayload.hasDeleted(method: SingleMethodPayload) = MethodComparison(this, method, ChangeType.DELETED) + suspend infix fun TestDetails.of(session: SessionPayload): TestSessionMap { + return TestSessionMap(this, session = session) + } + + suspend infix fun TestSessionMap.covers(method: SingleMethodPayload): TestCoverageMap { + return TestCoverageMap(this.test, this.session, TestResult.PASSED, method, IntArray(method.probesCount) { 1 }) + } + + suspend infix fun TestSessionMap.failsOn(method: SingleMethodPayload): TestCoverageMap { + return TestCoverageMap(this.test, this.session, TestResult.FAILED, method, IntArray(method.probesCount) { 1 }) + } + suspend infix fun TestDetails.covers(method: SingleMethodPayload): TestCoverageMap { - return TestCoverageMap(this, method, IntArray(method.probesCount) { 1 }) + return TestCoverageMap(this, session1, TestResult.PASSED, method, IntArray(method.probesCount) { 1 }) + } + + suspend infix fun TestDetails.failsOn(method: SingleMethodPayload): TestCoverageMap { + return TestCoverageMap(this, session1, TestResult.FAILED, method, IntArray(method.probesCount) { 1 }) } suspend infix fun TestCoverageMap.with(probes: IntArray): TestCoverageMap { - return TestCoverageMap(this.test, this.method, probes) + return TestCoverageMap(this.test, this.session, this.result, this.method, probes) } suspend infix fun MethodComparison.comparedTo(baseline: InstancePayload) { @@ -178,7 +203,7 @@ class TestDataDsl(val client: HttpClient) { this.method to this.probes ) client.launchTest( - session1, this.test, build, methods + this.session, this.test, build, methods, this.result ) } }