From c2221fcbe6b69f65dff391b6fd8b5ed4c8f223db Mon Sep 17 00:00:00 2001 From: Alexey Kuznetsov Date: Sat, 12 Sep 2026 22:45:33 -0400 Subject: [PATCH 1/7] Fix Robolectric 4.17 muzzle validation --- .../robolectric-4.13/build.gradle | 4 +++ .../robolectric-4.13/gradle.lockfile | 1 + .../robolectric/RobolectricTestAnnotator.java | 26 +++++++++++++++---- .../smoketest/GradleDaemonSmokeTest.java | 2 +- .../test-succeed-robolectric/build.gradleTest | 2 +- .../test-succeed-robolectric/events.ftl | 6 ++--- 6 files changed, 31 insertions(+), 10 deletions(-) diff --git a/dd-java-agent/instrumentation/robolectric-4.13/build.gradle b/dd-java-agent/instrumentation/robolectric-4.13/build.gradle index 38cdb55d0db..c9cd551e9e1 100644 --- a/dd-java-agent/instrumentation/robolectric-4.13/build.gradle +++ b/dd-java-agent/instrumentation/robolectric-4.13/build.gradle @@ -11,6 +11,9 @@ muzzle { // androidx.test:monitor is an Android archive (.aar) that a JVM configuration cannot consume; // it is not referenced by the advice/helper. Mirror the compileOnly exclusion below. excludeDependency 'androidx.test:monitor' + // Robolectric loads the selected Android SDK dynamically, so android-all is not a transitive + // dependency even though android.os.Build is available when the helper runs. + extraDependency 'org.robolectric:android-all:14-robolectric-10818077' } } @@ -32,6 +35,7 @@ dependencies { compileOnly(group: 'org.robolectric', name: 'robolectric', version: '4.16.1') { exclude group: 'androidx.test', module: 'monitor' } + compileOnly group: 'org.robolectric', name: 'android-all', version: '14-robolectric-10818077' // RobolectricTestRunner extends JUnit's BlockJUnit4ClassRunner; JUnit must be on the compile // classpath so its supertypes resolve (both javac and forbiddenApis walk the class hierarchy). compileOnly group: 'junit', name: 'junit', version: '4.13.2' diff --git a/dd-java-agent/instrumentation/robolectric-4.13/gradle.lockfile b/dd-java-agent/instrumentation/robolectric-4.13/gradle.lockfile index 721b02f69ba..49f762c7d94 100644 --- a/dd-java-agent/instrumentation/robolectric-4.13/gradle.lockfile +++ b/dd-java-agent/instrumentation/robolectric-4.13/gradle.lockfile @@ -119,6 +119,7 @@ org.ow2.asm:asm-tree:9.8=compileClasspath org.ow2.asm:asm-util:9.10.1=spotbugs org.ow2.asm:asm-util:9.7.1=testRuntimeClasspath org.ow2.asm:asm:9.10.1=buildTimeInstrumentationPlugin,compileClasspath,muzzleTooling,runtimeClasspath,spotbugs,testCompileClasspath,testRuntimeClasspath +org.robolectric:android-all:14-robolectric-10818077=compileClasspath org.robolectric:annotations:4.16.1=compileClasspath org.robolectric:junit:4.16.1=compileClasspath org.robolectric:nativeruntime:4.16.1=compileClasspath diff --git a/dd-java-agent/instrumentation/robolectric-4.13/src/main/java/datadog/trace/instrumentation/robolectric/RobolectricTestAnnotator.java b/dd-java-agent/instrumentation/robolectric-4.13/src/main/java/datadog/trace/instrumentation/robolectric/RobolectricTestAnnotator.java index 8396ddc8c2d..ff3ff2e811f 100644 --- a/dd-java-agent/instrumentation/robolectric-4.13/src/main/java/datadog/trace/instrumentation/robolectric/RobolectricTestAnnotator.java +++ b/dd-java-agent/instrumentation/robolectric-4.13/src/main/java/datadog/trace/instrumentation/robolectric/RobolectricTestAnnotator.java @@ -1,11 +1,13 @@ package datadog.trace.instrumentation.robolectric; +import android.os.Build; import datadog.trace.api.gateway.RequestContext; import datadog.trace.api.gateway.RequestContextSlot; import datadog.trace.bootstrap.instrumentation.api.AgentSpan; import datadog.trace.bootstrap.instrumentation.api.AgentTracer; import datadog.trace.bootstrap.instrumentation.api.Tags; import java.io.File; +import java.lang.reflect.Field; import java.net.URL; import java.security.CodeSource; import java.security.ProtectionDomain; @@ -13,7 +15,6 @@ import java.util.regex.Pattern; import org.robolectric.RobolectricTestRunner; import org.robolectric.RuntimeEnvironment; -import org.robolectric.versioning.AndroidVersions; public final class RobolectricTestAnnotator { @@ -40,10 +41,10 @@ public static void annotate() { } span.setTag(Tags.TEST_ANDROID_API_LEVEL, apiLevel); - AndroidVersions.AndroidRelease release = AndroidVersions.getReleaseForSdkInt(apiLevel); - if (release != null) { - span.setTag(Tags.TEST_ANDROID_RELEASE, release.getVersion()); - span.setTag(Tags.TEST_ANDROID_CODENAME, release.getShortCode()); + span.setTag(Tags.TEST_ANDROID_RELEASE, Build.VERSION.RELEASE); + String androidCodename = androidCodename(apiLevel); + if (androidCodename != null) { + span.setTag(Tags.TEST_ANDROID_CODENAME, androidCodename); } String robolectricVersion = robolectricVersion(); if (robolectricVersion != null) { @@ -51,6 +52,21 @@ public static void annotate() { } } + private static String androidCodename(int apiLevel) { + try { + // Released Android SDKs report "REL" through Build.VERSION.CODENAME. Derive the public + // short codename (U, V, and so on) from the matching VERSION_CODES field instead. + for (Field field : Build.VERSION_CODES.class.getFields()) { + if (field.getType() == int.class && field.getInt(null) == apiLevel) { + return field.getName().substring(0, 1); + } + } + } catch (Throwable t) { + // Ignore missing or inaccessible fields and omit the optional codename tag. + } + return null; + } + private static String robolectricVersion() { try { // RuntimeEnvironment is re-loaded by the sandbox classloader with no CodeSource, but the diff --git a/dd-smoke-tests/gradle/src/test/java/datadog/smoketest/GradleDaemonSmokeTest.java b/dd-smoke-tests/gradle/src/test/java/datadog/smoketest/GradleDaemonSmokeTest.java index 37cd8c8a99c..c334a361d06 100644 --- a/dd-smoke-tests/gradle/src/test/java/datadog/smoketest/GradleDaemonSmokeTest.java +++ b/dd-smoke-tests/gradle/src/test/java/datadog/smoketest/GradleDaemonSmokeTest.java @@ -151,7 +151,7 @@ void testNew( void testRobolectric(String gradleVersion, String projectName, int expectedTraces) throws IOException { Assumptions.assumeTrue( - JavaVirtualMachine.isJavaVersionBetween(17, 22), "Robolectric 4.16 supports JDK 17-21"); + JavaVirtualMachine.isJavaVersionBetween(17, 22), "Robolectric 4.17 supports JDK 17-21"); Assumptions.assumeFalse( OperatingSystem.architecture().isArm64(), "Robolectric does not support arm64 (missing native runtime binaries, follow https://github.com/robolectric/robolectric/issues/9166)"); diff --git a/dd-smoke-tests/gradle/src/test/resources/test-succeed-robolectric/build.gradleTest b/dd-smoke-tests/gradle/src/test/resources/test-succeed-robolectric/build.gradleTest index 566eba49ada..52e930174a1 100644 --- a/dd-smoke-tests/gradle/src/test/resources/test-succeed-robolectric/build.gradleTest +++ b/dd-smoke-tests/gradle/src/test/resources/test-succeed-robolectric/build.gradleTest @@ -96,7 +96,7 @@ configurations.configureEach { dependencies { testImplementation 'junit:junit:4.13.2' - testImplementation 'org.robolectric:robolectric:4.16.1' + testImplementation 'org.robolectric:robolectric:4.17' // Pre-built Android SDK jar for the level the fixtures configure. testImplementation 'org.robolectric:android-all:14-robolectric-10818077' // androidx.test:core pulls in androidx.test:monitor (InstrumentationRegistry); ext:junit provides diff --git a/dd-smoke-tests/gradle/src/test/resources/test-succeed-robolectric/events.ftl b/dd-smoke-tests/gradle/src/test/resources/test-succeed-robolectric/events.ftl index b08072b29ab..d9c3587bac1 100644 --- a/dd-smoke-tests/gradle/src/test/resources/test-succeed-robolectric/events.ftl +++ b/dd-smoke-tests/gradle/src/test/resources/test-succeed-robolectric/events.ftl @@ -389,7 +389,7 @@ "span.kind" : "test", "test.android.codename" : "U", "test.android.release" : "14", - "test.android.robolectric.version" : "4.16.1", + "test.android.robolectric.version" : "4.17", "test.final_status" : "pass", "test.framework" : "junit4", "test.framework_version" : "4.13.2", @@ -507,7 +507,7 @@ "span.kind" : "test", "test.android.codename" : "U", "test.android.release" : "14", - "test.android.robolectric.version" : "4.16.1", + "test.android.robolectric.version" : "4.17", "test.final_status" : "pass", "test.framework" : "junit4", "test.framework_version" : "4.13.2", @@ -688,4 +688,4 @@ }, "type" : "span", "version" : 1 -} ] \ No newline at end of file +} ] From 7e275d74a7868f7bd633419cf1420b2b9022602e Mon Sep 17 00:00:00 2001 From: Alexey Kuznetsov Date: Sun, 13 Sep 2026 11:08:46 -0400 Subject: [PATCH 2/7] Preserve Robolectric minor release codenames --- .../robolectric/RobolectricTestAnnotator.java | 16 +++++++++++-- .../RobolectricTestAnnotatorTest.groovy | 24 +++++++++++++++++++ 2 files changed, 38 insertions(+), 2 deletions(-) create mode 100644 dd-java-agent/instrumentation/robolectric-4.13/src/test/groovy/datadog/trace/instrumentation/robolectric/RobolectricTestAnnotatorTest.groovy diff --git a/dd-java-agent/instrumentation/robolectric-4.13/src/main/java/datadog/trace/instrumentation/robolectric/RobolectricTestAnnotator.java b/dd-java-agent/instrumentation/robolectric-4.13/src/main/java/datadog/trace/instrumentation/robolectric/RobolectricTestAnnotator.java index ff3ff2e811f..68b8b60ff21 100644 --- a/dd-java-agent/instrumentation/robolectric-4.13/src/main/java/datadog/trace/instrumentation/robolectric/RobolectricTestAnnotator.java +++ b/dd-java-agent/instrumentation/robolectric-4.13/src/main/java/datadog/trace/instrumentation/robolectric/RobolectricTestAnnotator.java @@ -55,10 +55,10 @@ public static void annotate() { private static String androidCodename(int apiLevel) { try { // Released Android SDKs report "REL" through Build.VERSION.CODENAME. Derive the public - // short codename (U, V, and so on) from the matching VERSION_CODES field instead. + // short codename (NMR1, Sv2, U, and so on) from the matching VERSION_CODES field instead. for (Field field : Build.VERSION_CODES.class.getFields()) { if (field.getType() == int.class && field.getInt(null) == apiLevel) { - return field.getName().substring(0, 1); + return androidCodename(field.getName()); } } } catch (Throwable t) { @@ -67,6 +67,18 @@ private static String androidCodename(int apiLevel) { return null; } + static String androidCodename(String versionCodeName) { + int minorReleaseIndex = versionCodeName.lastIndexOf("_MR"); + if (minorReleaseIndex >= 0) { + return versionCodeName.substring(0, 1) + versionCodeName.substring(minorReleaseIndex + 1); + } + int versionIndex = versionCodeName.lastIndexOf("_V"); + if (versionIndex >= 0) { + return versionCodeName.substring(0, 1) + "v" + versionCodeName.substring(versionIndex + 2); + } + return versionCodeName.substring(0, 1); + } + private static String robolectricVersion() { try { // RuntimeEnvironment is re-loaded by the sandbox classloader with no CodeSource, but the diff --git a/dd-java-agent/instrumentation/robolectric-4.13/src/test/groovy/datadog/trace/instrumentation/robolectric/RobolectricTestAnnotatorTest.groovy b/dd-java-agent/instrumentation/robolectric-4.13/src/test/groovy/datadog/trace/instrumentation/robolectric/RobolectricTestAnnotatorTest.groovy new file mode 100644 index 00000000000..230f09efa02 --- /dev/null +++ b/dd-java-agent/instrumentation/robolectric-4.13/src/test/groovy/datadog/trace/instrumentation/robolectric/RobolectricTestAnnotatorTest.groovy @@ -0,0 +1,24 @@ +package datadog.trace.instrumentation.robolectric + +import spock.lang.Specification + +class RobolectricTestAnnotatorTest extends Specification { + + def "derives #codename from #versionCodeName"() { + expect: + RobolectricTestAnnotator.androidCodename(versionCodeName) == codename + + where: + versionCodeName | codename + "JELLY_BEAN" | "J" + "JELLY_BEAN_MR1" | "JMR1" + "JELLY_BEAN_MR2" | "JMR2" + "LOLLIPOP_MR1" | "LMR1" + "N_MR1" | "NMR1" + "O_MR1" | "OMR1" + "S_V2" | "Sv2" + "TIRAMISU" | "T" + "UPSIDE_DOWN_CAKE" | "U" + "VANILLA_ICE_CREAM" | "V" + } +} From 0c0fbfd217928a5e5306e0b78e2c9df8253de32e Mon Sep 17 00:00:00 2001 From: Alexey Kuznetsov Date: Sun, 13 Sep 2026 11:14:54 -0400 Subject: [PATCH 3/7] Convert Robolectric codename test to JUnit --- .../RobolectricTestAnnotatorTest.groovy | 24 ------------------ .../RobolectricTestAnnotatorTest.java | 25 +++++++++++++++++++ 2 files changed, 25 insertions(+), 24 deletions(-) delete mode 100644 dd-java-agent/instrumentation/robolectric-4.13/src/test/groovy/datadog/trace/instrumentation/robolectric/RobolectricTestAnnotatorTest.groovy create mode 100644 dd-java-agent/instrumentation/robolectric-4.13/src/test/java/datadog/trace/instrumentation/robolectric/RobolectricTestAnnotatorTest.java diff --git a/dd-java-agent/instrumentation/robolectric-4.13/src/test/groovy/datadog/trace/instrumentation/robolectric/RobolectricTestAnnotatorTest.groovy b/dd-java-agent/instrumentation/robolectric-4.13/src/test/groovy/datadog/trace/instrumentation/robolectric/RobolectricTestAnnotatorTest.groovy deleted file mode 100644 index 230f09efa02..00000000000 --- a/dd-java-agent/instrumentation/robolectric-4.13/src/test/groovy/datadog/trace/instrumentation/robolectric/RobolectricTestAnnotatorTest.groovy +++ /dev/null @@ -1,24 +0,0 @@ -package datadog.trace.instrumentation.robolectric - -import spock.lang.Specification - -class RobolectricTestAnnotatorTest extends Specification { - - def "derives #codename from #versionCodeName"() { - expect: - RobolectricTestAnnotator.androidCodename(versionCodeName) == codename - - where: - versionCodeName | codename - "JELLY_BEAN" | "J" - "JELLY_BEAN_MR1" | "JMR1" - "JELLY_BEAN_MR2" | "JMR2" - "LOLLIPOP_MR1" | "LMR1" - "N_MR1" | "NMR1" - "O_MR1" | "OMR1" - "S_V2" | "Sv2" - "TIRAMISU" | "T" - "UPSIDE_DOWN_CAKE" | "U" - "VANILLA_ICE_CREAM" | "V" - } -} diff --git a/dd-java-agent/instrumentation/robolectric-4.13/src/test/java/datadog/trace/instrumentation/robolectric/RobolectricTestAnnotatorTest.java b/dd-java-agent/instrumentation/robolectric-4.13/src/test/java/datadog/trace/instrumentation/robolectric/RobolectricTestAnnotatorTest.java new file mode 100644 index 00000000000..a337e77fbae --- /dev/null +++ b/dd-java-agent/instrumentation/robolectric-4.13/src/test/java/datadog/trace/instrumentation/robolectric/RobolectricTestAnnotatorTest.java @@ -0,0 +1,25 @@ +package datadog.trace.instrumentation.robolectric; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.tabletest.junit.TableTest; + +class RobolectricTestAnnotatorTest { + + @TableTest({ + "scenario | versionCodeName | codename", + "major release | JELLY_BEAN | J ", + "Jelly Bean MR1 | JELLY_BEAN_MR1 | JMR1 ", + "Jelly Bean MR2 | JELLY_BEAN_MR2 | JMR2 ", + "Lollipop MR1 | LOLLIPOP_MR1 | LMR1 ", + "Nougat MR1 | N_MR1 | NMR1 ", + "Oreo MR1 | O_MR1 | OMR1 ", + "Snow Cone v2 | S_V2 | Sv2 ", + "single word | TIRAMISU | T ", + "multi-word U | UPSIDE_DOWN_CAKE | U ", + "multi-word V | VANILLA_ICE_CREAM | V " + }) + void derivesCodename(String versionCodeName, String codename) { + assertEquals(codename, RobolectricTestAnnotator.androidCodename(versionCodeName)); + } +} From ee6e311d70d57843667e51387a8079f98e1895db Mon Sep 17 00:00:00 2001 From: Daniel Mohedano Date: Mon, 14 Sep 2026 14:57:20 +0200 Subject: [PATCH 4/7] fix: avoid reflection for Robolectric Android codenames --- .../robolectric/AndroidVersionUtils.java | 51 +++++++++++++++++++ .../RobolectricInstrumentation.java | 4 +- .../robolectric/RobolectricTestAnnotator.java | 30 +---------- .../RobolectricTestAnnotatorTest.java | 25 --------- 4 files changed, 55 insertions(+), 55 deletions(-) create mode 100644 dd-java-agent/instrumentation/robolectric-4.13/src/main/java/datadog/trace/instrumentation/robolectric/AndroidVersionUtils.java delete mode 100644 dd-java-agent/instrumentation/robolectric-4.13/src/test/java/datadog/trace/instrumentation/robolectric/RobolectricTestAnnotatorTest.java diff --git a/dd-java-agent/instrumentation/robolectric-4.13/src/main/java/datadog/trace/instrumentation/robolectric/AndroidVersionUtils.java b/dd-java-agent/instrumentation/robolectric-4.13/src/main/java/datadog/trace/instrumentation/robolectric/AndroidVersionUtils.java new file mode 100644 index 00000000000..15ea07ab329 --- /dev/null +++ b/dd-java-agent/instrumentation/robolectric-4.13/src/main/java/datadog/trace/instrumentation/robolectric/AndroidVersionUtils.java @@ -0,0 +1,51 @@ +package datadog.trace.instrumentation.robolectric; + +public final class AndroidVersionUtils { + + private static final String[] CODENAMES = { + null, + "BASE", + "BASE_1_1", + "CUPCAKE", + "DONUT", + "ECLAIR", + "ECLAIR_0_1", + "ECLAIR_MR1", + "FROYO", + "GINGERBREAD", + "GINGERBREAD_MR1", + "HONEYCOMB", + "HONEYCOMB_MR1", + "HONEYCOMB_MR2", + "ICE_CREAM_SANDWICH", + "ICE_CREAM_SANDWICH_MR1", + "JELLY_BEAN", + "JELLY_BEAN_MR1", + "JELLY_BEAN_MR2", + "KITKAT", + "KITKAT_WATCH", + "LOLLIPOP", + "LOLLIPOP_MR1", + "M", + "N", + "N_MR1", + "O", + "O_MR1", + "P", + "Q", + "R", + "S", + "S_V2", + "TIRAMISU", + "UPSIDE_DOWN_CAKE", + "VANILLA_ICE_CREAM", + "BAKLAVA", + "CINNAMON_BUN" + }; + + private AndroidVersionUtils() {} + + public static String codename(int apiLevel) { + return apiLevel > 0 && apiLevel < CODENAMES.length ? CODENAMES[apiLevel] : null; + } +} diff --git a/dd-java-agent/instrumentation/robolectric-4.13/src/main/java/datadog/trace/instrumentation/robolectric/RobolectricInstrumentation.java b/dd-java-agent/instrumentation/robolectric-4.13/src/main/java/datadog/trace/instrumentation/robolectric/RobolectricInstrumentation.java index d58de1dc64b..f6302b05a8c 100644 --- a/dd-java-agent/instrumentation/robolectric-4.13/src/main/java/datadog/trace/instrumentation/robolectric/RobolectricInstrumentation.java +++ b/dd-java-agent/instrumentation/robolectric-4.13/src/main/java/datadog/trace/instrumentation/robolectric/RobolectricInstrumentation.java @@ -37,7 +37,9 @@ public ElementMatcher hierarchyMatcher() { @Override public String[] helperClassNames() { - return new String[] {packageName + ".RobolectricTestAnnotator"}; + return new String[] { + packageName + ".AndroidVersionUtils", packageName + ".RobolectricTestAnnotator" + }; } @Override diff --git a/dd-java-agent/instrumentation/robolectric-4.13/src/main/java/datadog/trace/instrumentation/robolectric/RobolectricTestAnnotator.java b/dd-java-agent/instrumentation/robolectric-4.13/src/main/java/datadog/trace/instrumentation/robolectric/RobolectricTestAnnotator.java index 68b8b60ff21..ee118b46092 100644 --- a/dd-java-agent/instrumentation/robolectric-4.13/src/main/java/datadog/trace/instrumentation/robolectric/RobolectricTestAnnotator.java +++ b/dd-java-agent/instrumentation/robolectric-4.13/src/main/java/datadog/trace/instrumentation/robolectric/RobolectricTestAnnotator.java @@ -7,7 +7,6 @@ import datadog.trace.bootstrap.instrumentation.api.AgentTracer; import datadog.trace.bootstrap.instrumentation.api.Tags; import java.io.File; -import java.lang.reflect.Field; import java.net.URL; import java.security.CodeSource; import java.security.ProtectionDomain; @@ -42,7 +41,7 @@ public static void annotate() { span.setTag(Tags.TEST_ANDROID_API_LEVEL, apiLevel); span.setTag(Tags.TEST_ANDROID_RELEASE, Build.VERSION.RELEASE); - String androidCodename = androidCodename(apiLevel); + String androidCodename = AndroidVersionUtils.codename(apiLevel); if (androidCodename != null) { span.setTag(Tags.TEST_ANDROID_CODENAME, androidCodename); } @@ -52,33 +51,6 @@ public static void annotate() { } } - private static String androidCodename(int apiLevel) { - try { - // Released Android SDKs report "REL" through Build.VERSION.CODENAME. Derive the public - // short codename (NMR1, Sv2, U, and so on) from the matching VERSION_CODES field instead. - for (Field field : Build.VERSION_CODES.class.getFields()) { - if (field.getType() == int.class && field.getInt(null) == apiLevel) { - return androidCodename(field.getName()); - } - } - } catch (Throwable t) { - // Ignore missing or inaccessible fields and omit the optional codename tag. - } - return null; - } - - static String androidCodename(String versionCodeName) { - int minorReleaseIndex = versionCodeName.lastIndexOf("_MR"); - if (minorReleaseIndex >= 0) { - return versionCodeName.substring(0, 1) + versionCodeName.substring(minorReleaseIndex + 1); - } - int versionIndex = versionCodeName.lastIndexOf("_V"); - if (versionIndex >= 0) { - return versionCodeName.substring(0, 1) + "v" + versionCodeName.substring(versionIndex + 2); - } - return versionCodeName.substring(0, 1); - } - private static String robolectricVersion() { try { // RuntimeEnvironment is re-loaded by the sandbox classloader with no CodeSource, but the diff --git a/dd-java-agent/instrumentation/robolectric-4.13/src/test/java/datadog/trace/instrumentation/robolectric/RobolectricTestAnnotatorTest.java b/dd-java-agent/instrumentation/robolectric-4.13/src/test/java/datadog/trace/instrumentation/robolectric/RobolectricTestAnnotatorTest.java deleted file mode 100644 index a337e77fbae..00000000000 --- a/dd-java-agent/instrumentation/robolectric-4.13/src/test/java/datadog/trace/instrumentation/robolectric/RobolectricTestAnnotatorTest.java +++ /dev/null @@ -1,25 +0,0 @@ -package datadog.trace.instrumentation.robolectric; - -import static org.junit.jupiter.api.Assertions.assertEquals; - -import org.tabletest.junit.TableTest; - -class RobolectricTestAnnotatorTest { - - @TableTest({ - "scenario | versionCodeName | codename", - "major release | JELLY_BEAN | J ", - "Jelly Bean MR1 | JELLY_BEAN_MR1 | JMR1 ", - "Jelly Bean MR2 | JELLY_BEAN_MR2 | JMR2 ", - "Lollipop MR1 | LOLLIPOP_MR1 | LMR1 ", - "Nougat MR1 | N_MR1 | NMR1 ", - "Oreo MR1 | O_MR1 | OMR1 ", - "Snow Cone v2 | S_V2 | Sv2 ", - "single word | TIRAMISU | T ", - "multi-word U | UPSIDE_DOWN_CAKE | U ", - "multi-word V | VANILLA_ICE_CREAM | V " - }) - void derivesCodename(String versionCodeName, String codename) { - assertEquals(codename, RobolectricTestAnnotator.androidCodename(versionCodeName)); - } -} From 3a1adeb8f3f86d2cfc23968797479e7ac5960336 Mon Sep 17 00:00:00 2001 From: Daniel Mohedano Date: Mon, 14 Sep 2026 14:57:52 +0200 Subject: [PATCH 5/7] test: cover fixed and latest Robolectric versions --- .../smoketest/GradleDaemonSmokeTest.java | 57 ++++++++++++++++--- .../test-succeed-robolectric/build.gradleTest | 2 +- .../test-succeed-robolectric/events.ftl | 8 +-- 3 files changed, 55 insertions(+), 12 deletions(-) diff --git a/dd-smoke-tests/gradle/src/test/java/datadog/smoketest/GradleDaemonSmokeTest.java b/dd-smoke-tests/gradle/src/test/java/datadog/smoketest/GradleDaemonSmokeTest.java index c334a361d06..c3d8be6a2f4 100644 --- a/dd-smoke-tests/gradle/src/test/java/datadog/smoketest/GradleDaemonSmokeTest.java +++ b/dd-smoke-tests/gradle/src/test/java/datadog/smoketest/GradleDaemonSmokeTest.java @@ -144,14 +144,20 @@ void testNew( } @TableTest({ - "scenario | gradleVersion | projectName | expectedTraces", - "robolectric-latest | latest | test-succeed-robolectric | 7 " + "scenario | gradleVersion | robolectricVersion | expectedVersion | projectName | expectedTraces", + "robolectric-4.16 | latest | 4.16.1 | 4.16.1 | test-succeed-robolectric | 7 ", + "robolectric-latest | latest | + | any | test-succeed-robolectric | 7 " }) @ParameterizedTest - void testRobolectric(String gradleVersion, String projectName, int expectedTraces) + void testRobolectric( + String gradleVersion, + String robolectricVersion, + String expectedVersion, + String projectName, + int expectedTraces) throws IOException { Assumptions.assumeTrue( - JavaVirtualMachine.isJavaVersionBetween(17, 22), "Robolectric 4.17 supports JDK 17-21"); + JavaVirtualMachine.isJavaVersionBetween(17, 22), "Robolectric supports JDK 17-21"); Assumptions.assumeFalse( OperatingSystem.architecture().isArm64(), "Robolectric does not support arm64 (missing native runtime binaries, follow https://github.com/robolectric/robolectric/issues/9166)"); @@ -163,15 +169,42 @@ void testRobolectric(String gradleVersion, String projectName, int expectedTrace givenGradleProjectProperties(); ensureDependenciesDownloaded(gradleVersion); - BuildResult buildResult = runGradleTests(gradleVersion, true, false); + BuildResult buildResult = + runGradleTests(gradleVersion, true, false, "-ProbolectricVersion=" + robolectricVersion); assertBuildSuccessful(buildResult); + List> events = mockBackend.waitForEvents(expectedTraces); + assertRobolectricVersion(events, expectedVersion); verifyEventsAndCoverages( projectName, "gradle", gradleVersion, - mockBackend.waitForEvents(expectedTraces), - mockBackend.waitForCoverages(0)); + events, + mockBackend.waitForCoverages(0), + Collections.singletonList("content.meta.['test.android.robolectric.version']")); + } + + private static void assertRobolectricVersion( + List> events, String expectedVersion) { + int taggedEvents = 0; + for (Map event : events) { + Object content = event.get("content"); + if (!(content instanceof Map)) { + continue; + } + Object meta = ((Map) content).get("meta"); + if (!(meta instanceof Map)) { + continue; + } + Object version = ((Map) meta).get("test.android.robolectric.version"); + if (version != null) { + if (!"any".equals(expectedVersion)) { + assertEquals(expectedVersion, version); + } + taggedEvents++; + } + } + assertEquals(2, taggedEvents); } @TableTest({ @@ -371,7 +404,17 @@ private void givenGradleProjectProperties(Map additionalArgs) th private BuildResult runGradleTests( String gradleVersion, boolean successExpected, boolean configurationCache) throws IOException { + return runGradleTests(gradleVersion, successExpected, configurationCache, new String[0]); + } + + private BuildResult runGradleTests( + String gradleVersion, + boolean successExpected, + boolean configurationCache, + String... additionalArguments) + throws IOException { List arguments = new java.util.ArrayList<>(Arrays.asList("test", "--stacktrace")); + arguments.addAll(Arrays.asList(additionalArguments)); if (gradleVersion.compareTo("4.5") > 0) { // warning mode available starting from Gradle 4.5 arguments.addAll(Arrays.asList("--warning-mode", "all")); diff --git a/dd-smoke-tests/gradle/src/test/resources/test-succeed-robolectric/build.gradleTest b/dd-smoke-tests/gradle/src/test/resources/test-succeed-robolectric/build.gradleTest index 52e930174a1..a126426e4eb 100644 --- a/dd-smoke-tests/gradle/src/test/resources/test-succeed-robolectric/build.gradleTest +++ b/dd-smoke-tests/gradle/src/test/resources/test-succeed-robolectric/build.gradleTest @@ -96,7 +96,7 @@ configurations.configureEach { dependencies { testImplementation 'junit:junit:4.13.2' - testImplementation 'org.robolectric:robolectric:4.17' + testImplementation "org.robolectric:robolectric:${robolectricVersion}" // Pre-built Android SDK jar for the level the fixtures configure. testImplementation 'org.robolectric:android-all:14-robolectric-10818077' // androidx.test:core pulls in androidx.test:monitor (InstrumentationRegistry); ext:junit provides diff --git a/dd-smoke-tests/gradle/src/test/resources/test-succeed-robolectric/events.ftl b/dd-smoke-tests/gradle/src/test/resources/test-succeed-robolectric/events.ftl index d9c3587bac1..d0d7ed7d09d 100644 --- a/dd-smoke-tests/gradle/src/test/resources/test-succeed-robolectric/events.ftl +++ b/dd-smoke-tests/gradle/src/test/resources/test-succeed-robolectric/events.ftl @@ -387,9 +387,9 @@ "runtime.vendor" : ${content_meta_runtime_vendor}, "runtime.version" : ${content_meta_runtime_version}, "span.kind" : "test", - "test.android.codename" : "U", + "test.android.codename" : "UPSIDE_DOWN_CAKE", "test.android.release" : "14", - "test.android.robolectric.version" : "4.17", + "test.android.robolectric.version" : ${content_meta_test_android_robolectric_version}, "test.final_status" : "pass", "test.framework" : "junit4", "test.framework_version" : "4.13.2", @@ -505,9 +505,9 @@ "runtime.vendor" : ${content_meta_runtime_vendor}, "runtime.version" : ${content_meta_runtime_version}, "span.kind" : "test", - "test.android.codename" : "U", + "test.android.codename" : "UPSIDE_DOWN_CAKE", "test.android.release" : "14", - "test.android.robolectric.version" : "4.17", + "test.android.robolectric.version" : ${content_meta_test_android_robolectric_version}, "test.final_status" : "pass", "test.framework" : "junit4", "test.framework_version" : "4.13.2", From 4b4b8453e20983e1831999e74e77806ac8bfb602 Mon Sep 17 00:00:00 2001 From: Daniel Mohedano Date: Mon, 14 Sep 2026 15:54:04 +0200 Subject: [PATCH 6/7] fix: smoke test version resolution --- .../smoketest/GradleDaemonSmokeTest.java | 23 ++++++++++++++----- .../test-succeed-robolectric/build.gradleTest | 2 +- 2 files changed, 18 insertions(+), 7 deletions(-) diff --git a/dd-smoke-tests/gradle/src/test/java/datadog/smoketest/GradleDaemonSmokeTest.java b/dd-smoke-tests/gradle/src/test/java/datadog/smoketest/GradleDaemonSmokeTest.java index c3d8be6a2f4..84b6f6a4ae8 100644 --- a/dd-smoke-tests/gradle/src/test/java/datadog/smoketest/GradleDaemonSmokeTest.java +++ b/dd-smoke-tests/gradle/src/test/java/datadog/smoketest/GradleDaemonSmokeTest.java @@ -169,8 +169,9 @@ void testRobolectric( givenGradleProjectProperties(); ensureDependenciesDownloaded(gradleVersion); - BuildResult buildResult = - runGradleTests(gradleVersion, true, false, "-ProbolectricVersion=" + robolectricVersion); + Map additionalEnvVars = + Collections.singletonMap("SMOKE_TEST_ROBOLECTRIC_VERSION", robolectricVersion); + BuildResult buildResult = runGradleTests(gradleVersion, true, false, additionalEnvVars); assertBuildSuccessful(buildResult); List> events = mockBackend.waitForEvents(expectedTraces); @@ -404,17 +405,17 @@ private void givenGradleProjectProperties(Map additionalArgs) th private BuildResult runGradleTests( String gradleVersion, boolean successExpected, boolean configurationCache) throws IOException { - return runGradleTests(gradleVersion, successExpected, configurationCache, new String[0]); + return runGradleTests( + gradleVersion, successExpected, configurationCache, Collections.emptyMap()); } private BuildResult runGradleTests( String gradleVersion, boolean successExpected, boolean configurationCache, - String... additionalArguments) + Map additionalEnvVars) throws IOException { List arguments = new java.util.ArrayList<>(Arrays.asList("test", "--stacktrace")); - arguments.addAll(Arrays.asList(additionalArguments)); if (gradleVersion.compareTo("4.5") > 0) { // warning mode available starting from Gradle 4.5 arguments.addAll(Arrays.asList("--warning-mode", "all")); @@ -422,7 +423,7 @@ private BuildResult runGradleTests( if (configurationCache) { arguments.addAll(Arrays.asList("--configuration-cache", "--rerun-tasks")); } - return runGradle(gradleVersion, arguments, successExpected); + return runGradle(gradleVersion, arguments, successExpected, additionalEnvVars); } /** @@ -460,6 +461,15 @@ private void ensureDependenciesDownloaded(String gradleVersion) { private BuildResult runGradle( String gradleVersion, List arguments, boolean successExpected) throws IOException { + return runGradle(gradleVersion, arguments, successExpected, Collections.emptyMap()); + } + + private BuildResult runGradle( + String gradleVersion, + List arguments, + boolean successExpected, + Map additionalEnvVars) + throws IOException { Map buildEnv = new HashMap<>(); buildEnv.put("GRADLE_ARGS", ""); buildEnv.put("GRADLE_OPTS", ""); @@ -468,6 +478,7 @@ private BuildResult runGradle( buildEnv.put( GradleDistribution.GRADLE_DISTRIBUTION_URL_ENV, GradleDistribution.uriFor(gradleVersion).toString()); + buildEnv.putAll(additionalEnvVars); String mavenRepositoryProxy = System.getenv("MAVEN_REPOSITORY_PROXY"); if (mavenRepositoryProxy != null) { diff --git a/dd-smoke-tests/gradle/src/test/resources/test-succeed-robolectric/build.gradleTest b/dd-smoke-tests/gradle/src/test/resources/test-succeed-robolectric/build.gradleTest index a126426e4eb..99f5d4d702e 100644 --- a/dd-smoke-tests/gradle/src/test/resources/test-succeed-robolectric/build.gradleTest +++ b/dd-smoke-tests/gradle/src/test/resources/test-succeed-robolectric/build.gradleTest @@ -96,7 +96,7 @@ configurations.configureEach { dependencies { testImplementation 'junit:junit:4.13.2' - testImplementation "org.robolectric:robolectric:${robolectricVersion}" + testImplementation "org.robolectric:robolectric:${System.getenv('SMOKE_TEST_ROBOLECTRIC_VERSION')}" // Pre-built Android SDK jar for the level the fixtures configure. testImplementation 'org.robolectric:android-all:14-robolectric-10818077' // androidx.test:core pulls in androidx.test:monitor (InstrumentationRegistry); ext:junit provides From 6cd6bbc53beae6ea7225d884f1c5cac713f268a5 Mon Sep 17 00:00:00 2001 From: Alexey Kuznetsov Date: Mon, 14 Sep 2026 11:32:35 -0400 Subject: [PATCH 7/7] Rollback temporarey fix. --- dd-java-agent/instrumentation/robolectric-4.13/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dd-java-agent/instrumentation/robolectric-4.13/build.gradle b/dd-java-agent/instrumentation/robolectric-4.13/build.gradle index 9f2ddb9f13e..c9cd551e9e1 100644 --- a/dd-java-agent/instrumentation/robolectric-4.13/build.gradle +++ b/dd-java-agent/instrumentation/robolectric-4.13/build.gradle @@ -7,7 +7,7 @@ muzzle { pass { group = 'org.robolectric' module = 'robolectric' - versions = '[4.13,4.17)' + versions = '[4.13,)' // androidx.test:monitor is an Android archive (.aar) that a JVM configuration cannot consume; // it is not referenced by the advice/helper. Mirror the compileOnly exclusion below. excludeDependency 'androidx.test:monitor'