Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/CODEOWNERS
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@
/dd-java-agent/instrumentation/jacoco-0.8.9/ @DataDog/ci-app-libraries
/dd-java-agent/instrumentation/junit @DataDog/ci-app-libraries
/dd-java-agent/instrumentation/karate-1.0/ @DataDog/ci-app-libraries
/dd-java-agent/instrumentation/robolectric/ @DataDog/ci-app-libraries
/dd-java-agent/instrumentation/scalatest-3.0.8/ @DataDog/ci-app-libraries
/dd-java-agent/instrumentation/selenium-3.13/ @DataDog/ci-app-libraries
/dd-java-agent/instrumentation/testng/ @DataDog/ci-app-libraries
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import datadog.trace.api.civisibility.telemetry.tag.EventType;
import datadog.trace.api.civisibility.telemetry.tag.FailedTestReplayEnabled;
import datadog.trace.api.civisibility.telemetry.tag.HasFailedAllRetries;
import datadog.trace.api.civisibility.telemetry.tag.IsAndroid;
import datadog.trace.api.civisibility.telemetry.tag.IsAttemptToFix;
import datadog.trace.api.civisibility.telemetry.tag.IsDisabled;
import datadog.trace.api.civisibility.telemetry.tag.IsModified;
Expand Down Expand Up @@ -323,7 +324,8 @@ public void end(@Nullable Long endTime) {
span.getTag(Tags.TEST_IS_RUM_ACTIVE) != null ? IsRum.TRUE : null,
CIConstants.SELENIUM_BROWSER_DRIVER.equals(span.getTag(Tags.TEST_BROWSER_DRIVER))
? BrowserDriver.SELENIUM
: null);
: null,
span.getTag(Tags.TEST_ANDROID_API_LEVEL) != null ? IsAndroid.TRUE : null);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
import datadog.trace.api.civisibility.CIConstants;
import datadog.trace.api.civisibility.DDTest;
import datadog.trace.api.civisibility.DDTestSuite;
import datadog.trace.api.civisibility.android.AndroidTestContext;
import datadog.trace.api.civisibility.android.AndroidTestInfo;
import datadog.trace.api.civisibility.config.TestIdentifier;
import datadog.trace.api.civisibility.config.TestSourceData;
import datadog.trace.api.civisibility.events.TestEventsHandler;
Expand Down Expand Up @@ -220,6 +222,24 @@ public void onTestStart(
inProgressTests.put(descriptor, test);
}

/** Applies emulated Android SDK metadata captured by the Robolectric instrumentation. */
private void populateAndroidTags(TestImpl test) {
AndroidTestInfo androidInfo = AndroidTestContext.getAndClear();
if (androidInfo == null) {
return;
}
test.setTag(Tags.TEST_ANDROID_API_LEVEL, androidInfo.getApiLevel());
if (androidInfo.getRelease() != null) {
test.setTag(Tags.TEST_ANDROID_RELEASE, androidInfo.getRelease());
}
if (androidInfo.getCodename() != null) {
test.setTag(Tags.TEST_ANDROID_CODENAME, androidInfo.getCodename());
}
if (androidInfo.getRobolectricVersion() != null) {
test.setTag(Tags.TEST_ANDROID_ROBOLECTRIC_VERSION, androidInfo.getRobolectricVersion());
}
}

