Skip to content

Commit

Permalink
Add Exception Replay smoke test (#8388)
Browse files Browse the repository at this point in the history
refactor smoke test to be able to add JVM options/properties
ad smoke test for max captured frames
  • Loading branch information
jpbempel authored Feb 24, 2025
1 parent d57a858 commit 685be55
Show file tree
Hide file tree
Showing 5 changed files with 134 additions and 34 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ protected ProcessBuilder createProcessBuilder(Path logFilePath, String... params
@Test
@DisplayName("testCodeOriginTraceAnnotation")
void testCodeOriginTraceAnnotation() throws Exception {
appUrl = startAppAndAndGetUrl();
execute(appUrl, TRACED_METHOD_NAME);
waitForInstrumentation(appUrl);
execute(appUrl, TRACED_METHOD_NAME);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,15 @@
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;

import com.datadog.debugger.sink.Snapshot;
import datadog.trace.api.Platform;
import datadog.trace.bootstrap.debugger.CapturedContext;
import datadog.trace.test.agent.decoder.DecodedSpan;
import datadog.trace.test.agent.decoder.DecodedTrace;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
Expand All @@ -20,12 +21,11 @@

public class ExceptionDebuggerIntegrationTest extends ServerAppDebuggerIntegrationTest {

private String snapshotId0;
private String snapshotId1;
private String snapshotId2;
private List<String> snapshotIdTags = new ArrayList<>();
private boolean traceReceived;
private boolean snapshotReceived;
private Map<String, Snapshot> snapshots = new HashMap<>();
private List<String> additionalJvmArgs = new ArrayList<>();

@Override
protected ProcessBuilder createProcessBuilder(Path logFilePath, String... params) {
Expand All @@ -36,6 +36,7 @@ protected ProcessBuilder createProcessBuilder(Path logFilePath, String... params
commandParams.add("-Ddd.third.party.excludes=datadog.smoketest");
// disable DI to make sure exception debugger works alone
commandParams.remove("-Ddd.dynamic.instrumentation.enabled=true");
commandParams.addAll(additionalJvmArgs);
return ProcessBuilderHelper.createProcessBuilder(
commandParams, logFilePath, getAppClass(), params);
}
Expand All @@ -46,13 +47,18 @@ protected ProcessBuilder createProcessBuilder(Path logFilePath, String... params
value = "datadog.trace.api.Platform#isJ9",
disabledReason = "we cannot get local variable debug info")
void testSimpleSingleFrameException() throws Exception {
appUrl = startAppAndAndGetUrl();
execute(appUrl, TRACED_METHOD_NAME, "oops"); // instrumenting first exception
waitForInstrumentation(appUrl);
execute(appUrl, TRACED_METHOD_NAME, "oops"); // collecting snapshots and sending them
registerTraceListener(this::receiveExceptionReplayTrace);
registerSnapshotListener(this::receiveSnapshot);
processRequests(
() -> {
if (snapshotIdTags.isEmpty()) {
return false;
}
String snapshotId0 = snapshotIdTags.get(0);
if (traceReceived && snapshotReceived && snapshots.containsKey(snapshotId0)) {
Snapshot snapshot = snapshots.get(snapshotId0);
assertNotNull(snapshot);
Expand All @@ -71,6 +77,7 @@ void testSimpleSingleFrameException() throws Exception {
value = "datadog.trace.api.Platform#isJ9",
disabledReason = "we cannot get local variable debug info")
void testNoSubsequentCaptureAfterFirst() throws Exception {
appUrl = startAppAndAndGetUrl();
testSimpleSingleFrameException();
resetSnapshotsAndTraces();
// we should not receive any more snapshots after the first one
Expand All @@ -88,39 +95,40 @@ void testNoSubsequentCaptureAfterFirst() throws Exception {
processRequests(() -> traceReceived && !snapshotReceived);
}

// DeepOops exception stacktrace:
// java.lang.RuntimeException: oops
// datadog.smoketest.debugger.ServerDebuggerTestApplication.tracedMethodWithException(ServerDebuggerTestApplication.java:190)
// datadog.smoketest.debugger.ServerDebuggerTestApplication.tracedMethodWithDeepException5(ServerDebuggerTestApplication.java:210)
// datadog.smoketest.debugger.ServerDebuggerTestApplication.tracedMethodWithDeepException4(ServerDebuggerTestApplication.java:206)
// datadog.smoketest.debugger.ServerDebuggerTestApplication.tracedMethodWithDeepException3(ServerDebuggerTestApplication.java:202)
// datadog.smoketest.debugger.ServerDebuggerTestApplication.tracedMethodWithDeepException2(ServerDebuggerTestApplication.java:198)
// datadog.smoketest.debugger.ServerDebuggerTestApplication.tracedMethodWithDeepException1(ServerDebuggerTestApplication.java:194)
// datadog.smoketest.debugger.ServerDebuggerTestApplication.runTracedMethod(ServerDebuggerTestApplication.java:140)
@Test
@DisplayName("test3CapturedFrames")
@DisabledIf(
value = "datadog.trace.api.Platform#isJ9",
disabledReason = "we cannot get local variable debug info")
void test3CapturedFrames() throws Exception {
appUrl = startAppAndAndGetUrl();
execute(appUrl, TRACED_METHOD_NAME, "deepOops"); // instrumenting first exception
waitForInstrumentation(appUrl);
execute(appUrl, TRACED_METHOD_NAME, "deepOops"); // collecting snapshots and sending them
registerTraceListener(this::receiveExceptionReplayTrace);
registerSnapshotListener(this::receiveSnapshot);
processRequests(
() -> {
if (snapshotIdTags.isEmpty()) {
return false;
}
String snapshotId0 = snapshotIdTags.get(0);
String snapshotId1 = snapshotIdTags.get(1);
String snapshotId2 = snapshotIdTags.get(2);
if (traceReceived
&& snapshotReceived
&& snapshots.containsKey(snapshotId0)
&& snapshots.containsKey(snapshotId1)
&& snapshots.containsKey(snapshotId2)) {
// java.lang.RuntimeException: oops
// at
// datadog.smoketest.debugger.ServerDebuggerTestApplication.tracedMethodWithException(ServerDebuggerTestApplication.java:190)
// at
// datadog.smoketest.debugger.ServerDebuggerTestApplication.tracedMethodWithDeepException5(ServerDebuggerTestApplication.java:210)
// at
// datadog.smoketest.debugger.ServerDebuggerTestApplication.tracedMethodWithDeepException4(ServerDebuggerTestApplication.java:206)
// at
// datadog.smoketest.debugger.ServerDebuggerTestApplication.tracedMethodWithDeepException3(ServerDebuggerTestApplication.java:202)
// at
// datadog.smoketest.debugger.ServerDebuggerTestApplication.tracedMethodWithDeepException2(ServerDebuggerTestApplication.java:198)
// at
// datadog.smoketest.debugger.ServerDebuggerTestApplication.tracedMethodWithDeepException1(ServerDebuggerTestApplication.java:194)
// at
// datadog.smoketest.debugger.ServerDebuggerTestApplication.runTracedMethod(ServerDebuggerTestApplication.java:140)
// snapshot 0
Snapshot snapshot = snapshots.get(snapshotId0);
assertNotNull(snapshot);
Expand Down Expand Up @@ -152,14 +160,89 @@ void test3CapturedFrames() throws Exception {
});
}

@Test
@DisplayName("test5CapturedFrames")
@DisabledIf(
value = "datadog.trace.api.Platform#isJ9",
disabledReason = "we cannot get local variable debug info")
void test5CapturedFrames() throws Exception {
additionalJvmArgs.add("-Ddd.exception.replay.capture.max.frames=5");
appUrl = startAppAndAndGetUrl();
execute(appUrl, TRACED_METHOD_NAME, "deepOops"); // instrumenting first exception
waitForInstrumentation(appUrl);
execute(appUrl, TRACED_METHOD_NAME, "deepOops"); // collecting snapshots and sending them
registerTraceListener(this::receiveExceptionReplayTrace);
registerSnapshotListener(this::receiveSnapshot);
processRequests(
() -> {
if (snapshotIdTags.isEmpty()) {
return false;
}
String snapshotId0 = snapshotIdTags.get(0);
String snapshotId1 = snapshotIdTags.get(1);
String snapshotId2 = snapshotIdTags.get(2);
String snapshotId3 = snapshotIdTags.get(3);
String snapshotId4 = snapshotIdTags.get(4);
if (traceReceived
&& snapshotReceived
&& snapshots.containsKey(snapshotId0)
&& snapshots.containsKey(snapshotId1)
&& snapshots.containsKey(snapshotId2)
&& snapshots.containsKey(snapshotId3)
&& snapshots.containsKey(snapshotId4)) {
// snapshot 0
Snapshot snapshot = snapshots.get(snapshotId0);
assertNotNull(snapshot);
assertEquals(
"oops", snapshot.getCaptures().getReturn().getCapturedThrowable().getMessage());
assertEquals(
"datadog.smoketest.debugger.ServerDebuggerTestApplication.tracedMethodWithException",
snapshot.getStack().get(0).getFunction());
assertFullMethodCaptureArgs(snapshot.getCaptures().getReturn());
// snapshot 1
snapshot = snapshots.get(snapshotId1);
assertEquals(
"oops", snapshot.getCaptures().getReturn().getCapturedThrowable().getMessage());
assertEquals(
"datadog.smoketest.debugger.ServerDebuggerTestApplication.tracedMethodWithDeepException5",
snapshot.getStack().get(0).getFunction());
assertFullMethodCaptureArgs(snapshot.getCaptures().getReturn());
// snapshot 2
snapshot = snapshots.get(snapshotId2);
assertEquals(
"oops", snapshot.getCaptures().getReturn().getCapturedThrowable().getMessage());
assertEquals(
"datadog.smoketest.debugger.ServerDebuggerTestApplication.tracedMethodWithDeepException4",
snapshot.getStack().get(0).getFunction());
assertFullMethodCaptureArgs(snapshot.getCaptures().getReturn());
// snapshot 3
snapshot = snapshots.get(snapshotId3);
assertEquals(
"oops", snapshot.getCaptures().getReturn().getCapturedThrowable().getMessage());
assertEquals(
"datadog.smoketest.debugger.ServerDebuggerTestApplication.tracedMethodWithDeepException3",
snapshot.getStack().get(0).getFunction());
assertFullMethodCaptureArgs(snapshot.getCaptures().getReturn());
// snapshot 4
snapshot = snapshots.get(snapshotId4);
assertEquals(
"oops", snapshot.getCaptures().getReturn().getCapturedThrowable().getMessage());
assertEquals(
"datadog.smoketest.debugger.ServerDebuggerTestApplication.tracedMethodWithDeepException2",
snapshot.getStack().get(0).getFunction());
assertFullMethodCaptureArgs(snapshot.getCaptures().getReturn());
return true;
}
return false;
});
}

private void resetSnapshotsAndTraces() {
resetTraceListener();
traceReceived = false;
snapshotReceived = false;
snapshots.clear();
snapshotId0 = null;
snapshotId1 = null;
snapshotId2 = null;
snapshotIdTags.clear();
}

private void assertFullMethodCaptureArgs(CapturedContext context) {
Expand All @@ -178,10 +261,13 @@ private void receiveExceptionReplayTrace(DecodedTrace decodedTrace) {
for (DecodedSpan span : decodedTrace.getSpans()) {
if (isTracedFullMethodSpan(span) && span.getMeta().containsKey("error.debug_info_captured")) {
// assert that we have received the trace with ER tags only once
assertNull(snapshotId0);
snapshotId0 = span.getMeta().get("_dd.debug.error.0.snapshot_id");
snapshotId1 = span.getMeta().get("_dd.debug.error.1.snapshot_id");
snapshotId2 = span.getMeta().get("_dd.debug.error.2.snapshot_id");
assertTrue(snapshotIdTags.isEmpty());
for (int i = 0; i < 5; i++) {
String snapshotId = span.getMeta().get("_dd.debug.error." + i + ".snapshot_id");
if (snapshotId != null) {
snapshotIdTags.add(snapshotId);
}
}
assertFalse(traceReceived);
traceReceived = true;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,22 @@
import java.util.Collections;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInfo;
import org.junit.jupiter.api.condition.DisabledIf;

public class ProbeStateIntegrationTest extends ServerAppDebuggerIntegrationTest {

@BeforeEach
@Override
void setup(TestInfo testInfo) throws Exception {
super.setup(testInfo);
appUrl = startAppAndAndGetUrl();
}

@Test
@DisplayName("testAddRemoveProbes")
@DisabledIf(value = "datadog.trace.api.Platform#isJ9", disabledReason = "Flaky on J9 JVMs")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
import datadog.trace.bootstrap.debugger.ProbeId;
import datadog.trace.test.agent.decoder.DecodedSpan;
import datadog.trace.util.TagsHelper;
import java.io.EOFException;
import java.io.IOException;
import java.util.List;
import java.util.concurrent.TimeUnit;
Expand Down Expand Up @@ -42,17 +41,15 @@ public class ServerAppDebuggerIntegrationTest extends BaseIntegrationTest {
private OkHttpClient httpClient = new OkHttpClient();
protected String appUrl;

@Override
@BeforeEach
@Override
void setup(TestInfo testInfo) throws Exception {
super.setup(testInfo);
controlServer = new MockWebServer();
// controlServer.setDispatcher(new ControlDispatcher());
controlServer.start();
LOG.info("ControlServer on {}", controlServer.getPort());
controlUrl = controlServer.url(CONTROL_URL);
startApp();
appUrl = waitForAppStartedAndGetUrl();
}

@Override
Expand Down Expand Up @@ -146,12 +143,9 @@ protected void waitForReTransformation(String appUrl) throws IOException {
LOG.info("re-transformation done");
}

protected void startApp() throws IOException {
protected String startAppAndAndGetUrl() throws InterruptedException, IOException {
controlServer.enqueue(EMPTY_200_RESPONSE); // ack response
targetProcess = createProcessBuilder(logFilePath, controlUrl.toString()).start();
}

protected String waitForAppStartedAndGetUrl() throws InterruptedException, EOFException {
RecordedRequest recordedRequest = controlServer.takeRequest(30, TimeUnit.SECONDS);
assertNotNull(recordedRequest);
String appUrl = recordedRequest.getBody().readUtf8Line();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,21 @@
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInfo;
import org.junit.jupiter.api.condition.DisabledIf;

public class SpanDecorationProbesIntegrationTests extends ServerAppDebuggerIntegrationTest {

@BeforeEach
@Override
void setup(TestInfo testInfo) throws Exception {
super.setup(testInfo);
appUrl = startAppAndAndGetUrl();
}

@Override
protected ProcessBuilder createProcessBuilder(Path logFilePath, String... params) {
List<String> commandParams = getDebuggerCommandParams();
Expand Down

0 comments on commit 685be55

Please sign in to comment.