From cfc2c6f52c6155cefd8e2863f4d3d4ecd8c8d86c Mon Sep 17 00:00:00 2001 From: RomanDavlyatshin Date: Tue, 20 Jan 2026 17:00:00 +0400 Subject: [PATCH 1/8] feat: add probe position storage to CoverageManager & send cov per method --- .../drill/agent/test2code/coverage/CoverageManager.kt | 10 +++++++++- .../drill/agent/test2code/coverage/CoverageSender.kt | 8 +++++--- .../drill/agent/test2code/coverage/Instrumentation.kt | 7 ++++++- .../com/epam/drill/agent/test2code/coverage/Models.kt | 1 + .../drill/agent/test2code/coverage/ProbesProvider.kt | 6 +++++- 5 files changed, 26 insertions(+), 6 deletions(-) diff --git a/test2code/src/main/kotlin/com/epam/drill/agent/test2code/coverage/CoverageManager.kt b/test2code/src/main/kotlin/com/epam/drill/agent/test2code/coverage/CoverageManager.kt index 03e1f321..a20bdb2b 100644 --- a/test2code/src/main/kotlin/com/epam/drill/agent/test2code/coverage/CoverageManager.kt +++ b/test2code/src/main/kotlin/com/epam/drill/agent/test2code/coverage/CoverageManager.kt @@ -16,6 +16,7 @@ package com.epam.drill.agent.test2code.coverage import com.epam.drill.agent.jacoco.AgentProbes +import java.util.concurrent.ConcurrentHashMap open class CoverageManager( private val threadCoverageRecorder: ICoverageRecorder = ThreadCoverageRecorder(), @@ -23,6 +24,8 @@ open class CoverageManager( ) : IProbesProxy, ICoverageRecorder by threadCoverageRecorder { + private val classProbePositions: ConcurrentHashMap>> = ConcurrentHashMap() + override fun invoke( id: Long, num: Int, @@ -37,12 +40,17 @@ open class CoverageManager( name = name, probes = AgentProbes(probeCount), sessionId = coverage.context.sessionId, - testId = coverage.context.testId + testId = coverage.context.testId, + probePositions = classProbePositions[id]!! // TODO do not throw ) } return execDatum.probes } + override fun addProbePositions(classId: Long, probePositions: Map>) { + classProbePositions[classId] = probePositions + } + override fun pollRecorded(): Sequence { return threadCoverageRecorder.pollRecorded() + globalCoverageRecorder.pollRecorded() } diff --git a/test2code/src/main/kotlin/com/epam/drill/agent/test2code/coverage/CoverageSender.kt b/test2code/src/main/kotlin/com/epam/drill/agent/test2code/coverage/CoverageSender.kt index 1c9e61ae..64c7ed41 100644 --- a/test2code/src/main/kotlin/com/epam/drill/agent/test2code/coverage/CoverageSender.kt +++ b/test2code/src/main/kotlin/com/epam/drill/agent/test2code/coverage/CoverageSender.kt @@ -20,7 +20,7 @@ import java.util.concurrent.TimeUnit import mu.KotlinLogging import com.epam.drill.agent.common.transport.AgentMessageDestination import com.epam.drill.agent.common.transport.AgentMessageSender -import com.epam.drill.agent.test2code.common.api.ClassCoverage +import com.epam.drill.agent.test2code.common.api.MethodCoverage import com.epam.drill.agent.test2code.common.api.toBitSet import com.epam.drill.agent.test2code.common.transport.CoveragePayload import kotlinx.serialization.KSerializer @@ -73,11 +73,13 @@ class IntervalCoverageSender( */ private fun sendProbes(dataToSend: Sequence) { dataToSend - .map { ClassCoverage( + .flatMap { it.probePositions.map { (signature, positions) -> MethodCoverage( classname = it.name, + signature = signature, testId = it.testId, testSessionId = it.sessionId, - probes = it.probes.values.toBitSet()) } + probes = it.probes.values.copyOfRange(positions.first, positions.first + positions.second).toBitSet() + ) } } .chunked(pageSize) .forEach { sender.send(destination, CoveragePayload(groupId, appId, instanceId, it), CoveragePayload.serializer()) } } diff --git a/test2code/src/main/kotlin/com/epam/drill/agent/test2code/coverage/Instrumentation.kt b/test2code/src/main/kotlin/com/epam/drill/agent/test2code/coverage/Instrumentation.kt index 2d900557..c9de1260 100644 --- a/test2code/src/main/kotlin/com/epam/drill/agent/test2code/coverage/Instrumentation.kt +++ b/test2code/src/main/kotlin/com/epam/drill/agent/test2code/coverage/Instrumentation.kt @@ -21,6 +21,7 @@ import com.epam.drill.agent.jacoco.BooleanArrayProbeInserter.* import com.epam.drill.agent.jacoco.DrillClassProbesAdapter import com.epam.drill.agent.jacoco.DrillDuplicateFrameEliminator import com.epam.drill.agent.jacoco.DrillMethodInstrumenter +import com.epam.drill.agent.test2code.classparsing.ClassProbeCounter import com.epam.drill.agent.test2code.classparsing.ProbeCounter import org.jacoco.core.internal.data.CRC64 import org.jacoco.core.internal.flow.* @@ -42,10 +43,14 @@ class DrillInstrumenter( val classId = CRC64.classId(initialBytes) //count probes before transformation - val counter = ProbeCounter() val reader = InstrSupport.classReaderFor(initialBytes) + val counter = ClassProbeCounter(className) reader.accept(DrillClassProbesAdapter(counter, false), 0) + probesProxy.addProbePositions(classId, counter.methods.associate { m -> + "${m.classname}:${m.name}:${m.params}:${m.returnType}" to Pair(m.probesCount, m.probesStartPos) + }) + val genId = classCounter.incrementAndGet() val probeCount = counter.count val strategy = DrillProbeStrategy( diff --git a/test2code/src/main/kotlin/com/epam/drill/agent/test2code/coverage/Models.kt b/test2code/src/main/kotlin/com/epam/drill/agent/test2code/coverage/Models.kt index c64a94c0..a3d9ea91 100644 --- a/test2code/src/main/kotlin/com/epam/drill/agent/test2code/coverage/Models.kt +++ b/test2code/src/main/kotlin/com/epam/drill/agent/test2code/coverage/Models.kt @@ -32,4 +32,5 @@ data class ExecDatum( val probes: AgentProbes, val sessionId: String, val testId: String, + val probePositions: Map> ) diff --git a/test2code/src/main/kotlin/com/epam/drill/agent/test2code/coverage/ProbesProvider.kt b/test2code/src/main/kotlin/com/epam/drill/agent/test2code/coverage/ProbesProvider.kt index 29122602..7d056a94 100644 --- a/test2code/src/main/kotlin/com/epam/drill/agent/test2code/coverage/ProbesProvider.kt +++ b/test2code/src/main/kotlin/com/epam/drill/agent/test2code/coverage/ProbesProvider.kt @@ -21,7 +21,11 @@ import com.epam.drill.agent.jacoco.AgentProbes * Provides boolean array for the probe. * Implementations must be kotlin singleton objects. */ -typealias IProbesProxy = (ClassId, Int, String, Int) -> AgentProbes + +interface IProbesProxy { + fun invoke(id: ClassId, num: Int, name: String, probeCount: Int): AgentProbes + fun addProbePositions(classId: Long, probePositions: Map>) +} const val SESSION_CONTEXT_NONE = "SESSION_CONTEXT_NONE" const val TEST_CONTEXT_NONE = "TEST_CONTEXT_NONE" From ae757a7236edd71730f6cfd4757b390366e50fac Mon Sep 17 00:00:00 2001 From: RomanDavlyatshin Date: Tue, 20 Jan 2026 18:07:38 +0400 Subject: [PATCH 2/8] build: set sharedLibsRef to feature/properties-file-parse-multiline-values --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index 9be62a1f..8cda8ed2 100644 --- a/gradle.properties +++ b/gradle.properties @@ -21,7 +21,7 @@ apacheHttpClientVersion = 5.2.3 aesyDatasizeVersion = 1.0.0 bytebuddyVersion = 1.14.11 -sharedLibsRef = main +sharedLibsRef = feature/properties-file-parse-multiline-values sharedLibsLocalPath = lib-jvm-shared nativeAgentLibName = drill-agent nativeAgentHookEnabled = false From 78fb6c9aee08c48f00d6c6736c9bdcf39f624c87 Mon Sep 17 00:00:00 2001 From: RomanDavlyatshin Date: Tue, 20 Jan 2026 18:15:38 +0400 Subject: [PATCH 3/8] test: fix GlobalCoverageRecorderTest --- .../agent/test2code/coverage/GlobalCoverageRecorderTest.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test2code/src/test/kotlin/com/epam/drill/agent/test2code/coverage/GlobalCoverageRecorderTest.kt b/test2code/src/test/kotlin/com/epam/drill/agent/test2code/coverage/GlobalCoverageRecorderTest.kt index af3ab796..b4d46025 100644 --- a/test2code/src/test/kotlin/com/epam/drill/agent/test2code/coverage/GlobalCoverageRecorderTest.kt +++ b/test2code/src/test/kotlin/com/epam/drill/agent/test2code/coverage/GlobalCoverageRecorderTest.kt @@ -92,7 +92,8 @@ class GlobalCoverageRecorderTest { name = "foo", sessionId = SESSION_CONTEXT_AMBIENT, probes = AgentProbes(initialSize = probes.size, values = booleanArrayOf(*probes)), - testId = TEST_CONTEXT_NONE + testId = TEST_CONTEXT_NONE, + probePositions = emptyMap() ) } From 3013cf0d4937d0e4d7c7b1a1dcb553336d49a7a8 Mon Sep 17 00:00:00 2001 From: RomanDavlyatshin Date: Tue, 20 Jan 2026 19:25:15 +0400 Subject: [PATCH 4/8] fix: probesStartPos and probesCount mixed places --- .../com/epam/drill/agent/test2code/coverage/Instrumentation.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test2code/src/main/kotlin/com/epam/drill/agent/test2code/coverage/Instrumentation.kt b/test2code/src/main/kotlin/com/epam/drill/agent/test2code/coverage/Instrumentation.kt index c9de1260..ea5c8114 100644 --- a/test2code/src/main/kotlin/com/epam/drill/agent/test2code/coverage/Instrumentation.kt +++ b/test2code/src/main/kotlin/com/epam/drill/agent/test2code/coverage/Instrumentation.kt @@ -48,7 +48,7 @@ class DrillInstrumenter( reader.accept(DrillClassProbesAdapter(counter, false), 0) probesProxy.addProbePositions(classId, counter.methods.associate { m -> - "${m.classname}:${m.name}:${m.params}:${m.returnType}" to Pair(m.probesCount, m.probesStartPos) + "${m.classname}:${m.name}:${m.params}:${m.returnType}" to Pair(m.probesStartPos, m.probesCount) }) val genId = classCounter.incrementAndGet() From 3d03a047a04a63534b26ad239f57a39ef73ede06 Mon Sep 17 00:00:00 2001 From: RomanDavlyatshin Date: Wed, 21 Jan 2026 16:16:11 +0400 Subject: [PATCH 5/8] feat: filter coverage with empty probes --- .../agent/test2code/coverage/CoverageSender.kt | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/test2code/src/main/kotlin/com/epam/drill/agent/test2code/coverage/CoverageSender.kt b/test2code/src/main/kotlin/com/epam/drill/agent/test2code/coverage/CoverageSender.kt index 64c7ed41..4a8e4912 100644 --- a/test2code/src/main/kotlin/com/epam/drill/agent/test2code/coverage/CoverageSender.kt +++ b/test2code/src/main/kotlin/com/epam/drill/agent/test2code/coverage/CoverageSender.kt @@ -73,13 +73,17 @@ class IntervalCoverageSender( */ private fun sendProbes(dataToSend: Sequence) { dataToSend - .flatMap { it.probePositions.map { (signature, positions) -> MethodCoverage( - classname = it.name, - signature = signature, - testId = it.testId, - testSessionId = it.sessionId, - probes = it.probes.values.copyOfRange(positions.first, positions.first + positions.second).toBitSet() - ) } } + .flatMap { it.probePositions.mapNotNull { (signature, positions) -> + val methodProbes = it.probes.values.copyOfRange(positions.first, positions.first + positions.second).toBitSet() + if (!methodProbes.isEmpty) null + else MethodCoverage( + classname = it.name, + signature = signature, + testId = it.testId, + testSessionId = it.sessionId, + probes = methodProbes + ) + }} .chunked(pageSize) .forEach { sender.send(destination, CoveragePayload(groupId, appId, instanceId, it), CoveragePayload.serializer()) } } From 1e83e3c2af9f16579535685c40de855c966bd9b1 Mon Sep 17 00:00:00 2001 From: RomanDavlyatshin Date: Wed, 21 Jan 2026 16:19:59 +0400 Subject: [PATCH 6/8] refactor: remove redundant classname from ExecDatum --- .../com/epam/drill/agent/test2code/coverage/CoverageManager.kt | 1 - .../com/epam/drill/agent/test2code/coverage/CoverageSender.kt | 1 - .../kotlin/com/epam/drill/agent/test2code/coverage/Models.kt | 1 - .../drill/agent/test2code/coverage/GlobalCoverageRecorderTest.kt | 1 - 4 files changed, 4 deletions(-) diff --git a/test2code/src/main/kotlin/com/epam/drill/agent/test2code/coverage/CoverageManager.kt b/test2code/src/main/kotlin/com/epam/drill/agent/test2code/coverage/CoverageManager.kt index a20bdb2b..95c9116b 100644 --- a/test2code/src/main/kotlin/com/epam/drill/agent/test2code/coverage/CoverageManager.kt +++ b/test2code/src/main/kotlin/com/epam/drill/agent/test2code/coverage/CoverageManager.kt @@ -37,7 +37,6 @@ open class CoverageManager( val execDatum = coverage.execData.getOrPut(id) { ExecDatum( id = id, - name = name, probes = AgentProbes(probeCount), sessionId = coverage.context.sessionId, testId = coverage.context.testId, diff --git a/test2code/src/main/kotlin/com/epam/drill/agent/test2code/coverage/CoverageSender.kt b/test2code/src/main/kotlin/com/epam/drill/agent/test2code/coverage/CoverageSender.kt index 4a8e4912..27004cda 100644 --- a/test2code/src/main/kotlin/com/epam/drill/agent/test2code/coverage/CoverageSender.kt +++ b/test2code/src/main/kotlin/com/epam/drill/agent/test2code/coverage/CoverageSender.kt @@ -77,7 +77,6 @@ class IntervalCoverageSender( val methodProbes = it.probes.values.copyOfRange(positions.first, positions.first + positions.second).toBitSet() if (!methodProbes.isEmpty) null else MethodCoverage( - classname = it.name, signature = signature, testId = it.testId, testSessionId = it.sessionId, diff --git a/test2code/src/main/kotlin/com/epam/drill/agent/test2code/coverage/Models.kt b/test2code/src/main/kotlin/com/epam/drill/agent/test2code/coverage/Models.kt index a3d9ea91..1f2a14ac 100644 --- a/test2code/src/main/kotlin/com/epam/drill/agent/test2code/coverage/Models.kt +++ b/test2code/src/main/kotlin/com/epam/drill/agent/test2code/coverage/Models.kt @@ -28,7 +28,6 @@ typealias ExecData = ConcurrentHashMap */ data class ExecDatum( val id: ClassId, - val name: String, val probes: AgentProbes, val sessionId: String, val testId: String, diff --git a/test2code/src/test/kotlin/com/epam/drill/agent/test2code/coverage/GlobalCoverageRecorderTest.kt b/test2code/src/test/kotlin/com/epam/drill/agent/test2code/coverage/GlobalCoverageRecorderTest.kt index b4d46025..b62b526d 100644 --- a/test2code/src/test/kotlin/com/epam/drill/agent/test2code/coverage/GlobalCoverageRecorderTest.kt +++ b/test2code/src/test/kotlin/com/epam/drill/agent/test2code/coverage/GlobalCoverageRecorderTest.kt @@ -89,7 +89,6 @@ class GlobalCoverageRecorderTest { private fun ContextCoverage.putProbes(classId: Long, vararg probes: Boolean) { execData[classId] = ExecDatum( id = classId, - name = "foo", sessionId = SESSION_CONTEXT_AMBIENT, probes = AgentProbes(initialSize = probes.size, values = booleanArrayOf(*probes)), testId = TEST_CONTEXT_NONE, From 9af1c44a97dc64c929dd8db3f33a64bc839a822e Mon Sep 17 00:00:00 2001 From: RomanDavlyatshin Date: Wed, 21 Jan 2026 16:35:02 +0400 Subject: [PATCH 7/8] build: set sharedLibsRef & remove commented code --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index 8cda8ed2..4d0d5639 100644 --- a/gradle.properties +++ b/gradle.properties @@ -21,7 +21,7 @@ apacheHttpClientVersion = 5.2.3 aesyDatasizeVersion = 1.0.0 bytebuddyVersion = 1.14.11 -sharedLibsRef = feature/properties-file-parse-multiline-values +sharedLibsRef = feat/send-coverage-data-per-method sharedLibsLocalPath = lib-jvm-shared nativeAgentLibName = drill-agent nativeAgentHookEnabled = false From f9875902a81d72e3ff55a783a712e6bc5f837d03 Mon Sep 17 00:00:00 2001 From: RomanDavlyatshin Date: Thu, 22 Jan 2026 20:34:31 +0400 Subject: [PATCH 8/8] feat: send commitSha & buildVersion with CoveragePayload --- .../com/epam/drill/agent/test2code/Test2Code.kt | 2 ++ .../drill/agent/test2code/coverage/CoverageSender.kt | 11 ++++++++++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/test2code/src/main/kotlin/com/epam/drill/agent/test2code/Test2Code.kt b/test2code/src/main/kotlin/com/epam/drill/agent/test2code/Test2Code.kt index 43b26b87..f9ebf777 100644 --- a/test2code/src/main/kotlin/com/epam/drill/agent/test2code/Test2Code.kt +++ b/test2code/src/main/kotlin/com/epam/drill/agent/test2code/Test2Code.kt @@ -60,6 +60,8 @@ class Test2Code( groupId = configuration.agentMetadata.groupId, appId = configuration.agentMetadata.appId, instanceId = configuration.agentMetadata.instanceId, + commitSha = configuration.agentMetadata.commitSha, + buildVersion = configuration.agentMetadata.buildVersion, intervalMs = configuration.parameters[Test2CodeParameterDefinitions.COVERAGE_SEND_INTERVAL], pageSize = configuration.parameters[Test2CodeParameterDefinitions.COVERAGE_SEND_PAGE_SIZE], sender = sender, diff --git a/test2code/src/main/kotlin/com/epam/drill/agent/test2code/coverage/CoverageSender.kt b/test2code/src/main/kotlin/com/epam/drill/agent/test2code/coverage/CoverageSender.kt index 27004cda..041679e9 100644 --- a/test2code/src/main/kotlin/com/epam/drill/agent/test2code/coverage/CoverageSender.kt +++ b/test2code/src/main/kotlin/com/epam/drill/agent/test2code/coverage/CoverageSender.kt @@ -33,6 +33,8 @@ interface CoverageSender { class IntervalCoverageSender( private val groupId: String, private val appId: String, + private val commitSha: String?, + private val buildVersion: String?, private val instanceId: String, private val intervalMs: Long, private val pageSize: Int, @@ -84,7 +86,14 @@ class IntervalCoverageSender( ) }} .chunked(pageSize) - .forEach { sender.send(destination, CoveragePayload(groupId, appId, instanceId, it), CoveragePayload.serializer()) } + .forEach { sender.send(destination, CoveragePayload( + groupId = groupId, + appId = appId, + instanceId = instanceId, + commitSha = commitSha, + buildVersion = buildVersion, + coverage = it + ), CoveragePayload.serializer()) } } }