From 12c35ab0c6a1e52da62644b21cfb4dfbccc718ba Mon Sep 17 00:00:00 2001 From: Jean-Philippe Bempel Date: Tue, 22 Sep 2026 15:16:12 +0200 Subject: [PATCH 1/2] Fix tests using precompiled MyResource class instead of loading a precompiled and preinstrumented class, we are compiling and instrumenting with JAX-RS instrumentation on-the-fly. Add InstrumentationTestHelper.installTracerInstrumentation(), which self-attaches and applies the real JAX-RS InstrumenterModule via AgentInstaller Modify CapturedSnapshotTest.tracerInstrumentedClass and MetricProbesInstrumentationTest.localVarNotInScope --- dd-java-agent/agent-debugger/build.gradle | 2 ++ .../debugger/agent/CapturedSnapshotTest.java | 15 +++++++++++--- .../MetricProbesInstrumentationTest.java | 13 +++++++++--- .../agent/SnapshotSerializationTest.java | 6 ++++++ .../java/utils/InstrumentationTestHelper.java | 19 ++++++++++++++++++ .../src/test/resources/MyResource.class | Bin 3931 -> 0 bytes .../datadog/debugger/jaxrs/MyResource.java | 18 +++++++++++++++++ 7 files changed, 67 insertions(+), 6 deletions(-) delete mode 100644 dd-java-agent/agent-debugger/src/test/resources/MyResource.class create mode 100644 dd-java-agent/agent-debugger/src/test/resources/com/datadog/debugger/jaxrs/MyResource.java diff --git a/dd-java-agent/agent-debugger/build.gradle b/dd-java-agent/agent-debugger/build.gradle index 32aef3e51ec..30277dc639a 100644 --- a/dd-java-agent/agent-debugger/build.gradle +++ b/dd-java-agent/agent-debugger/build.gradle @@ -77,6 +77,8 @@ dependencies { testImplementation project(':dd-java-agent:agent-debugger:debugger-test-scala') testImplementation project(':dd-trace-core') testImplementation project(':dd-java-agent:agent-installer') + testImplementation project(':dd-java-agent:instrumentation:rs:jax-rs:jax-rs-annotations:jax-rs-annotations-1.1.1') + testImplementation group: 'javax.ws.rs', name: 'jsr311-api', version: '1.1.1' testImplementation project(':remote-config:remote-config-core') testImplementation project(':utils:test-utils') diff --git a/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/agent/CapturedSnapshotTest.java b/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/agent/CapturedSnapshotTest.java index a800e1cb1e7..2ecb5c06174 100644 --- a/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/agent/CapturedSnapshotTest.java +++ b/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/agent/CapturedSnapshotTest.java @@ -27,6 +27,7 @@ import static utils.InstrumentationTestHelper.compile; import static utils.InstrumentationTestHelper.compileAndLoadClass; import static utils.InstrumentationTestHelper.getLineForLineProbe; +import static utils.InstrumentationTestHelper.installTracerInstrumentation; import static utils.InstrumentationTestHelper.loadClass; import static utils.TestClassFileHelper.getClassFileBytes; import static utils.TestHelper.getFixtureContent; @@ -72,6 +73,7 @@ import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; +import java.lang.instrument.ClassFileTransformer; import java.lang.instrument.Instrumentation; import java.net.URISyntaxException; import java.net.URL; @@ -1839,9 +1841,16 @@ public void tracerInstrumentedClass() throws Exception { DebuggerContext.initClassFilter(new DenyListHelper(null)); final String CLASS_NAME = "com.datadog.debugger.jaxrs.MyResource"; TestSnapshotListener listener = installMethodProbe(CLASS_NAME, "createResource", null); - // load a class file that was previously instrumented by the DD tracer as JAX-RS resource - Class testClass = - loadClass(CLASS_NAME, getClass().getResource("/MyResource.class").getFile()); + // compile the JAX-RS resource fixture and weave it with the real tracer JAX-RS + // instrumentation, so this test exercises argument-name resolution against the same + // bytecode shape the tracer actually produces + ClassFileTransformer jaxRsTransformer = installTracerInstrumentation(instr); + Class testClass; + try { + testClass = compileAndLoadClass(CLASS_NAME); + } finally { + instr.removeTransformer(jaxRsTransformer); + } Object result = Reflect.onClass(testClass) .create() diff --git a/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/agent/MetricProbesInstrumentationTest.java b/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/agent/MetricProbesInstrumentationTest.java index 413fa216cb9..699f6bd6dc8 100644 --- a/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/agent/MetricProbesInstrumentationTest.java +++ b/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/agent/MetricProbesInstrumentationTest.java @@ -19,7 +19,7 @@ import static org.mockito.Mockito.when; import static utils.InstrumentationTestHelper.compileAndLoadClass; import static utils.InstrumentationTestHelper.getLineForLineProbe; -import static utils.InstrumentationTestHelper.loadClass; +import static utils.InstrumentationTestHelper.installTracerInstrumentation; import com.datadog.debugger.el.DSL; import com.datadog.debugger.el.ValueScript; @@ -1297,8 +1297,15 @@ public void localVarNotInScope() throws IOException, URISyntaxException { .valueScript(new ValueScript(DSL.len(DSL.ref("varStr")), "len(varStr)")) .build(); MetricForwarderListener listener = installMetricProbes(metricProbe); - Class testClass = - loadClass(CLASS_NAME, getClass().getResource("/MyResource.class").getFile()); + // compile the JAX-RS resource fixture and weave it with the real tracer JAX-RS + // instrumentation, so this test exercises the same bytecode shape the tracer produces + ClassFileTransformer jaxRsTransformer = installTracerInstrumentation(instr); + Class testClass; + try { + testClass = compileAndLoadClass(CLASS_NAME); + } finally { + instr.removeTransformer(jaxRsTransformer); + } Object result = Reflect.onClass(testClass) .create() diff --git a/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/agent/SnapshotSerializationTest.java b/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/agent/SnapshotSerializationTest.java index 3c45c046014..b856fcd8a5d 100644 --- a/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/agent/SnapshotSerializationTest.java +++ b/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/agent/SnapshotSerializationTest.java @@ -179,6 +179,12 @@ public void roundTripCaughtException() throws IOException { new CapturedStackFrame("f2", 23), new CapturedStackFrame("f3", 34)), null)); + captures.addCaughtException( + new CapturedContext.CapturedThrowable( + NullPointerException.class.getTypeName(), + "illegal argument", + Collections.emptyList(), + null)); String buffer = adapter.toJson(snapshot); System.out.println("roundTripCaughtException: " + buffer); Snapshot deserializedSnapshot = adapter.fromJson(buffer); diff --git a/dd-java-agent/agent-debugger/src/test/java/utils/InstrumentationTestHelper.java b/dd-java-agent/agent-debugger/src/test/java/utils/InstrumentationTestHelper.java index 303d976d69b..4919e59cdc5 100644 --- a/dd-java-agent/agent-debugger/src/test/java/utils/InstrumentationTestHelper.java +++ b/dd-java-agent/agent-debugger/src/test/java/utils/InstrumentationTestHelper.java @@ -4,8 +4,13 @@ import static utils.TestHelper.getFixtureLines; import com.datadog.debugger.agent.CapturedSnapshotTest; +import datadog.instrument.classinject.ClassInjector; +import datadog.trace.agent.tooling.AgentInstaller; +import datadog.trace.agent.tooling.InstrumenterModule; import datadog.trace.bootstrap.debugger.ProbeId; import java.io.IOException; +import java.lang.instrument.ClassFileTransformer; +import java.lang.instrument.Instrumentation; import java.net.MalformedURLException; import java.net.URISyntaxException; import java.net.URL; @@ -13,6 +18,7 @@ import java.nio.file.Files; import java.nio.file.Paths; import java.util.Collections; +import java.util.EnumSet; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -84,6 +90,19 @@ public static int getLineForLineProbe(String className, ProbeId lineProbeId) { return getLineForLineProbe(className, ".java", lineProbeId); } + /** + * Installs the real tracer instrumentation modules (e.g. jax-rs-annotations) on top of the given + * {@link Instrumentation}, so that classes defined afterwards (typically via {@link + * #compileAndLoadClass}) are woven with actual tracer advice, the same way they would be by the + * production agent. Callers must remove the returned transformer once done, e.g. via {@link + * Instrumentation#removeTransformer(ClassFileTransformer)}. + */ + public static ClassFileTransformer installTracerInstrumentation(Instrumentation instr) { + ClassInjector.enableClassInjection(instr); + return AgentInstaller.installBytebuddyAgent( + instr, false, EnumSet.of(InstrumenterModule.TargetSystem.TRACING)); + } + public static int getLineForLineProbe(String className, String ext, ProbeId lineProbeId) { List lines = getFixtureLines("/" + className.replace('.', '/') + ext); for (int i = 0; i < lines.size(); i++) { diff --git a/dd-java-agent/agent-debugger/src/test/resources/MyResource.class b/dd-java-agent/agent-debugger/src/test/resources/MyResource.class deleted file mode 100644 index eba334f1d9c847152824e91aed0d7551feaeddf3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3931 zcmcgu+fy6Y8UG!0+116^7RL1^b)qB$19p%@9a4+bHOAmNffyqS!EWj<(g7BWU9r2u z(EBB6lQwPBy0^4xJMDCa*TxSN+G*Op(3w7Erth8UpOA;l^uaa#ofV2C3^NH21!=YC zJLkLozRUUkaqo|B12}_U#}UHAI?@^*(eS81^cl;x{Br`Kj?M{z@PJd00tbgJTRvZ2 zn3nFiIb9?rHSFZg;)Lm1YQ0Si`*W5jaAG*`EMy9%Zx)={OhHbUXJ@6GnKzeRFEg?- zCOxO@=4GEibKaGvFRK)RvmL|p=8~Byn)Yla=ew3Y+jl?Za%Uaw%JjU<`xF*6OV)F8 zMcH9}EQ#=bRbTQydB7!I*JmV0JLpz-nc>Du} zPoSm7IzHz*w^Sx*cue3iwNcfdr$;8!$IbM6D|Vi-4sh1+IFYMKxJdXnhFGYOo=Cj_3}OY5F~ zk1LEBx=%9A%)DQqQqPF= z=N!U%zCMJND(M_zIGVhIgvyGGEUst>f+`M?}wbqYk|^!t=@lj&cRZa%nKxg8s@!RL2&W^ zX-nQINk+4X1yz6SA-#>I@VZ^oJ+W+TIDi%_A+j8IWJ4C#yh!Kv%u-+vQvb<@>`z zpNhjb@vQ`2#w!er$JU?3w{bxQ<~y7N9DC4p9gb%&?>7f`SG&*JPU5@xo+^^>vq+{T z;gdrgm%X_peo(2NA9AFe*(Uu6QPu8$OkDCs$CF9?R8bc3GxjLU%ay&7vaPhDhA5vJR~h~a{zkg~0P$9^t@Ek2ff)aR zWaR+jNN`1~ae$)896Cx`lBBmpHY~D{=${dq3Z2YNg-_+CBFEQpDwmBK(YJfGP_N#q zw`#w|StHu2_he&6>~Cm)18qj^uV~MPQ|+lUFWf;k8!;kAcmwB}u!#d=6|cRdi`A{o zbzICwQ$y=`HmeyL4?N$5$vYS~G$Y!zh8I%d)D*Q}p9-bAsa`!IcW@)OhWr|4gM(|h zIcaF?aI(6gQ$o23Mr0E$MtBp=N~@u5U?l`YU&YbX7uNB$8>w&H*ubkHtRg%~_qwvt zlUeOlRzKbwOEqQVf1 zZ4Ivnw(D!SJ!$A0_=%t?8+b!d(~HWU;|hac3P!V~ii{B!74x`#ptc=|+)YmF{VqTm z5jC{G7}4J|144L*|800zghT>u;*hv4Zgc$({)K;YeOvrS{Eq7oe#Nl=6UXi%q@jHa z4#O(eA9!s!@=qID4~V4IJqM@f>!bzrCe1(AK~vYevhci dV~~UA1AIH|QCkxpB%ui(4bCS>K3>^+=zlNYT>Ah3 diff --git a/dd-java-agent/agent-debugger/src/test/resources/com/datadog/debugger/jaxrs/MyResource.java b/dd-java-agent/agent-debugger/src/test/resources/com/datadog/debugger/jaxrs/MyResource.java new file mode 100644 index 00000000000..67a20e145e7 --- /dev/null +++ b/dd-java-agent/agent-debugger/src/test/resources/com/datadog/debugger/jaxrs/MyResource.java @@ -0,0 +1,18 @@ +package com.datadog.debugger.jaxrs; + +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.QueryParam; +import javax.ws.rs.core.Context; + +@Path("myresource") +public class MyResource { + + @GET + public Object createResource( + String apiKey, @Context String uriInfo, @QueryParam("value") int value) { + String varStr = "foo"; + Object response = new Object(); + return response; + } +} From 7aa786fb7e06b5f3786db33adce7e32f43b4989d Mon Sep 17 00:00:00 2001 From: Jean-Philippe Bempel Date: Wed, 23 Sep 2026 14:50:45 +0200 Subject: [PATCH 2/2] address codex comments use jax-rs 2 api --- dd-java-agent/agent-debugger/build.gradle | 4 +-- .../debugger/agent/CapturedSnapshotTest.java | 34 +++++++++---------- .../MetricProbesInstrumentationTest.java | 26 +++++++------- 3 files changed, 32 insertions(+), 32 deletions(-) diff --git a/dd-java-agent/agent-debugger/build.gradle b/dd-java-agent/agent-debugger/build.gradle index 30277dc639a..a4de7c2a0cf 100644 --- a/dd-java-agent/agent-debugger/build.gradle +++ b/dd-java-agent/agent-debugger/build.gradle @@ -77,8 +77,8 @@ dependencies { testImplementation project(':dd-java-agent:agent-debugger:debugger-test-scala') testImplementation project(':dd-trace-core') testImplementation project(':dd-java-agent:agent-installer') - testImplementation project(':dd-java-agent:instrumentation:rs:jax-rs:jax-rs-annotations:jax-rs-annotations-1.1.1') - testImplementation group: 'javax.ws.rs', name: 'jsr311-api', version: '1.1.1' + testImplementation project(':dd-java-agent:instrumentation:rs:jax-rs:jax-rs-annotations:jax-rs-annotations-2.0') + testImplementation group: 'javax.ws.rs', name: 'javax.ws.rs-api', version: '2.1.1' testImplementation project(':remote-config:remote-config-core') testImplementation project(':utils:test-utils') diff --git a/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/agent/CapturedSnapshotTest.java b/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/agent/CapturedSnapshotTest.java index 2ecb5c06174..321758461d1 100644 --- a/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/agent/CapturedSnapshotTest.java +++ b/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/agent/CapturedSnapshotTest.java @@ -1840,33 +1840,33 @@ public void multiLambdas() throws IOException, URISyntaxException { public void tracerInstrumentedClass() throws Exception { DebuggerContext.initClassFilter(new DenyListHelper(null)); final String CLASS_NAME = "com.datadog.debugger.jaxrs.MyResource"; - TestSnapshotListener listener = installMethodProbe(CLASS_NAME, "createResource", null); // compile the JAX-RS resource fixture and weave it with the real tracer JAX-RS // instrumentation, so this test exercises argument-name resolution against the same // bytecode shape the tracer actually produces ClassFileTransformer jaxRsTransformer = installTracerInstrumentation(instr); Class testClass; try { + TestSnapshotListener listener = installMethodProbe(CLASS_NAME, "createResource", null); testClass = compileAndLoadClass(CLASS_NAME); + Object result = + Reflect.onClass(testClass) + .create() + .call("createResource", (Object) null, (Object) null, 1) + .get(); + Snapshot snapshot = assertOneSnapshot(listener); + Map arguments = + snapshot.getCaptures().getEntry().getArguments(); + // it's important there is no null key in this map, as Jackson is not happy about it + // it's means here that argument names are not resolved correctly + Assertions.assertFalse(arguments.containsKey(null)); + assertEquals(4, arguments.size()); + assertTrue(arguments.containsKey("this")); + assertTrue(arguments.containsKey("apiKey")); + assertTrue(arguments.containsKey("uriInfo")); + assertTrue(arguments.containsKey("value")); } finally { instr.removeTransformer(jaxRsTransformer); } - Object result = - Reflect.onClass(testClass) - .create() - .call("createResource", (Object) null, (Object) null, 1) - .get(); - Snapshot snapshot = assertOneSnapshot(listener); - Map arguments = - snapshot.getCaptures().getEntry().getArguments(); - // it's important there is no null key in this map, as Jackson is not happy about it - // it's means here that argument names are not resolved correctly - Assertions.assertFalse(arguments.containsKey(null)); - assertEquals(4, arguments.size()); - assertTrue(arguments.containsKey("this")); - assertTrue(arguments.containsKey("apiKey")); - assertTrue(arguments.containsKey("uriInfo")); - assertTrue(arguments.containsKey("value")); } @Test diff --git a/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/agent/MetricProbesInstrumentationTest.java b/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/agent/MetricProbesInstrumentationTest.java index 699f6bd6dc8..d75ac862a29 100644 --- a/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/agent/MetricProbesInstrumentationTest.java +++ b/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/agent/MetricProbesInstrumentationTest.java @@ -1291,28 +1291,28 @@ public void primitivesFunction() throws IOException, URISyntaxException { public void localVarNotInScope() throws IOException, URISyntaxException { final String METRIC_NAME = "lenstr"; final String CLASS_NAME = "com.datadog.debugger.jaxrs.MyResource"; - MetricProbe metricProbe = - createMetricBuilder(METRIC_ID, METRIC_NAME, GAUGE) - .where(CLASS_NAME, "createResource", null) - .valueScript(new ValueScript(DSL.len(DSL.ref("varStr")), "len(varStr)")) - .build(); - MetricForwarderListener listener = installMetricProbes(metricProbe); // compile the JAX-RS resource fixture and weave it with the real tracer JAX-RS // instrumentation, so this test exercises the same bytecode shape the tracer produces ClassFileTransformer jaxRsTransformer = installTracerInstrumentation(instr); Class testClass; try { + MetricProbe metricProbe = + createMetricBuilder(METRIC_ID, METRIC_NAME, GAUGE) + .where(CLASS_NAME, "createResource", null) + .valueScript(new ValueScript(DSL.len(DSL.ref("varStr")), "len(varStr)")) + .build(); + MetricForwarderListener listener = installMetricProbes(metricProbe); testClass = compileAndLoadClass(CLASS_NAME); + Object result = + Reflect.onClass(testClass) + .create() + .call("createResource", (Object) null, (Object) null, 1) + .get(); + assertNotNull(result); + assertFalse(listener.gauges.containsKey(METRIC_NAME)); } finally { instr.removeTransformer(jaxRsTransformer); } - Object result = - Reflect.onClass(testClass) - .create() - .call("createResource", (Object) null, (Object) null, 1) - .get(); - assertNotNull(result); - assertFalse(listener.gauges.containsKey(METRIC_NAME)); } private MetricForwarderListener installMethodMetric(