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..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 @@ -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,33 @@ 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 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)); + } +} 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 +} ]