From a38aee54d53d567e970180d7ed60fb5530d9f931 Mon Sep 17 00:00:00 2001 From: Alexey Kuznetsov Date: Wed, 10 Sep 2025 09:12:15 -0400 Subject: [PATCH 1/5] Update Spock to 2.4-M6 to fix deadlock issues on CI. --- .../spring-webflux/spring-webflux-6.0/build.gradle | 12 ++++-------- gradle/libs.versions.toml | 9 ++------- 2 files changed, 6 insertions(+), 15 deletions(-) diff --git a/dd-java-agent/instrumentation/spring/spring-webflux/spring-webflux-6.0/build.gradle b/dd-java-agent/instrumentation/spring/spring-webflux/spring-webflux-6.0/build.gradle index f94e7c6b287..d4cbd5d4dba 100644 --- a/dd-java-agent/instrumentation/spring/spring-webflux/spring-webflux-6.0/build.gradle +++ b/dd-java-agent/instrumentation/spring/spring-webflux/spring-webflux-6.0/build.gradle @@ -35,8 +35,8 @@ dependencies { testImplementation project(':dd-java-agent:instrumentation:reactor-core-3.1') testImplementation project(':dd-java-agent:instrumentation:reactive-streams') testImplementation project(':dd-java-agent:instrumentation:reactor-netty-1') - testImplementation group: 'org.spockframework', name: 'spock-spring', version: '1.1-groovy-2.4' + testImplementation libs.spock.spring testImplementation group: 'org.springframework', name: 'spring-webflux', version: '6.0.0' testImplementation group: 'io.projectreactor.netty', name: 'reactor-netty', version: '1.1.3' testImplementation group: 'org.springframework', name: 'spring-test', version: '6.0.0' @@ -52,8 +52,7 @@ dependencies { exclude group: 'org.hamcrest', module: 'hamcrest-library' } bootTestImplementation project(':dd-java-agent:instrumentation:spring:spring-webflux:spring-webflux-5.0') - // our default version of spock is too old - bootTestImplementation libs.bundles.spock24.spring + bootTestImplementation libs.spock.spring latestDepBootTestImplementation group: 'org.springframework.boot', name: 'spring-boot-starter-webflux', version: '3.+' latestDepBootTestImplementation group: 'org.springframework.boot', name: 'spring-boot-starter-test', version: '3.+', { @@ -61,8 +60,7 @@ dependencies { exclude group: 'org.hamcrest', module: 'hamcrest-library' } latestDepBootTestImplementation project(':dd-java-agent:instrumentation:spring:spring-webflux:spring-webflux-5.0') - // our default version of spock is too old - latestDepBootTestImplementation libs.bundles.spock24.spring + latestDepBootTestImplementation libs.spock.spring iastTestImplementation group: 'org.springframework.boot', name: 'spring-boot-starter-reactor-netty', version: '3.0.0' iastTestImplementation group: 'org.springframework.boot', name: 'spring-boot-starter-json', version: '3.0.0' @@ -78,8 +76,6 @@ dependencies { } iastTestImplementation group: 'org.springframework.boot', name: 'spring-boot-starter-reactor-netty', version: '3.0.0' iastTestImplementation group: 'org.springframework.boot', name: 'spring-boot-starter-json', version: '3.0.0' - - // our default version of spock is too old - iastTestImplementation libs.bundles.spock24.spring + iastTestImplementation libs.spock.spring } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index a21e3a1ab5c..82db45bcce9 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -6,8 +6,7 @@ okhttp = "3.12.15" # Datadog fork to support Java 7 okhttp-legacy = "[3.0,3.12.12]" # 3.12.x is last version to support Java7 okio = "1.17.6" # Datadog fork -spock = "2.3-groovy-3.0" -spock24 = "2.4-M6-groovy-3.0" +spock = "2.4-M6-groovy-3.0" groovy = "3.0.24" junit5 = "5.9.2" junit-platform = "1.9.2" @@ -65,12 +64,9 @@ aircompressor = { module = 'io.airlift:aircompressor', version = '2.0.2'} # Testing spock-core = { module = "org.spockframework:spock-core", version.ref = "spock" } spock-junit4 = { module = "org.spockframework:spock-junit4", version.ref = "spock" } +spock-spring = { module = "org.spockframework:spock-spring", version.ref = "spock" } objenesis = { module = "org.objenesis:objenesis", version = "3.3" } # Used by Spock for mocking: -spock24-core = { module = "org.spockframework:spock-core", version.ref = "spock24" } -spock24-junit4 = { module = "org.spockframework:spock-junit4", version.ref = "spock24" } -spock24-spring = { module = "org.spockframework:spock-spring", version.ref = "spock24" } - groovy = { module = "org.codehaus.groovy:groovy-all", version.ref = "groovy" } groovy-yaml = { module = "org.codehaus.groovy:groovy-yaml", version.ref = "groovy" } junit-jupiter = { module = "org.junit.jupiter:junit-jupiter", version.ref = "junit5" } @@ -109,7 +105,6 @@ asm = ["asm", "asmcommons"] cafe-crypto = ["cafe-crypto-curve25519", "cafe-crypto-ed25519"] # Testing spock = ["spock-core", "spock-junit4", "objenesis"] -spock24-spring = ["spock24-core", "spock24-junit4", "spock24-spring"] junit5 = ["junit-jupiter", "junit-jupiter-params"] junit-platform = ["junit-platform-launcher"] mockito = ["mokito-core", "mokito-junit-jupiter", "byte-buddy", "byte-buddy-agent"] From 2395beb69f7ab93575d3794d3885ec6fc04df025 Mon Sep 17 00:00:00 2001 From: Nikita Tkachenko Date: Thu, 11 Sep 2025 19:06:36 +0200 Subject: [PATCH 2/5] Update JUnit and Mockito versions to be compatible with Spock 2.4 --- dd-java-agent/instrumentation-testing/build.gradle | 2 +- dd-java-agent/instrumentation/cucumber/build.gradle | 6 +++--- dd-java-agent/instrumentation/junit-5.3/build.gradle | 12 ++++++------ .../junit-5.3/cucumber-junit-5/build.gradle | 10 +++++----- .../instrumentation/junit-5.3/junit-5.8/build.gradle | 12 ++++++------ .../junit-5.3/spock-junit-5/build.gradle | 7 +++---- dd-java-agent/instrumentation/karate/build.gradle | 4 ++-- dd-java-agent/instrumentation/selenium/build.gradle | 4 ++-- dd-java-agent/testing/build.gradle | 2 +- gradle/java_deps.gradle | 4 +++- gradle/libs.versions.toml | 4 ++-- 11 files changed, 34 insertions(+), 33 deletions(-) diff --git a/dd-java-agent/instrumentation-testing/build.gradle b/dd-java-agent/instrumentation-testing/build.gradle index 3fb86ac3de3..d4b3e247854 100644 --- a/dd-java-agent/instrumentation-testing/build.gradle +++ b/dd-java-agent/instrumentation-testing/build.gradle @@ -12,7 +12,7 @@ dependencies { implementation project(':dd-java-agent:agent-debugger') - implementation 'org.junit.platform:junit-platform-runner:1.9.0' + implementation "org.junit.platform:junit-platform-runner:${libs.versions.junit.platform.get()}" testImplementation project(':dd-java-agent:instrumentation:trace-annotation') diff --git a/dd-java-agent/instrumentation/cucumber/build.gradle b/dd-java-agent/instrumentation/cucumber/build.gradle index 0f8f883d533..21ae5de3546 100644 --- a/dd-java-agent/instrumentation/cucumber/build.gradle +++ b/dd-java-agent/instrumentation/cucumber/build.gradle @@ -13,9 +13,9 @@ addTestSuiteForDir('latestDepTest', 'test') dependencies { compileOnly group: 'io.cucumber', name: 'cucumber-core', version: '5.4.0' - testImplementation group: 'org.junit.platform', name: 'junit-platform-launcher', version: '1.9.2' - testImplementation group: 'org.junit.platform', name: 'junit-platform-suite', version: '1.9.2' - testImplementation group: 'org.junit.platform', name: 'junit-platform-suite-engine', version: '1.9.2' + testImplementation group: 'org.junit.platform', name: 'junit-platform-launcher', version: libs.versions.junit.platform.get() + testImplementation group: 'org.junit.platform', name: 'junit-platform-suite', version: libs.versions.junit.platform.get() + testImplementation group: 'org.junit.platform', name: 'junit-platform-suite-engine', version: libs.versions.junit.platform.get() testImplementation group: 'io.cucumber', name: 'cucumber-junit-platform-engine', version: '5.4.0' testImplementation group: 'io.cucumber', name: 'cucumber-java', version: '5.4.0' diff --git a/dd-java-agent/instrumentation/junit-5.3/build.gradle b/dd-java-agent/instrumentation/junit-5.3/build.gradle index cc51544c614..83733009046 100644 --- a/dd-java-agent/instrumentation/junit-5.3/build.gradle +++ b/dd-java-agent/instrumentation/junit-5.3/build.gradle @@ -32,9 +32,9 @@ dependencies { // versions used below are not the minimum ones that we support, // but the tests need to use them in order to be compliant with Spock 2.x - testImplementation group: 'org.junit.platform', name: 'junit-platform-launcher', version: '1.12.0' - testImplementation group: 'org.junit.jupiter', name: 'junit-jupiter-engine', version: '5.12.0' - testImplementation group: 'org.junit.jupiter', name: 'junit-jupiter-params', version: '5.12.0' + testImplementation group: 'org.junit.platform', name: 'junit-platform-launcher', version: libs.versions.junit.platform.get() + testImplementation group: 'org.junit.jupiter', name: 'junit-jupiter-engine', version: libs.versions.junit5.get() + testImplementation group: 'org.junit.jupiter', name: 'junit-jupiter-params', version: libs.versions.junit5.get() latestDepTestImplementation group: 'org.junit.platform', name: 'junit-platform-launcher', version: '+' latestDepTestImplementation group: 'org.junit.jupiter', name: 'junit-jupiter-engine', version: '+' @@ -43,8 +43,8 @@ dependencies { configurations.matching({ it.name.startsWith('test') }).each({ it.resolutionStrategy { - force group: 'org.junit.platform', name: 'junit-platform-launcher', version: '1.12.0' - force group: 'org.junit.jupiter', name: 'junit-jupiter-engine', version: '5.12.0' - force group: 'org.junit.jupiter', name: 'junit-jupiter-params', version: '5.12.0' + force group: 'org.junit.platform', name: 'junit-platform-launcher', version: libs.versions.junit.platform.get() + force group: 'org.junit.jupiter', name: 'junit-jupiter-engine', version: libs.versions.junit5.get() + force group: 'org.junit.jupiter', name: 'junit-jupiter-params', version: libs.versions.junit5.get() } }) diff --git a/dd-java-agent/instrumentation/junit-5.3/cucumber-junit-5/build.gradle b/dd-java-agent/instrumentation/junit-5.3/cucumber-junit-5/build.gradle index 85b190534fa..0a54591717f 100644 --- a/dd-java-agent/instrumentation/junit-5.3/cucumber-junit-5/build.gradle +++ b/dd-java-agent/instrumentation/junit-5.3/cucumber-junit-5/build.gradle @@ -19,9 +19,9 @@ dependencies { testImplementation project(':dd-java-agent:agent-ci-visibility:civisibility-instrumentation-test-fixtures') - testImplementation group: 'org.junit.platform', name: 'junit-platform-launcher', version: '1.12.0' - testImplementation group: 'org.junit.platform', name: 'junit-platform-suite', version: '1.12.0' - testImplementation group: 'org.junit.platform', name: 'junit-platform-suite-engine', version: '1.12.0' + testImplementation group: 'org.junit.platform', name: 'junit-platform-launcher', version: libs.versions.junit.platform.get() + testImplementation group: 'org.junit.platform', name: 'junit-platform-suite', version: libs.versions.junit.platform.get() + testImplementation group: 'org.junit.platform', name: 'junit-platform-suite-engine', version: libs.versions.junit.platform.get() testImplementation group: 'io.cucumber', name: 'cucumber-junit-platform-engine', version: '5.4.0' testImplementation group: 'io.cucumber', name: 'cucumber-java', version: '5.4.0' @@ -33,7 +33,7 @@ dependencies { configurations.matching({ it.name.startsWith('test') }).each({ it.resolutionStrategy { - force group: 'org.junit.platform', name: 'junit-platform-launcher', version: '1.12.0' - force group: 'org.junit.platform', name: 'junit-platform-suite', version: '1.12.0' + force group: 'org.junit.platform', name: 'junit-platform-launcher', version: libs.versions.junit.platform.get() + force group: 'org.junit.platform', name: 'junit-platform-suite', version: libs.versions.junit.platform.get() } }) diff --git a/dd-java-agent/instrumentation/junit-5.3/junit-5.8/build.gradle b/dd-java-agent/instrumentation/junit-5.3/junit-5.8/build.gradle index 340f04855ec..bfb22eee89d 100644 --- a/dd-java-agent/instrumentation/junit-5.3/junit-5.8/build.gradle +++ b/dd-java-agent/instrumentation/junit-5.3/junit-5.8/build.gradle @@ -36,9 +36,9 @@ dependencies { // versions used below are not the minimum ones that we support, // but the tests need to use them in order to be compliant with Spock 2.x - testImplementation group: 'org.junit.platform', name: 'junit-platform-launcher', version: '1.12.0' - testImplementation group: 'org.junit.jupiter', name: 'junit-jupiter-engine', version: '5.12.0' - testImplementation group: 'org.junit.jupiter', name: 'junit-jupiter-params', version: '5.12.0' + testImplementation group: 'org.junit.platform', name: 'junit-platform-launcher', version: libs.versions.junit.platform.get() + testImplementation group: 'org.junit.jupiter', name: 'junit-jupiter-engine', version: libs.versions.junit5.get() + testImplementation group: 'org.junit.jupiter', name: 'junit-jupiter-params', version: libs.versions.junit5.get() latestDepTestImplementation group: 'org.junit.platform', name: 'junit-platform-launcher', version: '+' latestDepTestImplementation group: 'org.junit.jupiter', name: 'junit-jupiter-engine', version: '+' @@ -47,8 +47,8 @@ dependencies { configurations.matching({ it.name.startsWith('test') }).each({ it.resolutionStrategy { - force group: 'org.junit.platform', name: 'junit-platform-launcher', version: '1.12.0' - force group: 'org.junit.jupiter', name: 'junit-jupiter-engine', version: '5.12.0' - force group: 'org.junit.jupiter', name: 'junit-jupiter-params', version: '5.12.0' + force group: 'org.junit.platform', name: 'junit-platform-launcher', version: libs.versions.junit.platform.get() + force group: 'org.junit.jupiter', name: 'junit-jupiter-engine', version: libs.versions.junit5.get() + force group: 'org.junit.jupiter', name: 'junit-jupiter-params', version: libs.versions.junit5.get() } }) diff --git a/dd-java-agent/instrumentation/junit-5.3/spock-junit-5/build.gradle b/dd-java-agent/instrumentation/junit-5.3/spock-junit-5/build.gradle index 34abb9356f6..a63d01f4ffa 100644 --- a/dd-java-agent/instrumentation/junit-5.3/spock-junit-5/build.gradle +++ b/dd-java-agent/instrumentation/junit-5.3/spock-junit-5/build.gradle @@ -22,16 +22,15 @@ dependencies { testImplementation project(':dd-java-agent:agent-ci-visibility:civisibility-instrumentation-test-fixtures') - testImplementation group: 'org.junit.platform', name: 'junit-platform-launcher', version: '1.12.0' + testImplementation group: 'org.junit.platform', name: 'junit-platform-launcher', version: libs.versions.junit.platform.get() testImplementation group: 'org.spockframework', name: 'spock-core', version: "2.2-groovy-${spockGroovyVersion}" - // Broken from 2.4: https://datadoghq.atlassian.net/browse/AIDM-163 - latestDepTestImplementation group: 'org.spockframework', name: 'spock-core', version: '2.3+' + latestDepTestImplementation group: 'org.spockframework', name: 'spock-core', version: '2.+' } configurations.matching({ it.name.startsWith('test') }).each({ it.resolutionStrategy { - force group: 'org.junit.platform', name: 'junit-platform-launcher', version: '1.12.0' + force group: 'org.junit.platform', name: 'junit-platform-launcher', version: libs.versions.junit.platform.get() force group: 'org.spockframework', name: 'spock-core', version: "2.2-groovy-${spockGroovyVersion}" } }) diff --git a/dd-java-agent/instrumentation/karate/build.gradle b/dd-java-agent/instrumentation/karate/build.gradle index f5146cfc221..3db28f83d34 100644 --- a/dd-java-agent/instrumentation/karate/build.gradle +++ b/dd-java-agent/instrumentation/karate/build.gradle @@ -28,8 +28,8 @@ dependencies { compileOnly group: 'com.intuit.karate', name: 'karate-core', version: '1.0.0' testImplementation project(':dd-java-agent:agent-ci-visibility:civisibility-instrumentation-test-fixtures') - testImplementation group: 'org.junit.platform', name: 'junit-platform-launcher', version: '1.8.2' - testImplementation group: 'org.junit.jupiter', name: 'junit-jupiter-engine', version: '5.8.2' + testImplementation group: 'org.junit.platform', name: 'junit-platform-launcher', version: libs.versions.junit.platform.get() + testImplementation group: 'org.junit.jupiter', name: 'junit-jupiter-engine', version: libs.versions.junit5.get() testImplementation (group: 'com.intuit.karate', name: 'karate-core', version: '1.0.0') { // excluding logback to avoid conflicts with libs.bundles.test.logging diff --git a/dd-java-agent/instrumentation/selenium/build.gradle b/dd-java-agent/instrumentation/selenium/build.gradle index 3ef8f505710..80f45dcf91e 100644 --- a/dd-java-agent/instrumentation/selenium/build.gradle +++ b/dd-java-agent/instrumentation/selenium/build.gradle @@ -25,8 +25,8 @@ dependencies { testFixturesApi project(':dd-java-agent:agent-ci-visibility:civisibility-instrumentation-test-fixtures') testFixturesApi project(':dd-java-agent:instrumentation:junit-5.3') - testFixturesApi group: 'org.junit.platform', name: 'junit-platform-launcher', version: '1.8.2' - testFixturesApi group: 'org.junit.jupiter', name: 'junit-jupiter-engine', version: '5.8.2' + testFixturesApi group: 'org.junit.platform', name: 'junit-platform-launcher', version: libs.versions.junit.platform.get() + testFixturesApi group: 'org.junit.jupiter', name: 'junit-jupiter-engine', version: libs.versions.junit5.get() testImplementation group: 'org.seleniumhq.selenium', name: 'selenium-java', version: '3.141.59' testImplementation group: 'org.seleniumhq.selenium', name: 'htmlunit-driver', version: '2.70.0' diff --git a/dd-java-agent/testing/build.gradle b/dd-java-agent/testing/build.gradle index 51e299ac65e..f105724a93b 100644 --- a/dd-java-agent/testing/build.gradle +++ b/dd-java-agent/testing/build.gradle @@ -49,7 +49,7 @@ dependencies { api libs.groovy - implementation 'org.junit.platform:junit-platform-runner:1.9.0' + implementation "org.junit.platform:junit-platform-runner:${libs.versions.junit.platform.get()}" implementation project(':dd-java-agent:appsec') } diff --git a/gradle/java_deps.gradle b/gradle/java_deps.gradle index e09833384d5..69391241807 100644 --- a/gradle/java_deps.gradle +++ b/gradle/java_deps.gradle @@ -5,6 +5,8 @@ dependencies { testImplementation libs.groovy testImplementation libs.bundles.test.logging - // Required to update dependency lock files + // JUnit and Mockito compatible with Spock 2.4.0 + testRuntimeOnly libs.bundles.junit5 testRuntimeOnly libs.bundles.junit.platform + testRuntimeOnly libs.mokito.core } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 82db45bcce9..828a167a373 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -8,8 +8,8 @@ okio = "1.17.6" # Datadog fork spock = "2.4-M6-groovy-3.0" groovy = "3.0.24" -junit5 = "5.9.2" -junit-platform = "1.9.2" +junit5 = "5.12.2" +junit-platform = "1.12.2" logback = "1.2.13" bytebuddy = "1.17.5" scala = "2.11.12" # Last version to support Java 7 (2.12+ require Java 8+) From bcea04f6834959930b111bd0240a056b95e2bda4 Mon Sep 17 00:00:00 2001 From: Nikita Tkachenko Date: Thu, 11 Sep 2025 19:08:17 +0200 Subject: [PATCH 3/5] Update JUnit dependencies pulled in by SpringBoot Gradle plugin to be compatible with Spock 2.4 --- dd-smoke-tests/apm-tracing-disabled/build.gradle | 1 + dd-smoke-tests/datastreams/kafkaschemaregistry/build.gradle | 1 + dd-smoke-tests/kafka-2/build.gradle | 1 + dd-smoke-tests/springboot-freemarker/build.gradle | 1 + dd-smoke-tests/springboot-java-11/build.gradle | 1 + dd-smoke-tests/springboot-java-17/build.gradle | 1 + dd-smoke-tests/springboot-jetty-jsp/build.gradle | 1 + dd-smoke-tests/springboot-jpa/build.gradle | 1 + dd-smoke-tests/springboot-thymeleaf/build.gradle | 1 + dd-smoke-tests/springboot-tomcat-jsp/build.gradle | 1 + dd-smoke-tests/springboot-tomcat/build.gradle | 2 +- dd-smoke-tests/springboot-velocity/build.gradle | 1 + gradle/spring-boot-plugin.gradle | 5 +++++ 13 files changed, 17 insertions(+), 1 deletion(-) create mode 100644 gradle/spring-boot-plugin.gradle diff --git a/dd-smoke-tests/apm-tracing-disabled/build.gradle b/dd-smoke-tests/apm-tracing-disabled/build.gradle index 6035c46a8b9..a10d460e3ae 100644 --- a/dd-smoke-tests/apm-tracing-disabled/build.gradle +++ b/dd-smoke-tests/apm-tracing-disabled/build.gradle @@ -6,6 +6,7 @@ plugins { } apply from: "$rootDir/gradle/java.gradle" +apply from: "$rootDir/gradle/spring-boot-plugin.gradle" description = 'ASM Standalone Billing Tests.' java { diff --git a/dd-smoke-tests/datastreams/kafkaschemaregistry/build.gradle b/dd-smoke-tests/datastreams/kafkaschemaregistry/build.gradle index 1ed41f3ae5e..eddec5fb03d 100644 --- a/dd-smoke-tests/datastreams/kafkaschemaregistry/build.gradle +++ b/dd-smoke-tests/datastreams/kafkaschemaregistry/build.gradle @@ -5,6 +5,7 @@ plugins { } apply from: "$rootDir/gradle/java.gradle" +apply from: "$rootDir/gradle/spring-boot-plugin.gradle" description = 'Kafka Smoke Tests.' jar { diff --git a/dd-smoke-tests/kafka-2/build.gradle b/dd-smoke-tests/kafka-2/build.gradle index 7afe3d79873..b4d926f905a 100644 --- a/dd-smoke-tests/kafka-2/build.gradle +++ b/dd-smoke-tests/kafka-2/build.gradle @@ -5,6 +5,7 @@ plugins { } apply from: "$rootDir/gradle/java.gradle" +apply from: "$rootDir/gradle/spring-boot-plugin.gradle" description = 'Kafka 2.x Smoke Tests.' dependencies { diff --git a/dd-smoke-tests/springboot-freemarker/build.gradle b/dd-smoke-tests/springboot-freemarker/build.gradle index e9e6d962c19..1b9e323f775 100644 --- a/dd-smoke-tests/springboot-freemarker/build.gradle +++ b/dd-smoke-tests/springboot-freemarker/build.gradle @@ -6,6 +6,7 @@ plugins { } apply from: "$rootDir/gradle/java.gradle" +apply from: "$rootDir/gradle/spring-boot-plugin.gradle" description = 'SpringBoot Freemarker Smoke Tests.' java { diff --git a/dd-smoke-tests/springboot-java-11/build.gradle b/dd-smoke-tests/springboot-java-11/build.gradle index a912b636b8e..a9594cd7c2e 100644 --- a/dd-smoke-tests/springboot-java-11/build.gradle +++ b/dd-smoke-tests/springboot-java-11/build.gradle @@ -10,6 +10,7 @@ ext { } apply from: "$rootDir/gradle/java.gradle" +apply from: "$rootDir/gradle/spring-boot-plugin.gradle" description = 'SpringBoot Java 11 Smoke Tests.' dependencies { diff --git a/dd-smoke-tests/springboot-java-17/build.gradle b/dd-smoke-tests/springboot-java-17/build.gradle index 810f9dcc170..3ab20c20da4 100644 --- a/dd-smoke-tests/springboot-java-17/build.gradle +++ b/dd-smoke-tests/springboot-java-17/build.gradle @@ -10,6 +10,7 @@ ext { } apply from: "$rootDir/gradle/java.gradle" +apply from: "$rootDir/gradle/spring-boot-plugin.gradle" description = 'SpringBoot Java 17 Smoke Tests.' dependencies { diff --git a/dd-smoke-tests/springboot-jetty-jsp/build.gradle b/dd-smoke-tests/springboot-jetty-jsp/build.gradle index aa99cd77534..6813c35843e 100644 --- a/dd-smoke-tests/springboot-jetty-jsp/build.gradle +++ b/dd-smoke-tests/springboot-jetty-jsp/build.gradle @@ -7,6 +7,7 @@ plugins { } apply from: "$rootDir/gradle/java.gradle" +apply from: "$rootDir/gradle/spring-boot-plugin.gradle" description = 'SpringBoot Jetty JSP Smoke Tests.' java { diff --git a/dd-smoke-tests/springboot-jpa/build.gradle b/dd-smoke-tests/springboot-jpa/build.gradle index 05e9c8212db..d257c858e6e 100644 --- a/dd-smoke-tests/springboot-jpa/build.gradle +++ b/dd-smoke-tests/springboot-jpa/build.gradle @@ -6,6 +6,7 @@ plugins { apply plugin: 'io.spring.dependency-management' apply from: "$rootDir/gradle/java.gradle" +apply from: "$rootDir/gradle/spring-boot-plugin.gradle" description = 'SpringBoot JPA Smoke Tests.' dependencies { diff --git a/dd-smoke-tests/springboot-thymeleaf/build.gradle b/dd-smoke-tests/springboot-thymeleaf/build.gradle index 80cfcedc0c4..7f1192ef674 100644 --- a/dd-smoke-tests/springboot-thymeleaf/build.gradle +++ b/dd-smoke-tests/springboot-thymeleaf/build.gradle @@ -6,6 +6,7 @@ plugins { } apply from: "$rootDir/gradle/java.gradle" +apply from: "$rootDir/gradle/spring-boot-plugin.gradle" description = 'SpringBoot thymeleaf 3 Smoke Tests.' java { diff --git a/dd-smoke-tests/springboot-tomcat-jsp/build.gradle b/dd-smoke-tests/springboot-tomcat-jsp/build.gradle index c80b878adfc..0bb2eec1290 100644 --- a/dd-smoke-tests/springboot-tomcat-jsp/build.gradle +++ b/dd-smoke-tests/springboot-tomcat-jsp/build.gradle @@ -7,6 +7,7 @@ plugins { } apply from: "$rootDir/gradle/java.gradle" +apply from: "$rootDir/gradle/spring-boot-plugin.gradle" description = 'SpringBoot Tomcat JSP Smoke Tests.' java { diff --git a/dd-smoke-tests/springboot-tomcat/build.gradle b/dd-smoke-tests/springboot-tomcat/build.gradle index 902096ce18d..34a010f814b 100644 --- a/dd-smoke-tests/springboot-tomcat/build.gradle +++ b/dd-smoke-tests/springboot-tomcat/build.gradle @@ -11,9 +11,9 @@ ext { } apply from: "$rootDir/gradle/java.gradle" +apply from: "$rootDir/gradle/spring-boot-plugin.gradle" description = 'SpringBoot Tomcat Smoke Tests.' - repositories { ivy { url 'https://archive.apache.org/dist' diff --git a/dd-smoke-tests/springboot-velocity/build.gradle b/dd-smoke-tests/springboot-velocity/build.gradle index b2be96af3c3..dc881c18c07 100644 --- a/dd-smoke-tests/springboot-velocity/build.gradle +++ b/dd-smoke-tests/springboot-velocity/build.gradle @@ -6,6 +6,7 @@ plugins { } apply from: "$rootDir/gradle/java.gradle" +apply from: "$rootDir/gradle/spring-boot-plugin.gradle" description = 'SpringBoot Velocity Smoke Tests.' dependencies { diff --git a/gradle/spring-boot-plugin.gradle b/gradle/spring-boot-plugin.gradle new file mode 100644 index 00000000000..6a504434434 --- /dev/null +++ b/gradle/spring-boot-plugin.gradle @@ -0,0 +1,5 @@ +// Spring Boot Gradle plugin pulls in JUnit Jupiter and JUnit Platform dependencies. +// Depending on the version of the plugin these dependencies may not be compatible with the Spock version that is used in the tracer. +// To ensure compatibility the versions are set explicitly here. +ext['junit-jupiter.version'] = libs.versions.junit5.get() +ext['junit-platform.version'] = libs.versions.junit.platform.get() From fd84df966b9b1746ca3eb5b53f9c837a67369196 Mon Sep 17 00:00:00 2001 From: Nikita Tkachenko Date: Thu, 11 Sep 2025 19:08:45 +0200 Subject: [PATCH 4/5] Fix Spock mocking issue in InstrumentationSpecification --- .../test/InstrumentationSpecification.groovy | 155 +------ .../agent/test/TrackingSpanDecorator.groovy | 391 ++++++++++++++++++ .../ValidatingRequestContextDecorator.groovy | 139 +++++++ 3 files changed, 539 insertions(+), 146 deletions(-) create mode 100644 dd-java-agent/instrumentation-testing/src/main/groovy/datadog/trace/agent/test/TrackingSpanDecorator.groovy create mode 100644 dd-java-agent/instrumentation-testing/src/main/groovy/datadog/trace/agent/test/ValidatingRequestContextDecorator.groovy diff --git a/dd-java-agent/instrumentation-testing/src/main/groovy/datadog/trace/agent/test/InstrumentationSpecification.groovy b/dd-java-agent/instrumentation-testing/src/main/groovy/datadog/trace/agent/test/InstrumentationSpecification.groovy index e08c966df10..05ae9c50323 100644 --- a/dd-java-agent/instrumentation-testing/src/main/groovy/datadog/trace/agent/test/InstrumentationSpecification.groovy +++ b/dd-java-agent/instrumentation-testing/src/main/groovy/datadog/trace/agent/test/InstrumentationSpecification.groovy @@ -25,24 +25,20 @@ import datadog.trace.agent.tooling.TracerInstaller import datadog.trace.agent.tooling.bytebuddy.matcher.ClassLoaderMatchers import datadog.trace.agent.tooling.bytebuddy.matcher.GlobalIgnores import datadog.trace.api.Config -import datadog.trace.api.DDSpanId import datadog.trace.api.IdGenerationStrategy import datadog.trace.api.ProcessTags import datadog.trace.api.StatsDClient import datadog.trace.api.TraceConfig import datadog.trace.api.config.GeneralConfig import datadog.trace.api.config.TracerConfig -import datadog.trace.api.gateway.RequestContext -import datadog.trace.api.internal.TraceSegment +import datadog.trace.api.datastreams.AgentDataStreamsMonitoring import datadog.trace.api.sampling.SamplingRule import datadog.trace.api.time.SystemTimeSource -import datadog.trace.api.datastreams.AgentDataStreamsMonitoring import datadog.trace.bootstrap.ActiveSubsystems -import datadog.trace.bootstrap.CallDepthThreadLocalMap import datadog.trace.bootstrap.InstrumentationErrors import datadog.trace.bootstrap.debugger.DebuggerContext import datadog.trace.bootstrap.instrumentation.api.AgentSpan -import datadog.trace.bootstrap.instrumentation.api.AgentTracer.TracerAPI +import datadog.trace.bootstrap.instrumentation.api.AgentTracer import datadog.trace.common.metrics.EventListener import datadog.trace.common.metrics.Sink import datadog.trace.common.writer.DDAgentWriter @@ -70,7 +66,6 @@ import org.junit.jupiter.api.extension.ExtendWith import org.slf4j.Logger import org.slf4j.LoggerFactory import org.spockframework.mock.MockUtil -import org.spockframework.mock.runtime.MockInvocation import spock.lang.Shared import java.lang.instrument.ClassFileTransformer @@ -152,7 +147,7 @@ abstract class InstrumentationSpecification extends DDSpecification implements A @SuppressWarnings('PropertyName') @Shared - TracerAPI TEST_TRACER + AgentTracer.TracerAPI TEST_TRACER @SuppressWarnings('PropertyName') @Shared @@ -175,10 +170,6 @@ abstract class InstrumentationSpecification extends DDSpecification implements A @Shared TestProfilingContextIntegration TEST_PROFILING_CONTEXT_INTEGRATION = new TestProfilingContextIntegration() - @SuppressWarnings('PropertyName') - @Shared - Set TEST_SPANS = Sets.newHashSet() - @SuppressWarnings('PropertyName') @Shared RecordingDatastreamsPayloadWriter TEST_DATA_STREAMS_WRITER @@ -264,7 +255,7 @@ abstract class InstrumentationSpecification extends DDSpecification implements A @Shared ConcurrentHashMap> spanFinishLocations = new ConcurrentHashMap<>() @Shared - ConcurrentHashMap originalToSpySpan = new ConcurrentHashMap<>() + ConcurrentHashMap originalToTrackingSpan = new ConcurrentHashMap<>() protected boolean enabledFinishTimingChecks() { false @@ -399,67 +390,13 @@ abstract class InstrumentationSpecification extends DDSpecification implements A boolean enabledFinishTimingChecks = this.enabledFinishTimingChecks() TEST_TRACER.startSpan(*_) >> { AgentSpan agentSpan = callRealMethod() - TEST_SPANS.add(agentSpan.spanId) if (!enabledFinishTimingChecks) { return agentSpan } - // rest of closure if for checking duplicate finishes and tags set after finish - AgentSpan spiedAgentSpan = Spy(agentSpan) - originalToSpySpan[agentSpan] = spiedAgentSpan - def handleFinish = { MockInvocation mi -> - def depth = CallDepthThreadLocalMap.incrementCallDepth(DDSpan) - try { - if (depth > 0) { - return - } - List locations - List newLocations - do { - locations = spanFinishLocations.get(agentSpan) - newLocations = (locations ?: []) + new Exception() - } while (!(locations == null ? - spanFinishLocations.putIfAbsent(agentSpan, newLocations) == null : - spanFinishLocations.replace(agentSpan, locations, newLocations))) - mi.callRealMethod() - } finally { - CallDepthThreadLocalMap.decrementCallDepth(DDSpan) - } - } - spiedAgentSpan.finish() >> { - handleFinish(delegate) - } - spiedAgentSpan.finish(_ as long) >> { - handleFinish(delegate) - } - spiedAgentSpan.finishWithDuration() >> { - handleFinish(delegate) - } - spiedAgentSpan.finishWithEndToEnd() >> { - handleFinish(delegate) - } - spiedAgentSpan.getLocalRootSpan() >> { - DDSpan unwrappedSpan = callRealMethod() - originalToSpySpan.getOrDefault(unwrappedSpan, unwrappedSpan) - } - RequestContext requestContext = agentSpan.getRequestContext() - TraceSegment segment = requestContext.getTraceSegment() - RequestContext spiedReqCtx = Spy(requestContext) - TraceSegment checkedSegment = new PreconditionCheckTraceSegment( - check: { - -> if (useStrictTraceWrites() && spiedAgentSpan.localRootSpan.isFinished()) { - throw new AssertionError("Interaction with TraceSegment after root span has already finished: $spiedAgentSpan") - }}, - delegate: segment - ) - spiedAgentSpan.getRequestContext() >> { - spiedReqCtx - } - spiedReqCtx.getTraceSegment() >> { - checkedSegment - } - - spiedAgentSpan + def trackingSpan = new TrackingSpanDecorator(agentSpan, spanFinishLocations, originalToTrackingSpan, useStrictTraceWrites()) + originalToTrackingSpan[agentSpan] = trackingSpan + return trackingSpan } // if a test enables the instrumentation it verifies, @@ -498,7 +435,6 @@ abstract class InstrumentationSpecification extends DDSpecification implements A println "Starting test: ${getSpecificationContext().getCurrentIteration().getName()} from ${specificationContext.currentSpec.name}" TEST_TRACER.flush() - TEST_SPANS.clear() if (isTestAgentEnabled()) { TEST_AGENT_WRITER.flush() @@ -561,7 +497,7 @@ abstract class InstrumentationSpecification extends DDSpecification implements A } } finally { spanFinishLocations.clear() - originalToSpySpan.clear() + originalToTrackingSpan.clear() } assert InstrumentationErrors.errorCount == 0 } @@ -578,7 +514,7 @@ abstract class InstrumentationSpecification extends DDSpecification implements A pw.write "Location $i:\n" def st = e.stackTrace int loc = st.findIndexOf { - it.className.startsWith('datadog.trace.core.DDSpan$SpockMock$') && + it.className.startsWith(TrackingSpanDecorator.class.name) && it.methodName.startsWith('finish') } for (int j = loc == -1 ? 0 : loc; j < st.length; j++) { @@ -590,77 +526,6 @@ abstract class InstrumentationSpecification extends DDSpecification implements A } } - class PreconditionCheckTraceSegment implements TraceSegment { - Closure check - TraceSegment delegate - - @Override - void setTagTop(String key, Object value, boolean sanitize) { - check() - delegate.setTagTop(key, value, sanitize) - } - - @Override - void setTagCurrent(String key, Object value, boolean sanitize) { - check() - delegate.setTagCurrent(key, value, sanitize) - } - - @Override - Object getTagTop(String key, boolean sanitize) { - check() - return delegate.getTagTop(key, sanitize) - } - - @Override - Object getTagCurrent(String key, boolean sanitize) { - check() - return delegate.getTagCurrent(key, sanitize) - } - - @Override - void setDataTop(String key, Object value) { - check() - delegate.setDataTop(key, value) - } - - @Override - Object getDataTop(String key) { - check() - return delegate.getDataTop(key) - } - - @Override - void setMetaStructTop(String key, Object value) { - check() - delegate.setMetaStructTop(key, value) - } - - @Override - void setMetaStructCurrent(String key, Object value) { - check() - delegate.setMetaStructCurrent(key, value) - } - - @Override - void effectivelyBlocked() { - check() - delegate.effectivelyBlocked() - } - - @Override - void setDataCurrent(String key, Object value) { - check() - delegate.setDataCurrent(key, value) - } - - @Override - Object getDataCurrent(String key) { - check() - return delegate.getDataCurrent(key) - } - } - /** Override to clean up things after the agent is removed */ protected void cleanupAfterAgent() {} @@ -793,5 +658,3 @@ class AbortTransformationException extends RuntimeException { super(message) } } - - diff --git a/dd-java-agent/instrumentation-testing/src/main/groovy/datadog/trace/agent/test/TrackingSpanDecorator.groovy b/dd-java-agent/instrumentation-testing/src/main/groovy/datadog/trace/agent/test/TrackingSpanDecorator.groovy new file mode 100644 index 00000000000..a5aba89b4cb --- /dev/null +++ b/dd-java-agent/instrumentation-testing/src/main/groovy/datadog/trace/agent/test/TrackingSpanDecorator.groovy @@ -0,0 +1,391 @@ +package datadog.trace.agent.test + +import datadog.trace.api.DDTraceId +import datadog.trace.api.TagMap +import datadog.trace.api.TraceConfig +import datadog.trace.api.gateway.Flow +import datadog.trace.api.gateway.RequestContext +import datadog.trace.api.interceptor.MutableSpan +import datadog.trace.bootstrap.CallDepthThreadLocalMap +import datadog.trace.bootstrap.instrumentation.api.AgentSpan +import datadog.trace.bootstrap.instrumentation.api.AgentSpanContext +import datadog.trace.bootstrap.instrumentation.api.AgentSpanLink +import datadog.trace.core.DDSpan + +import java.util.concurrent.ConcurrentHashMap + +/** + * Decorator for {@link AgentSpan} that keeps track of the decorated span's finish location. + * The locations are stored in {@link InstrumentationSpecification#spanFinishLocations} and + * are used to verify that the decorated span is not finished twice (see {@link InstrumentationSpecification#doCheckRepeatedFinish}). + * + *

It also wraps the span's {@link RequestContext} with {@link ValidatingRequestContextDecorator} + */ +class TrackingSpanDecorator implements AgentSpan { + + private final AgentSpan delegate + private final ConcurrentHashMap> spanFinishLocations + private final ConcurrentHashMap originalToTrackingSpan + private final RequestContext spiedRequestContext + + TrackingSpanDecorator(AgentSpan delegate, + ConcurrentHashMap> spanFinishLocations, + ConcurrentHashMap originalToTrackingSpan, + boolean useStrictTraceWrites) { + this.delegate = delegate + this.spanFinishLocations = spanFinishLocations + this.originalToTrackingSpan = originalToTrackingSpan + + RequestContext requestContext = delegate.getRequestContext() + this.spiedRequestContext = new ValidatingRequestContextDecorator(requestContext, this, useStrictTraceWrites) + } + + @Override + void finish() { + handleFinish { + it.finish() + } + } + + @Override + void finish(long finishMicros) { + handleFinish { + it.finish(finishMicros) + } + } + + @Override + void finishWithDuration(long durationNanos) { + handleFinish { + it.finishWithDuration(durationNanos) + } + } + + @Override + void finishWithEndToEnd() { + handleFinish { + it.finishWithEndToEnd() + } + } + + private void handleFinish(Closure delegateCall) { + def depth = CallDepthThreadLocalMap.incrementCallDepth(DDSpan) + try { + if (depth > 0) { + return + } + List locations + List newLocations + do { + locations = spanFinishLocations.get(delegate) + newLocations = (locations ?: []) + new Exception() + } while (!(locations == null ? + spanFinishLocations.putIfAbsent(delegate, newLocations) == null : + spanFinishLocations.replace(delegate, locations, newLocations))) + delegateCall.call(delegate) + } finally { + CallDepthThreadLocalMap.decrementCallDepth(DDSpan) + } + } + + @Override + AgentSpan getLocalRootSpan() { + AgentSpan localRootSpan = delegate.getLocalRootSpan() + return originalToTrackingSpan.getOrDefault(localRootSpan, localRootSpan) + } + + @Override + RequestContext getRequestContext() { + return spiedRequestContext + } + + @Override + DDTraceId getTraceId() { + return delegate.getTraceId() + } + + @Override + long getSpanId() { + return delegate.getSpanId() + } + + @Override + AgentSpan setTag(String key, boolean value) { + return delegate.setTag(key, value) + } + + @Override + void setRequestBlockingAction(Flow.Action.RequestBlockingAction rba) { + delegate.setRequestBlockingAction(rba) + } + + @Override + Flow.Action.RequestBlockingAction getRequestBlockingAction() { + return delegate.getRequestBlockingAction() + } + + @Override + AgentSpan setTag(String key, int value) { + return delegate.setTag(key, value) + } + + @Override + AgentSpan setTag(String key, long value) { + return delegate.setTag(key, value) + } + + @Override + AgentSpan setTag(String key, double value) { + return delegate.setTag(key, value) + } + + @Override + AgentSpan setTag(String key, String value) { + return delegate.setTag(key, value) + } + + @Override + AgentSpan setTag(String key, CharSequence value) { + return delegate.setTag(key, value) + } + + @Override + AgentSpan setTag(String key, Object value) { + return delegate.setTag(key, value) + } + + @Override + AgentSpan setAllTags(Map map) { + return delegate.setAllTags(map) + } + + @Override + AgentSpan setTag(String key, Number value) { + return delegate.setTag(key, value) + } + + @Override + AgentSpan setMetric(CharSequence key, int value) { + return delegate.setMetric(key, value) + } + + @Override + AgentSpan setMetric(CharSequence key, long value) { + return delegate.setMetric(key, value) + } + + @Override + AgentSpan setMetric(CharSequence key, double value) { + return delegate.setMetric(key, value) + } + + @Override + boolean isError() { + return delegate.isError() + } + + @Override + AgentSpan setSpanType(CharSequence type) { + return delegate.setSpanType(type) + } + + @Override + TagMap getTags() { + return delegate.getTags() + } + + @Override + Object getTag(String key) { + return delegate.getTag(key) + } + + @Override + AgentSpan setError(boolean error) { + return delegate.setError(error) + } + + @Override + MutableSpan getRootSpan() { + return delegate.getRootSpan() + } + + @Override + AgentSpan setError(boolean error, byte priority) { + return delegate.setError(error, priority) + } + + @Override + AgentSpan setMeasured(boolean measured) { + return delegate.setMeasured(measured) + } + + @Override + AgentSpan setErrorMessage(String errorMessage) { + return delegate.setErrorMessage(errorMessage) + } + + @Override + AgentSpan addThrowable(Throwable throwable) { + return delegate.addThrowable(throwable) + } + + @Override + AgentSpan addThrowable(Throwable throwable, byte errorPriority) { + return delegate.addThrowable(throwable, errorPriority) + } + + @Override + boolean isSameTrace(AgentSpan otherSpan) { + return delegate.isSameTrace(otherSpan) + } + + @Override + AgentSpanContext context() { + return delegate.context() + } + + @Override + String getBaggageItem(String key) { + return delegate.getBaggageItem(key) + } + + @Override + AgentSpan setBaggageItem(String key, String value) { + return delegate.setBaggageItem(key, value) + } + + @Override + AgentSpan setHttpStatusCode(int statusCode) { + return delegate.setHttpStatusCode(statusCode) + } + + @Override + short getHttpStatusCode() { + return delegate.getHttpStatusCode() + } + + @Override + void beginEndToEnd() { + delegate.beginEndToEnd() + } + + @Override + boolean phasedFinish() { + return delegate.phasedFinish() + } + + @Override + void publish() { + delegate.publish() + } + + @Override + CharSequence getSpanName() { + return delegate.getSpanName() + } + + @Override + void setSpanName(CharSequence spanName) { + delegate.setSpanName(spanName) + } + + @Override + boolean hasResourceName() { + return delegate.hasResourceName() + } + + @Override + byte getResourceNamePriority() { + return delegate.getResourceNamePriority() + } + + @Override + long getStartTime() { + return delegate.getStartTime() + } + + @Override + long getDurationNano() { + return delegate.getDurationNano() + } + + @Override + CharSequence getOperationName() { + return delegate.getOperationName() + } + + @Override + MutableSpan setOperationName(CharSequence serviceName) { + return delegate.setOperationName(serviceName) + } + + @Override + String getServiceName() { + return delegate.getServiceName() + } + + @Override + MutableSpan setServiceName(String serviceName) { + return delegate.setServiceName(serviceName) + } + + @Override + CharSequence getResourceName() { + return delegate.getResourceName() + } + + @Override + AgentSpan setResourceName(CharSequence resourceName) { + return delegate.setResourceName(resourceName) + } + + @Override + Integer getSamplingPriority() { + return delegate.getSamplingPriority() + } + + @Override + MutableSpan setSamplingPriority(int newPriority) { + return delegate.setSamplingPriority(newPriority) + } + + @Override + String getSpanType() { + return delegate.getSpanType() + } + + @Override + AgentSpan setResourceName(CharSequence resourceName, byte priority) { + return delegate.setResourceName(resourceName, priority) + } + + @Override + Integer forceSamplingDecision() { + return delegate.forceSamplingDecision() + } + + @Override + AgentSpan setSamplingPriority(int newPriority, int samplingMechanism) { + return delegate.setSamplingPriority(newPriority, samplingMechanism) + } + + @Override + TraceConfig traceConfig() { + return delegate.traceConfig() + } + + @Override + void addLink(AgentSpanLink link) { + delegate.addLink(link) + } + + @Override + AgentSpan setMetaStruct(String field, Object value) { + return delegate.setMetaStruct(field, value) + } + + @Override + boolean isOutbound() { + return delegate.isOutbound() + } +} diff --git a/dd-java-agent/instrumentation-testing/src/main/groovy/datadog/trace/agent/test/ValidatingRequestContextDecorator.groovy b/dd-java-agent/instrumentation-testing/src/main/groovy/datadog/trace/agent/test/ValidatingRequestContextDecorator.groovy new file mode 100644 index 00000000000..13c7ab59c3c --- /dev/null +++ b/dd-java-agent/instrumentation-testing/src/main/groovy/datadog/trace/agent/test/ValidatingRequestContextDecorator.groovy @@ -0,0 +1,139 @@ +package datadog.trace.agent.test + +import datadog.trace.api.gateway.BlockResponseFunction +import datadog.trace.api.gateway.RequestContext +import datadog.trace.api.gateway.RequestContextSlot +import datadog.trace.api.internal.TraceSegment + +import java.util.function.Function + +/** + * Decorator for {@link RequestContext} that validates interactions with the request's {@link TraceSegment}: + * if there is an attempt to modify the trace segment after the root span has finished, + * an assertion error will be thrown. + */ +class ValidatingRequestContextDecorator implements RequestContext { + + private final RequestContext delegate + private final TraceSegment traceSegment + + ValidatingRequestContextDecorator(RequestContext delegate, TrackingSpanDecorator spiedAgentSpan, boolean useStrictTraceWrites) { + this.delegate = delegate + + def segment = delegate.getTraceSegment() + this.traceSegment = new PreconditionCheckTraceSegment( + segment, { + -> + if (useStrictTraceWrites && spiedAgentSpan.localRootSpan.durationNano != 0) { + throw new AssertionError("Interaction with TraceSegment after root span has already finished: $spiedAgentSpan") + } + } + ) + } + + @Override + T getData(RequestContextSlot slot) { + return delegate.getData(slot) + } + + @Override + TraceSegment getTraceSegment() { + return traceSegment + } + + @Override + void setBlockResponseFunction(BlockResponseFunction blockResponseFunction) { + delegate.setBlockResponseFunction(blockResponseFunction) + } + + @Override + BlockResponseFunction getBlockResponseFunction() { + return delegate.getBlockResponseFunction() + } + + @Override + T getOrCreateMetaStructTop(String key, Function defaultValue) { + return delegate.getOrCreateMetaStructTop(key, defaultValue) + } + + @Override + void close() throws IOException { + delegate.close() + } + + private class PreconditionCheckTraceSegment implements TraceSegment { + private final Closure check + private final TraceSegment delegate + + PreconditionCheckTraceSegment(TraceSegment delegate, Closure check) { + this.delegate = delegate + this.check = check + } + + @Override + void setTagTop(String key, Object value, boolean sanitize) { + check() + delegate.setTagTop(key, value, sanitize) + } + + @Override + void setTagCurrent(String key, Object value, boolean sanitize) { + check() + delegate.setTagCurrent(key, value, sanitize) + } + + @Override + Object getTagTop(String key, boolean sanitize) { + check() + return delegate.getTagTop(key, sanitize) + } + + @Override + Object getTagCurrent(String key, boolean sanitize) { + check() + return delegate.getTagCurrent(key, sanitize) + } + + @Override + void setDataTop(String key, Object value) { + check() + delegate.setDataTop(key, value) + } + + @Override + Object getDataTop(String key) { + check() + return delegate.getDataTop(key) + } + + @Override + void setMetaStructTop(String key, Object value) { + check() + delegate.setMetaStructTop(key, value) + } + + @Override + void setMetaStructCurrent(String key, Object value) { + check() + delegate.setMetaStructCurrent(key, value) + } + + @Override + void effectivelyBlocked() { + check() + delegate.effectivelyBlocked() + } + + @Override + void setDataCurrent(String key, Object value) { + check() + delegate.setDataCurrent(key, value) + } + + @Override + Object getDataCurrent(String key) { + check() + return delegate.getDataCurrent(key) + } + } +} From d983a67aa5ea6814c80d3f082311c2d3a1e98c5f Mon Sep 17 00:00:00 2001 From: Nikita Tkachenko Date: Thu, 11 Sep 2025 19:09:01 +0200 Subject: [PATCH 5/5] Fix Spock instrumentation to work with version 2.4 --- .../instrumentation/junit5/CucumberUtils.java | 3 ++- .../instrumentation/junit5/SpockUtils.java | 13 +++++++++++- .../junit5/CompositeEngineListener.java | 9 ++++++++ .../junit5/JUnit5Instrumentation.java | 1 + .../junit5/TestDataFactory.java | 21 ++++++++++++++++++- .../JUnit5ExecutionInstrumentation.java | 4 ++++ 6 files changed, 48 insertions(+), 3 deletions(-) diff --git a/dd-java-agent/instrumentation/junit-5.3/cucumber-junit-5/src/main/java/datadog/trace/instrumentation/junit5/CucumberUtils.java b/dd-java-agent/instrumentation/junit-5.3/cucumber-junit-5/src/main/java/datadog/trace/instrumentation/junit5/CucumberUtils.java index 1be7ac3effc..e0dd68f6c98 100644 --- a/dd-java-agent/instrumentation/junit-5.3/cucumber-junit-5/src/main/java/datadog/trace/instrumentation/junit5/CucumberUtils.java +++ b/dd-java-agent/instrumentation/junit-5.3/cucumber-junit-5/src/main/java/datadog/trace/instrumentation/junit5/CucumberUtils.java @@ -23,7 +23,8 @@ public abstract class CucumberUtils { TestDataFactory.register( JUnitPlatformUtils.ENGINE_ID_CUCUMBER, CucumberUtils::toTestIdentifier, - d -> TestSourceData.UNKNOWN); + d -> TestSourceData.UNKNOWN, + null); } public static @Nullable String getCucumberVersion(TestEngine cucumberEngine) { diff --git a/dd-java-agent/instrumentation/junit-5.3/spock-junit-5/src/main/java/datadog/trace/instrumentation/junit5/SpockUtils.java b/dd-java-agent/instrumentation/junit-5.3/spock-junit-5/src/main/java/datadog/trace/instrumentation/junit5/SpockUtils.java index a5baa28f28d..eaa4a14dcc3 100644 --- a/dd-java-agent/instrumentation/junit-5.3/spock-junit-5/src/main/java/datadog/trace/instrumentation/junit5/SpockUtils.java +++ b/dd-java-agent/instrumentation/junit-5.3/spock-junit-5/src/main/java/datadog/trace/instrumentation/junit5/SpockUtils.java @@ -17,6 +17,8 @@ import org.junit.platform.engine.support.descriptor.MethodSource; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.spockframework.runtime.IterationNode; +import org.spockframework.runtime.ParameterizedFeatureNode; import org.spockframework.runtime.SpockNode; import org.spockframework.runtime.model.FeatureInfo; import org.spockframework.runtime.model.FeatureMetadata; @@ -39,7 +41,8 @@ public class SpockUtils { TestDataFactory.register( JUnitPlatformUtils.ENGINE_ID_SPOCK, SpockUtils::toTestIdentifier, - SpockUtils::toTestSourceData); + SpockUtils::toTestSourceData, + SpockUtils::shouldBeTraced); } /* @@ -107,6 +110,14 @@ public static TestSourceData toTestSourceData(TestDescriptor testDescriptor) { } } + public static boolean shouldBeTraced(TestDescriptor testDescriptor) { + // Spock 2.4 quirks: both FeatureNode and IterationNode are reported as tests. + // We want to filter out iteration nodes to avoid reporting the same test twice, + // but keep them for parameterized tests to report each set of parameters as a separate test. + return !(testDescriptor instanceof IterationNode) + || testDescriptor.getParent().map(c -> c instanceof ParameterizedFeatureNode).orElse(false); + } + private static Method getTestMethod(MethodSource methodSource) { String methodName = methodSource.getMethodName(); if (methodName == null) { diff --git a/dd-java-agent/instrumentation/junit-5.3/src/main/java/datadog/trace/instrumentation/junit5/CompositeEngineListener.java b/dd-java-agent/instrumentation/junit-5.3/src/main/java/datadog/trace/instrumentation/junit5/CompositeEngineListener.java index 005572f9d97..8f17d8c31b0 100644 --- a/dd-java-agent/instrumentation/junit-5.3/src/main/java/datadog/trace/instrumentation/junit5/CompositeEngineListener.java +++ b/dd-java-agent/instrumentation/junit-5.3/src/main/java/datadog/trace/instrumentation/junit5/CompositeEngineListener.java @@ -30,12 +30,18 @@ public void reportingEntryPublished(TestDescriptor testDescriptor, ReportEntry e @Override public void executionStarted(TestDescriptor testDescriptor) { + if (!TestDataFactory.shouldBeTraced(testDescriptor)) { + return; + } tracingListener.executionStarted(testDescriptor); delegate.executionStarted(testDescriptor); } @Override public void executionSkipped(TestDescriptor testDescriptor, String reason) { + if (!TestDataFactory.shouldBeTraced(testDescriptor)) { + return; + } tracingListener.executionSkipped(testDescriptor, reason); delegate.executionSkipped(testDescriptor, reason); } @@ -43,6 +49,9 @@ public void executionSkipped(TestDescriptor testDescriptor, String reason) { @Override public void executionFinished( TestDescriptor testDescriptor, TestExecutionResult testExecutionResult) { + if (!TestDataFactory.shouldBeTraced(testDescriptor)) { + return; + } tracingListener.executionFinished(testDescriptor, testExecutionResult); delegate.executionFinished(testDescriptor, testExecutionResult); } diff --git a/dd-java-agent/instrumentation/junit-5.3/src/main/java/datadog/trace/instrumentation/junit5/JUnit5Instrumentation.java b/dd-java-agent/instrumentation/junit-5.3/src/main/java/datadog/trace/instrumentation/junit5/JUnit5Instrumentation.java index 2b8c6c9d55c..b6e9ef3e032 100644 --- a/dd-java-agent/instrumentation/junit-5.3/src/main/java/datadog/trace/instrumentation/junit5/JUnit5Instrumentation.java +++ b/dd-java-agent/instrumentation/junit-5.3/src/main/java/datadog/trace/instrumentation/junit5/JUnit5Instrumentation.java @@ -49,6 +49,7 @@ public ElementMatcher hierarchyMatcher() { public String[] helperClassNames() { return new String[] { packageName + ".JUnitPlatformUtils", + packageName + ".TestDataFactory", packageName + ".TestEventsHandlerHolder", packageName + ".TracingListener", packageName + ".CompositeEngineListener", diff --git a/dd-java-agent/instrumentation/junit-5.3/src/main/java/datadog/trace/instrumentation/junit5/TestDataFactory.java b/dd-java-agent/instrumentation/junit-5.3/src/main/java/datadog/trace/instrumentation/junit5/TestDataFactory.java index 2fa590bfd67..0a3d673b0c2 100644 --- a/dd-java-agent/instrumentation/junit-5.3/src/main/java/datadog/trace/instrumentation/junit5/TestDataFactory.java +++ b/dd-java-agent/instrumentation/junit-5.3/src/main/java/datadog/trace/instrumentation/junit5/TestDataFactory.java @@ -6,6 +6,8 @@ import java.util.HashMap; import java.util.Map; import java.util.function.Function; +import java.util.function.Predicate; +import javax.annotation.Nullable; import org.junit.platform.engine.TestDescriptor; import org.junit.platform.engine.UniqueId; @@ -19,14 +21,22 @@ private TestDataFactory() {} private static volatile Map> TEST_SOURCE_DATA_FACTORY_BY_ENGINE_ID = Collections.emptyMap(); + private static volatile Map> + TEST_DESCRIPTOR_FILTER_BY_ENGINE_ID = Collections.emptyMap(); + public static synchronized void register( String engineId, Function testIdentifierFactory, - Function testSourceDataFactory) { + Function testSourceDataFactory, + @Nullable Predicate testDescriptorFilter) { TEST_IDENTIFIER_FACTORY_BY_ENGINE_ID = addEntry(TEST_IDENTIFIER_FACTORY_BY_ENGINE_ID, engineId, testIdentifierFactory); TEST_SOURCE_DATA_FACTORY_BY_ENGINE_ID = addEntry(TEST_SOURCE_DATA_FACTORY_BY_ENGINE_ID, engineId, testSourceDataFactory); + if (testDescriptorFilter != null) { + TEST_DESCRIPTOR_FILTER_BY_ENGINE_ID = + addEntry(TEST_DESCRIPTOR_FILTER_BY_ENGINE_ID, engineId, testDescriptorFilter); + } } private static Map addEntry(Map originalMap, K key, V value) { @@ -52,4 +62,13 @@ public static TestSourceData createTestSourceData(TestDescriptor testDescriptor) .orElse(JUnitPlatformUtils::toTestSourceData) .apply(testDescriptor); } + + public static boolean shouldBeTraced(TestDescriptor testDescriptor) { + UniqueId uniqueId = testDescriptor.getUniqueId(); + return uniqueId + .getEngineId() + .map(TEST_DESCRIPTOR_FILTER_BY_ENGINE_ID::get) + .map(filter -> filter.test(testDescriptor)) + .orElse(true); + } } diff --git a/dd-java-agent/instrumentation/junit-5.3/src/main/java/datadog/trace/instrumentation/junit5/execution/JUnit5ExecutionInstrumentation.java b/dd-java-agent/instrumentation/junit-5.3/src/main/java/datadog/trace/instrumentation/junit5/execution/JUnit5ExecutionInstrumentation.java index 342e53c79dd..8be607a6091 100644 --- a/dd-java-agent/instrumentation/junit-5.3/src/main/java/datadog/trace/instrumentation/junit5/execution/JUnit5ExecutionInstrumentation.java +++ b/dd-java-agent/instrumentation/junit-5.3/src/main/java/datadog/trace/instrumentation/junit5/execution/JUnit5ExecutionInstrumentation.java @@ -133,6 +133,10 @@ public static Boolean execute(@Advice.This HierarchicalTestExecutorService.TestT return null; } + if (!TestDataFactory.shouldBeTraced(testDescriptor)) { + return null; + } + String engineId = JUnitPlatformUtils.getEngineId(testDescriptor); TestFrameworkInstrumentation framework = JUnitPlatformUtils.engineIdToFramework(engineId);