From 1c1208be03a77baa4b326032d5666c80a7b775dd Mon Sep 17 00:00:00 2001 From: RomanDavlyatshin Date: Thu, 29 Jan 2026 21:51:32 +0400 Subject: [PATCH 1/2] feat: send method body_checksum in coverage --- .../drill/agent/test2code/classparsing/Ast.kt | 4 +--- .../agent/test2code/classparsing/Checksum.kt | 7 +++---- .../agent/test2code/coverage/CoverageManager.kt | 8 ++++---- .../agent/test2code/coverage/CoverageSender.kt | 11 ++++++++--- .../agent/test2code/coverage/Instrumentation.kt | 16 ++++++++++++---- .../drill/agent/test2code/coverage/Models.kt | 2 +- .../agent/test2code/coverage/ProbesProvider.kt | 9 ++++++++- 7 files changed, 37 insertions(+), 20 deletions(-) diff --git a/test2code/src/main/kotlin/com/epam/drill/agent/test2code/classparsing/Ast.kt b/test2code/src/main/kotlin/com/epam/drill/agent/test2code/classparsing/Ast.kt index b76f35b6..b8988121 100644 --- a/test2code/src/main/kotlin/com/epam/drill/agent/test2code/classparsing/Ast.kt +++ b/test2code/src/main/kotlin/com/epam/drill/agent/test2code/classparsing/Ast.kt @@ -131,9 +131,7 @@ fun parseAstClass(className: String, classBytes: ByteArray): List { } } -private fun AstMethod.classSignature() = - "${name}/${params}/${returnType}" - +private fun AstMethod.classSignature() = "${classname}:${name}:${params}:${returnType}" private fun getReturnType(methodNode: MethodNode): String { val returnTypeDesc: String = Type.getReturnType(methodNode.desc).descriptor diff --git a/test2code/src/main/kotlin/com/epam/drill/agent/test2code/classparsing/Checksum.kt b/test2code/src/main/kotlin/com/epam/drill/agent/test2code/classparsing/Checksum.kt index f8375874..e182fc9f 100644 --- a/test2code/src/main/kotlin/com/epam/drill/agent/test2code/classparsing/Checksum.kt +++ b/test2code/src/main/kotlin/com/epam/drill/agent/test2code/classparsing/Checksum.kt @@ -24,7 +24,7 @@ import java.io.ByteArrayInputStream val logger = KotlinLogging.logger { } -internal fun calculateMethodsChecksums( +fun calculateMethodsChecksums( classBytes: ByteArray, className: String ): Map = ClassParser(ByteArrayInputStream(classBytes), className) @@ -32,12 +32,11 @@ internal fun calculateMethodsChecksums( .methods // Filter needed for skipping interfaces, which have no opcodes for calculating checksum .filter { it.code != null } - .map { method -> method.classSignature() to calculateChecksum(method, className) } + .map { method -> method.classSignature(className) to calculateChecksum(method, className) } .filter { it.second != "" } .associate { it.first to it.second } -fun Method.classSignature() = - "${name}/${argumentTypes.asSequence().map { type -> type.toString() }.joinToString(separator = ",")}/${returnType}" +fun Method.classSignature(className: String) = "${className}:${name}:${argumentTypes.asSequence().map { type -> type.toString() }.joinToString(separator = ",")}:${returnType}" private fun calculateChecksum( method: Method, 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 95c9116b..78b0a722 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 @@ -24,7 +24,7 @@ open class CoverageManager( ) : IProbesProxy, ICoverageRecorder by threadCoverageRecorder { - private val classProbePositions: ConcurrentHashMap>> = ConcurrentHashMap() + private val classMethodsMetadata: ConcurrentHashMap = ConcurrentHashMap() override fun invoke( id: Long, @@ -40,14 +40,14 @@ open class CoverageManager( probes = AgentProbes(probeCount), sessionId = coverage.context.sessionId, testId = coverage.context.testId, - probePositions = classProbePositions[id]!! // TODO do not throw + methodsMetadata = classMethodsMetadata[id]!! // TODO do not throw ) } return execDatum.probes } - override fun addProbePositions(classId: Long, probePositions: Map>) { - classProbePositions[classId] = probePositions + override fun addClassMethodsMetadata(classId: Long, methodsMetadata: ClassMethodsMetadata) { + classMethodsMetadata[classId] = methodsMetadata } override fun pollRecorded(): Sequence { 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 041679e9..31ad6c45 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 @@ -75,11 +75,16 @@ class IntervalCoverageSender( */ private fun sendProbes(dataToSend: Sequence) { dataToSend - .flatMap { it.probePositions.mapNotNull { (signature, positions) -> - val methodProbes = it.probes.values.copyOfRange(positions.first, positions.first + positions.second).toBitSet() - if (!methodProbes.isEmpty) null + .flatMap { it.methodsMetadata.mapNotNull { (signature, metadata) -> + val methodProbes = it.probes.values.copyOfRange( + metadata.probesStartPos, + metadata.probesStartPos + metadata.probesCount + ).toBitSet() + + if (methodProbes.isEmpty) null else MethodCoverage( signature = signature, + bodyChecksum = metadata.bodyChecksum, testId = it.testId, testSessionId = it.sessionId, probes = methodProbes 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 ea5c8114..59bec08e 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 @@ -22,7 +22,7 @@ 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 com.epam.drill.agent.test2code.classparsing.calculateMethodsChecksums import org.jacoco.core.internal.data.CRC64 import org.jacoco.core.internal.flow.* import org.jacoco.core.internal.instr.* @@ -47,9 +47,17 @@ class DrillInstrumenter( 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.probesStartPos, m.probesCount) - }) + val bodyChecksums = calculateMethodsChecksums(initialBytes, className) + val classMethodsMetadata: ClassMethodsMetadata = counter.methods.associate { m -> + val signature = "${m.classname}:${m.name}:${m.params}:${m.returnType}" + signature to ClassMethodMetadata( + probesStartPos = m.probesStartPos, + probesCount = m.probesCount, + bodyChecksum = bodyChecksums[signature] ?: "" // interface methods don't have a body + ) + } + + probesProxy.addClassMethodsMetadata(classId, classMethodsMetadata) val genId = classCounter.incrementAndGet() val probeCount = counter.count 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 1f2a14ac..e646752d 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 @@ -31,5 +31,5 @@ data class ExecDatum( val probes: AgentProbes, val sessionId: String, val testId: String, - val probePositions: Map> + val methodsMetadata: ClassMethodsMetadata ) 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 7d056a94..ebefebba 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 @@ -24,9 +24,16 @@ import com.epam.drill.agent.jacoco.AgentProbes interface IProbesProxy { fun invoke(id: ClassId, num: Int, name: String, probeCount: Int): AgentProbes - fun addProbePositions(classId: Long, probePositions: Map>) + fun addClassMethodsMetadata(classId: Long, methodsMetadata: ClassMethodsMetadata) } +typealias ClassMethodsMetadata = Map +data class ClassMethodMetadata( + val probesStartPos: Int, + val probesCount: Int, + val bodyChecksum: String +) + const val SESSION_CONTEXT_NONE = "SESSION_CONTEXT_NONE" const val TEST_CONTEXT_NONE = "TEST_CONTEXT_NONE" const val SESSION_CONTEXT_AMBIENT = "GLOBAL" From df35f73454775871a3db928127ee9275f3a8433d Mon Sep 17 00:00:00 2001 From: RomanDavlyatshin Date: Tue, 3 Feb 2026 18:14:23 +0400 Subject: [PATCH 2/2] feat: move classMethodsMetadata from execdatum to coverageSender --- .../kotlin/com/epam/drill/agent/test2code/Test2Code.kt | 3 ++- .../epam/drill/agent/test2code/coverage/CoverageManager.kt | 5 ++--- .../epam/drill/agent/test2code/coverage/CoverageSender.kt | 7 +++++-- .../com/epam/drill/agent/test2code/coverage/Models.kt | 3 +-- 4 files changed, 10 insertions(+), 8 deletions(-) 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 f9ebf777..eda52523 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 @@ -66,7 +66,8 @@ class Test2Code( pageSize = configuration.parameters[Test2CodeParameterDefinitions.COVERAGE_SEND_PAGE_SIZE], sender = sender, collectReleasedProbes = { coverageManager.pollRecorded() }, - collectUnreleasedProbes = { coverageManager.getUnreleased() } + collectUnreleasedProbes = { coverageManager.getUnreleased() }, + classMethodsMetadata = coverageManager.classMethodsMetadata ) private val coverageCollectionEnabled = configuration.parameters[COVERAGE_COLLECTION_ENABLED] private val classScanningEnabled = configuration.parameters[Test2CodeParameterDefinitions.CLASS_SCANNING_ENABLED] 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 78b0a722..3d1dc810 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 @@ -24,7 +24,7 @@ open class CoverageManager( ) : IProbesProxy, ICoverageRecorder by threadCoverageRecorder { - private val classMethodsMetadata: ConcurrentHashMap = ConcurrentHashMap() + val classMethodsMetadata: ConcurrentHashMap = ConcurrentHashMap() override fun invoke( id: Long, @@ -39,8 +39,7 @@ open class CoverageManager( id = id, probes = AgentProbes(probeCount), sessionId = coverage.context.sessionId, - testId = coverage.context.testId, - methodsMetadata = classMethodsMetadata[id]!! // TODO do not throw + testId = coverage.context.testId ) } return execDatum.probes 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 31ad6c45..0e6ff035 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 @@ -24,6 +24,7 @@ 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 +import java.util.concurrent.ConcurrentHashMap interface CoverageSender { fun startSendingCoverage() @@ -40,7 +41,8 @@ class IntervalCoverageSender( private val pageSize: Int, private val sender: AgentMessageSender = StubSender(), private val collectReleasedProbes: () -> Sequence = { emptySequence() }, - private val collectUnreleasedProbes: () -> Sequence = { emptySequence() } + private val collectUnreleasedProbes: () -> Sequence = { emptySequence() }, + private val classMethodsMetadata: ConcurrentHashMap ) : CoverageSender { private val scheduledThreadPool = Executors.newSingleThreadScheduledExecutor() private val destination = AgentMessageDestination("POST", "coverage") @@ -75,7 +77,8 @@ class IntervalCoverageSender( */ private fun sendProbes(dataToSend: Sequence) { dataToSend - .flatMap { it.methodsMetadata.mapNotNull { (signature, metadata) -> + .flatMap { + classMethodsMetadata[it.id]!!.mapNotNull { (signature, metadata) -> val methodProbes = it.probes.values.copyOfRange( metadata.probesStartPos, metadata.probesStartPos + metadata.probesCount 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 e646752d..9ec545df 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 @@ -30,6 +30,5 @@ data class ExecDatum( val id: ClassId, val probes: AgentProbes, val sessionId: String, - val testId: String, - val methodsMetadata: ClassMethodsMetadata + val testId: String )