@Override
public void onTestSkip(TestKey descriptor, @Nullable String reason) {
TestImpl test = inProgressTests.get(descriptor);
Expand Down Expand Up @@ -284,6 +304,8 @@ public void onTestFinish(
test.setTag(Tags.TEST_FINAL_STATUS, testStatus);
}

populateAndroidTags(test);

test.end(endTime);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
muzzle {
pass {
group = 'org.robolectric'
module = 'robolectric'
versions = '[4.11,)'
}
}

apply from: "$rootDir/gradle/java.gradle"

dependencies {
// Instrumentation is validated through a scenario in `GradleDaemonSmokeTest`.
// Exclude androidx.test:monitor: it is an Android archive (.aar) published only to Google's Maven
// repository, is not referenced by the advice/helper.
compileOnly(group: 'org.robolectric', name: 'robolectric', version: '4.16.1') {
exclude group: 'androidx.test', module: 'monitor'
}
// 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'
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
# This is a Gradle generated file for dependency locking.
# Manual edits can break the build and are not advised.
# This file is expected to be part of source control.
# To regenerate this file, run: ./gradlew :dd-java-agent:instrumentation:robolectric:robolectric-4.11:dependencies --write-locks
cafe.cryptography:curve25519-elisabeth:0.1.0=testRuntimeClasspath
cafe.cryptography:ed25519-elisabeth:0.1.0=testRuntimeClasspath
ch.qos.logback:logback-classic:1.2.13=testCompileClasspath,testRuntimeClasspath
ch.qos.logback:logback-core:1.2.13=testCompileClasspath,testRuntimeClasspath
com.almworks.sqlite4java:sqlite4java:1.0.392=compileClasspath
com.blogspot.mydailyjava:weak-lock-free:0.17=buildTimeInstrumentationPlugin,compileClasspath,muzzleTooling,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.datadoghq.okhttp3:okhttp:3.12.15=testCompileClasspath,testRuntimeClasspath
com.datadoghq.okio:okio:1.17.6=testCompileClasspath,testRuntimeClasspath
com.datadoghq:dd-instrument-java:0.0.4=buildTimeInstrumentationPlugin,compileClasspath,muzzleBootstrap,muzzleTooling,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.datadoghq:dd-javac-plugin-client:0.2.2=buildTimeInstrumentationPlugin,compileClasspath,muzzleBootstrap,muzzleTooling,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.datadoghq:java-dogstatsd-client:4.4.5=testRuntimeClasspath
com.datadoghq:sketches-java:0.8.3=testRuntimeClasspath
com.github.javaparser:javaparser-core:3.25.6=codenarc
com.github.jnr:jffi:1.3.15=testRuntimeClasspath
com.github.jnr:jnr-a64asm:1.0.0=testRuntimeClasspath
com.github.jnr:jnr-constants:0.10.4=testRuntimeClasspath
com.github.jnr:jnr-enxio:0.32.20=testRuntimeClasspath
com.github.jnr:jnr-ffi:2.2.19=testRuntimeClasspath
com.github.jnr:jnr-posix:3.1.22=testRuntimeClasspath
com.github.jnr:jnr-unixsocket:0.38.25=testRuntimeClasspath
com.github.jnr:jnr-x86asm:1.0.2=testRuntimeClasspath
com.github.spotbugs:spotbugs-annotations:4.9.8=compileClasspath,spotbugs
com.github.spotbugs:spotbugs:4.9.8=spotbugs
com.github.stephenc.jcip:jcip-annotations:1.0-1=spotbugs
com.google.auto.service:auto-service-annotations:1.1.1=annotationProcessor,compileClasspath,testAnnotationProcessor,testCompileClasspath
com.google.auto.service:auto-service:1.1.1=annotationProcessor,testAnnotationProcessor
com.google.auto.value:auto-value-annotations:1.11.0=compileClasspath
com.google.auto:auto-common:1.2.1=annotationProcessor,testAnnotationProcessor
com.google.code.findbugs:jsr305:3.0.2=annotationProcessor,compileClasspath,spotbugs,testAnnotationProcessor,testCompileClasspath,testRuntimeClasspath
com.google.code.gson:gson:2.13.2=spotbugs
com.google.errorprone:error_prone_annotations:2.18.0=annotationProcessor,testAnnotationProcessor
com.google.errorprone:error_prone_annotations:2.36.0=compileClasspath
com.google.errorprone:error_prone_annotations:2.41.0=spotbugs
com.google.errorprone:error_prone_annotations:2.47.0=testCompileClasspath,testRuntimeClasspath
com.google.guava:failureaccess:1.0.1=annotationProcessor,testAnnotationProcessor
com.google.guava:failureaccess:1.0.3=compileClasspath,testCompileClasspath,testRuntimeClasspath
com.google.guava:guava:32.0.1-jre=annotationProcessor,testAnnotationProcessor
com.google.guava:guava:33.4.8-jre=compileClasspath
com.google.guava:guava:33.6.0-jre=testCompileClasspath,testRuntimeClasspath
com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava=annotationProcessor,compileClasspath,testAnnotationProcessor,testCompileClasspath,testRuntimeClasspath
com.google.j2objc:j2objc-annotations:2.8=annotationProcessor,testAnnotationProcessor
com.google.j2objc:j2objc-annotations:3.0.0=compileClasspath
com.google.j2objc:j2objc-annotations:3.1=testCompileClasspath,testRuntimeClasspath
com.google.re2j:re2j:1.8=testRuntimeClasspath
com.ibm.icu:icu4j:77.1=compileClasspath
com.squareup.moshi:moshi:1.11.0=testCompileClasspath,testRuntimeClasspath
com.squareup.okhttp3:logging-interceptor:3.12.12=testCompileClasspath,testRuntimeClasspath
com.squareup.okhttp3:okhttp:3.12.12=testCompileClasspath,testRuntimeClasspath
com.squareup.okio:okio:1.17.5=testCompileClasspath,testRuntimeClasspath
com.thoughtworks.qdox:qdox:1.12.1=codenarc
commons-fileupload:commons-fileupload:1.5=testCompileClasspath,testRuntimeClasspath
commons-io:commons-io:2.11.0=testCompileClasspath,testRuntimeClasspath
commons-io:commons-io:2.20.0=spotbugs
de.thetaphi:forbiddenapis:3.10=compileClasspath,testCompileClasspath,testRuntimeClasspath
io.leangen.geantyref:geantyref:1.3.16=testRuntimeClasspath
io.sqreen:libsqreen:17.3.0=testRuntimeClasspath
javax.annotation:javax.annotation-api:1.3.2=compileClasspath
javax.inject:javax.inject:1=compileClasspath
javax.servlet:javax.servlet-api:3.1.0=testCompileClasspath,testRuntimeClasspath
jaxen:jaxen:2.0.0=spotbugs
junit:junit:4.13.2=compileClasspath,testRuntimeClasspath
net.bytebuddy:byte-buddy-agent:1.18.10=buildTimeInstrumentationPlugin,compileClasspath,muzzleTooling,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
net.bytebuddy:byte-buddy:1.18.10=buildTimeInstrumentationPlugin,compileClasspath,muzzleTooling,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
net.java.dev.jna:jna-platform:5.8.0=testRuntimeClasspath
net.java.dev.jna:jna:5.8.0=testRuntimeClasspath
net.sf.saxon:Saxon-HE:12.9=spotbugs
org.apache.ant:ant-antlr:1.10.14=codenarc
org.apache.ant:ant-junit:1.10.14=codenarc
org.apache.bcel:bcel:6.11.0=spotbugs
org.apache.commons:commons-lang3:3.19.0=spotbugs
org.apache.commons:commons-text:1.14.0=spotbugs
org.apache.logging.log4j:log4j-api:2.25.2=spotbugs
org.apache.logging.log4j:log4j-core:2.25.2=spotbugs
org.apiguardian:apiguardian-api:1.1.2=testCompileClasspath,testRuntimeClasspath
org.bouncycastle:bcprov-jdk18on:1.81=compileClasspath
org.checkerframework:checker-qual:3.33.0=annotationProcessor,testAnnotationProcessor
org.codehaus.groovy:groovy-ant:3.0.23=codenarc
org.codehaus.groovy:groovy-docgenerator:3.0.23=codenarc
org.codehaus.groovy:groovy-groovydoc:3.0.23=codenarc
org.codehaus.groovy:groovy-json:3.0.23=codenarc
org.codehaus.groovy:groovy-json:3.0.25=testCompileClasspath,testRuntimeClasspath
org.codehaus.groovy:groovy-templates:3.0.23=codenarc
org.codehaus.groovy:groovy-xml:3.0.23=codenarc
org.codehaus.groovy:groovy:3.0.23=codenarc
org.codehaus.groovy:groovy:3.0.25=testCompileClasspath,testRuntimeClasspath
org.codenarc:CodeNarc:3.7.0=codenarc
org.dom4j:dom4j:2.2.0=spotbugs
org.gmetrics:GMetrics:2.1.0=codenarc
org.hamcrest:hamcrest-core:1.3=compileClasspath,testRuntimeClasspath
org.hamcrest:hamcrest:3.0=testCompileClasspath,testRuntimeClasspath
org.jctools:jctools-core-jdk11:4.0.6=testRuntimeClasspath
org.jctools:jctools-core:4.0.6=testRuntimeClasspath
org.jspecify:jspecify:1.0.0=compileClasspath,testCompileClasspath,testRuntimeClasspath
org.junit.jupiter:junit-jupiter-api:5.14.1=testCompileClasspath,testRuntimeClasspath
org.junit.jupiter:junit-jupiter-engine:5.14.1=testRuntimeClasspath
org.junit.jupiter:junit-jupiter-params:5.14.1=testCompileClasspath,testRuntimeClasspath
org.junit.jupiter:junit-jupiter:5.14.1=testCompileClasspath,testRuntimeClasspath
org.junit.platform:junit-platform-commons:1.14.1=testCompileClasspath,testRuntimeClasspath
org.junit.platform:junit-platform-engine:1.14.1=testCompileClasspath,testRuntimeClasspath
org.junit.platform:junit-platform-launcher:1.14.1=testRuntimeClasspath
org.junit.platform:junit-platform-runner:1.14.1=testRuntimeClasspath
org.junit.platform:junit-platform-suite-api:1.14.1=testRuntimeClasspath
org.junit.platform:junit-platform-suite-commons:1.14.1=testRuntimeClasspath
org.junit:junit-bom:5.14.0=spotbugs
org.junit:junit-bom:5.14.1=testCompileClasspath,testRuntimeClasspath
org.mockito:mockito-core:4.4.0=testRuntimeClasspath
org.objenesis:objenesis:3.3=testCompileClasspath,testRuntimeClasspath
org.opentest4j:opentest4j:1.3.0=testCompileClasspath,testRuntimeClasspath
org.ow2.asm:asm-analysis:9.7.1=testRuntimeClasspath
org.ow2.asm:asm-analysis:9.9=spotbugs
org.ow2.asm:asm-commons:9.8=compileClasspath
org.ow2.asm:asm-commons:9.9=spotbugs
org.ow2.asm:asm-commons:9.9.1=testRuntimeClasspath
org.ow2.asm:asm-tree:9.8=compileClasspath
org.ow2.asm:asm-tree:9.9=spotbugs
org.ow2.asm:asm-tree:9.9.1=testRuntimeClasspath
org.ow2.asm:asm-util:9.7.1=testRuntimeClasspath
org.ow2.asm:asm-util:9.9=spotbugs
org.ow2.asm:asm:9.9=spotbugs
org.ow2.asm:asm:9.9.1=buildTimeInstrumentationPlugin,compileClasspath,muzzleTooling,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.robolectric:annotations:4.16.1=compileClasspath
org.robolectric:junit:4.16.1=compileClasspath
org.robolectric:nativeruntime:4.16.1=compileClasspath
org.robolectric:pluginapi:4.16.1=compileClasspath
org.robolectric:plugins-maven-dependency-resolver:4.16.1=compileClasspath
org.robolectric:resources:4.16.1=compileClasspath
org.robolectric:robolectric:4.16.1=compileClasspath
org.robolectric:sandbox:4.16.1=compileClasspath
org.robolectric:shadowapi:4.16.1=compileClasspath
org.robolectric:shadows-framework:4.16.1=compileClasspath
org.robolectric:utils-reflector:4.16.1=compileClasspath
org.robolectric:utils:4.16.1=compileClasspath
org.slf4j:jcl-over-slf4j:1.7.30=testCompileClasspath,testRuntimeClasspath
org.slf4j:jul-to-slf4j:1.7.30=testCompileClasspath,testRuntimeClasspath
org.slf4j:log4j-over-slf4j:1.7.30=testCompileClasspath,testRuntimeClasspath
org.slf4j:slf4j-api:1.7.30=buildTimeInstrumentationPlugin,compileClasspath,muzzleBootstrap,muzzleTooling,runtimeClasspath
org.slf4j:slf4j-api:1.7.32=testCompileClasspath,testRuntimeClasspath
org.slf4j:slf4j-api:2.0.17=spotbugs,spotbugsSlf4j
org.slf4j:slf4j-simple:2.0.17=spotbugsSlf4j
org.snakeyaml:snakeyaml-engine:2.9=buildTimeInstrumentationPlugin,muzzleTooling,runtimeClasspath,testRuntimeClasspath
org.spockframework:spock-bom:2.4-groovy-3.0=testCompileClasspath,testRuntimeClasspath
org.spockframework:spock-core:2.4-groovy-3.0=testCompileClasspath,testRuntimeClasspath
org.tabletest:tabletest-junit:1.2.1=testCompileClasspath,testRuntimeClasspath
org.tabletest:tabletest-parser:1.2.0=testCompileClasspath,testRuntimeClasspath
org.xmlresolver:xmlresolver:5.3.3=spotbugs
empty=spotbugsPlugins
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package datadog.trace.instrumentation.robolectric;

import static datadog.trace.agent.tooling.bytebuddy.matcher.HierarchyMatchers.implementsInterface;
import static datadog.trace.agent.tooling.bytebuddy.matcher.NameMatchers.named;

import com.google.auto.service.AutoService;
import datadog.trace.agent.tooling.Instrumenter;
import datadog.trace.agent.tooling.InstrumenterModule;
import net.bytebuddy.asm.Advice;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.matcher.ElementMatcher;

/**
* Captures the emulated Android SDK for tests running under Robolectric.
*
* <p>Robolectric establishes the emulated SDK in {@code TestEnvironment#setUpApplicationState},
* which runs on the per-SDK sandbox "main" thread right before the test body (the SDK is not yet
* set when the JUnit test-start event fires, and is torn down before the finish event). This
* instrumentation reads the SDK there and stashes it in {@link
* datadog.trace.api.civisibility.android.AndroidTestContext}; the CI Visibility core drains it and
* attaches the {@code test.android.*} tags to the test span (see {@code
* TestEventsHandlerImpl#onTestFinish}).
*
* <p>Robolectric tests are JUnit tests, so their spans are still produced by the JUnit
* instrumentation — this only enriches them with the Android metadata, and only loads when
* Robolectric is on the classpath.
*/
@AutoService(InstrumenterModule.class)
public class RobolectricInstrumentation extends InstrumenterModule.CiVisibility
implements Instrumenter.ForTypeHierarchy, Instrumenter.HasMethodAdvice {

public RobolectricInstrumentation() {
super("ci-visibility", "robolectric");
}

@Override
public String hierarchyMarkerType() {
return "org.robolectric.internal.TestEnvironment";
}

@Override
public ElementMatcher<TypeDescription> hierarchyMatcher() {
return implementsInterface(named(hierarchyMarkerType()));
}

@Override
public String[] helperClassNames() {
return new String[] {packageName + ".RobolectricTestExtractor"};
}

@Override
public void methodAdvice(MethodTransformer transformer) {
transformer.applyAdvice(
named("setUpApplicationState"), getClass().getName() + "$SetUpApplicationStateAdvice");
}

public static class SetUpApplicationStateAdvice {
@Advice.OnMethodExit(suppress = Throwable.class)
public static void onExit() {
RobolectricTestExtractor.capture();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package datadog.trace.instrumentation.robolectric;

import datadog.trace.api.civisibility.android.AndroidTestContext;
import datadog.trace.api.civisibility.android.AndroidTestInfo;
import java.io.File;
import java.net.URL;
import java.security.CodeSource;
import java.security.ProtectionDomain;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.versioning.AndroidVersions;

/**
* Reads the emulated Android SDK and Robolectric version (once the sandbox has established the SDK)
* and hands them to the CI Visibility core via {@link AndroidTestContext}.
*/
public final class RobolectricTestExtractor {

/** Matches the version in a {@code robolectric-<version>.jar} file name. */
private static final Pattern ROBOLECTRIC_JAR = Pattern.compile("^robolectric-(.+)\\.jar$");

private RobolectricTestExtractor() {}

public static void capture() {
int apiLevel = RuntimeEnvironment.getApiLevel();
if (apiLevel <= 0) {
return;
}
String release = null;
String codename = null;
AndroidVersions.AndroidRelease androidRelease = AndroidVersions.getReleaseForSdkInt(apiLevel);
if (androidRelease != null) {
release = androidRelease.getVersion();
codename = androidRelease.getShortCode();
}
AndroidTestContext.set(new AndroidTestInfo(apiLevel, release, codename, robolectricVersion()));
}

private static String robolectricVersion() {
try {
// RuntimeEnvironment is re-loaded by the sandbox classloader with no CodeSource, but the
// runner runs outside the sandbox (it creates it), so it is delegated to the application
// classloader and its CodeSource points at the real robolectric-<version>.jar.
ProtectionDomain protectionDomain = RobolectricTestRunner.class.getProtectionDomain();
CodeSource codeSource = protectionDomain != null ? protectionDomain.getCodeSource() : null;
URL location = codeSource != null ? codeSource.getLocation() : null;
if (location == null) {
return null;
}
Matcher matcher = ROBOLECTRIC_JAR.matcher(new File(location.getPath()).getName());
return matcher.matches() ? matcher.group(1) : null;
} catch (Throwable t) {
return null;
}
}
}
Loading