From 2fac09ccaa76112933d731e6db0348a6a8b6c0b6 Mon Sep 17 00:00:00 2001 From: iryabov Date: Tue, 16 Dec 2025 09:56:30 +0100 Subject: [PATCH 1/2] fix: replace ThreadLocal with TransmittableThreadLocal in ThreadCoverageRecorder EPMDJ-11175 --- test2code/build.gradle.kts | 1 + .../drill/agent/test2code/coverage/ThreadCoverageRecorder.kt | 5 +++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/test2code/build.gradle.kts b/test2code/build.gradle.kts index 8e8cfd87..8d4de397 100644 --- a/test2code/build.gradle.kts +++ b/test2code/build.gradle.kts @@ -45,6 +45,7 @@ dependencies { implementation(project(":test2code-common")) implementation(project(":test2code-jacoco")) implementation(project(":konform")) + implementation(project(":transmittable-thread-local")) testImplementation(kotlin("test-junit")) } diff --git a/test2code/src/main/kotlin/com/epam/drill/agent/test2code/coverage/ThreadCoverageRecorder.kt b/test2code/src/main/kotlin/com/epam/drill/agent/test2code/coverage/ThreadCoverageRecorder.kt index 21e26527..435e44f5 100644 --- a/test2code/src/main/kotlin/com/epam/drill/agent/test2code/coverage/ThreadCoverageRecorder.kt +++ b/test2code/src/main/kotlin/com/epam/drill/agent/test2code/coverage/ThreadCoverageRecorder.kt @@ -15,14 +15,15 @@ */ package com.epam.drill.agent.test2code.coverage +import com.epam.drill.agent.ttl.TransmittableThreadLocal import mu.KotlinLogging class ThreadCoverageRecorder( private val execDataPool: DataPool = ConcurrentDataPool() ) : ICoverageRecorder { private val logger = KotlinLogging.logger {} - private val context: ThreadLocal = ThreadLocal() - private val execData: ThreadLocal = ThreadLocal() + private val context: ThreadLocal = TransmittableThreadLocal() + private val execData: ThreadLocal = TransmittableThreadLocal() override fun startRecording(sessionId: String?, testId: String?) { stopRecording(context.get()?.sessionId, context.get()?.testId) From 149caa7383c05da47163001c11cb0bd91ad391b1 Mon Sep 17 00:00:00 2001 From: iryabov Date: Tue, 16 Dec 2025 17:25:42 +0100 Subject: [PATCH 2/2] feat: add support for collecting unreleased probes in coverage recording EPMDJ-11175 --- .../kotlin/com/epam/drill/agent/test2code/Test2Code.kt | 3 ++- .../drill/agent/test2code/coverage/CoverageRecorder.kt | 1 + .../drill/agent/test2code/coverage/CoverageSender.kt | 8 +++++--- .../epam/drill/agent/test2code/coverage/DataPool.kt | 10 ++++++++++ .../agent/test2code/coverage/ThreadCoverageRecorder.kt | 6 ++++++ 5 files changed, 24 insertions(+), 4 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 6d014679..43b26b87 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 @@ -63,7 +63,8 @@ class Test2Code( intervalMs = configuration.parameters[Test2CodeParameterDefinitions.COVERAGE_SEND_INTERVAL], pageSize = configuration.parameters[Test2CodeParameterDefinitions.COVERAGE_SEND_PAGE_SIZE], sender = sender, - collectProbes = { coverageManager.pollRecorded() } + collectReleasedProbes = { coverageManager.pollRecorded() }, + collectUnreleasedProbes = { coverageManager.getUnreleased() } ) 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/CoverageRecorder.kt b/test2code/src/main/kotlin/com/epam/drill/agent/test2code/coverage/CoverageRecorder.kt index 50d54022..d7a49e21 100644 --- a/test2code/src/main/kotlin/com/epam/drill/agent/test2code/coverage/CoverageRecorder.kt +++ b/test2code/src/main/kotlin/com/epam/drill/agent/test2code/coverage/CoverageRecorder.kt @@ -20,6 +20,7 @@ interface ICoverageRecorder { fun stopRecording(sessionId: String?, testId: String?) fun getContext(): ContextCoverage? fun pollRecorded(): Sequence + fun getUnreleased(): Sequence = emptySequence() } data class ContextCoverage(val context: ContextKey, val execData: ExecData) \ No newline at end of file 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 2715a4a0..1c9e61ae 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 @@ -37,7 +37,8 @@ class IntervalCoverageSender( private val intervalMs: Long, private val pageSize: Int, private val sender: AgentMessageSender = StubSender(), - private val collectProbes: () -> Sequence = { emptySequence() } + private val collectReleasedProbes: () -> Sequence = { emptySequence() }, + private val collectUnreleasedProbes: () -> Sequence = { emptySequence() } ) : CoverageSender { private val scheduledThreadPool = Executors.newSingleThreadScheduledExecutor() private val destination = AgentMessageDestination("POST", "coverage") @@ -45,7 +46,7 @@ class IntervalCoverageSender( override fun startSendingCoverage() { scheduledThreadPool.scheduleAtFixedRate( - Runnable { sendProbes(collectProbes()) }, + Runnable { sendProbes(collectReleasedProbes()) }, 0, intervalMs, TimeUnit.MILLISECONDS @@ -59,7 +60,8 @@ class IntervalCoverageSender( logger.error("Failed to send some coverage data prior to shutdown") scheduledThreadPool.shutdownNow(); } - sendProbes(collectProbes()) + sendProbes(collectReleasedProbes()) + sendProbes(collectUnreleasedProbes()) sender.shutdown() logger.info { "Coverage sending job is stopped." } } diff --git a/test2code/src/main/kotlin/com/epam/drill/agent/test2code/coverage/DataPool.kt b/test2code/src/main/kotlin/com/epam/drill/agent/test2code/coverage/DataPool.kt index 498094b1..8b52bbba 100644 --- a/test2code/src/main/kotlin/com/epam/drill/agent/test2code/coverage/DataPool.kt +++ b/test2code/src/main/kotlin/com/epam/drill/agent/test2code/coverage/DataPool.kt @@ -39,6 +39,12 @@ interface DataPool { */ fun get(key: K): V? + /** + * Get all objects from the pool + * @return a map of all objects in the pool + */ + fun getAll(): Map + /** * Release the used object by key and move it into the released queue * @param key a key @@ -74,6 +80,10 @@ class ConcurrentDataPool : DataPool { return dataMap[key] } + override fun getAll(): Map { + return dataMap.toMap() + } + override fun pollReleased(): Sequence { return generateSequence { released.poll() } } diff --git a/test2code/src/main/kotlin/com/epam/drill/agent/test2code/coverage/ThreadCoverageRecorder.kt b/test2code/src/main/kotlin/com/epam/drill/agent/test2code/coverage/ThreadCoverageRecorder.kt index 435e44f5..85e982b7 100644 --- a/test2code/src/main/kotlin/com/epam/drill/agent/test2code/coverage/ThreadCoverageRecorder.kt +++ b/test2code/src/main/kotlin/com/epam/drill/agent/test2code/coverage/ThreadCoverageRecorder.kt @@ -17,6 +17,8 @@ package com.epam.drill.agent.test2code.coverage import com.epam.drill.agent.ttl.TransmittableThreadLocal import mu.KotlinLogging +import kotlin.sequences.filter +import kotlin.sequences.flatMap class ThreadCoverageRecorder( private val execDataPool: DataPool = ConcurrentDataPool() @@ -51,6 +53,10 @@ class ThreadCoverageRecorder( .filter { it.probes.containCovered() } } + override fun getUnreleased(): Sequence { + return execDataPool.getAll().values.flatMap { it.values }.filter { it.probes.containCovered() }.asSequence() + } + override fun getContext(): ContextCoverage? { return context.get()?.let { ctx -> execData.get()?.let { ContextCoverage(ctx, it) } } }