Skip to content

Commit 88fd665

Browse files
feat: implement SuiteEnd listener for sink flushing
1 parent 0228f71 commit 88fd665

File tree

8 files changed

+79
-5
lines changed

8 files changed

+79
-5
lines changed

dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/domain/TestSuiteImpl.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
import datadog.trace.api.Config;
88
import datadog.trace.api.civisibility.DDTestSuite;
9+
import datadog.trace.api.civisibility.InstrumentationTestBridge;
910
import datadog.trace.api.civisibility.config.LibraryCapability;
1011
import datadog.trace.api.civisibility.coverage.CoverageStore;
1112
import datadog.trace.api.civisibility.execution.TestStatus;
@@ -220,6 +221,8 @@ public void end(@Nullable Long endTime) {
220221
AgentTracer.closeActive();
221222
}
222223

224+
InstrumentationTestBridge.fireBeforeSuiteEnd();
225+
223226
onSpanFinish.accept(span);
224227

225228
if (endTime != null) {

dd-java-agent/agent-ci-visibility/src/testFixtures/groovy/datadog/trace/civisibility/CiVisibilitySmokeTest.groovy

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,4 +52,38 @@ abstract class CiVisibilitySmokeTest extends Specification {
5252
// an even more basic smoke check for distributions: assert that we received some
5353
assert !receivedTelemetryDistributions.isEmpty()
5454
}
55+
56+
protected static verifySnapshotLogs(List<Map<String, Object>> receivedLogs, int expectedSnapshots) {
57+
def logsPerSnapshot = 4 // 3 probe statuses + 1 snapshot log are expected per snapshot
58+
59+
assert receivedLogs.size() == logsPerSnapshot * expectedSnapshots
60+
61+
def probeStatusLogs = receivedLogs.findAll { it.containsKey("message") }
62+
def snapshotLogs = receivedLogs.findAll { !it.containsKey("message") }
63+
64+
verifyProbeStatuses(probeStatusLogs, expectedSnapshots)
65+
verifySnapshots(snapshotLogs)
66+
}
67+
68+
private static verifyProbeStatuses(List<Map<String, Object>> logs, int expectedCount) {
69+
assert logs.findAll { log -> ((String) log.message).startsWith("Received probe") }.size() == expectedCount
70+
assert logs.findAll { log -> ((String) log.message).startsWith("Installed probe") }.size() == expectedCount
71+
assert logs.findAll { log -> ((String) log.message).endsWith("is emitting.") }.size() == expectedCount
72+
}
73+
74+
private static verifySnapshots(List<Map<String, Object>> logs) {
75+
def requiredLogFields = ["logger.name", "logger.method", "dd.spanid", "dd.traceid"]
76+
def requiredSnapshotFields = ["captures", "exceptionId", "probe", "stack"]
77+
78+
logs.each { log ->
79+
assert log.product == "test_optimization"
80+
requiredLogFields.each { field -> log.containsKey(field) }
81+
82+
Map<String, Object> debuggerMap = log.debugger as Map<String, Object>
83+
Map<String, Object> snapshotContent = debuggerMap.snapshot as Map<String, Object>
84+
85+
assert snapshotContent != null
86+
requiredSnapshotFields.each { field -> snapshotContent.containsKey(field) }
87+
}
88+
}
5589
}

dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/sink/DebuggerSink.java

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
import com.datadog.debugger.uploader.BatchUploader;
55
import com.datadog.debugger.util.DebuggerMetrics;
66
import datadog.trace.api.Config;
7+
import datadog.trace.api.civisibility.InstrumentationTestBridge;
8+
import datadog.trace.api.civisibility.domain.TestContext;
79
import datadog.trace.bootstrap.debugger.DebuggerContext.SkipCause;
810
import datadog.trace.bootstrap.debugger.ProbeId;
911
import datadog.trace.util.AgentTaskScheduler;
@@ -68,6 +70,11 @@ public DebuggerSink(
6870
this.snapshotSink = snapshotSink;
6971
this.symbolSink = symbolSink;
7072
this.uploadFlushInterval = config.getDynamicInstrumentationUploadFlushInterval();
73+
74+
if (config.isCiVisibilityFailedTestReplayActive()) {
75+
// register test listener to flush snapshots on suite end
76+
InstrumentationTestBridge.registerListener(new DebuggerTestListener(this));
77+
}
7178
}
7279

7380
public void start() {
@@ -235,4 +242,23 @@ public void skipSnapshot(String probeId, SkipCause cause) {
235242
long getCurrentLowRateFlushInterval() {
236243
return currentLowRateFlushInterval;
237244
}
245+
246+
static class DebuggerTestListener implements InstrumentationTestBridge.TestListener {
247+
private final DebuggerSink sink;
248+
249+
DebuggerTestListener(DebuggerSink sink) {
250+
this.sink = sink;
251+
}
252+
253+
@Override
254+
public void beforeTestEnd(TestContext ignored) {
255+
// noop
256+
}
257+
258+
@Override
259+
public void beforeSuiteEnd() {
260+
LOGGER.debug("CiVisibility BeforeSuiteEnd fired, flushing sink");
261+
sink.lowRateFlush(sink);
262+
}
263+
}
238264
}

dd-java-agent/instrumentation/selenium/src/main/java/datadog/trace/instrumentation/selenium/SeleniumTestListener.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,4 +25,9 @@ public void beforeTestEnd(TestContext testContext) {
2525

2626
SeleniumUtils.beforePageClose(driver);
2727
}
28+
29+
@Override
30+
public void beforeSuiteEnd() {
31+
// noop
32+
}
2833
}

dd-smoke-tests/junit-console/src/test/groovy/datadog/smoketest/JUnitConsoleSmokeTest.groovy

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,8 +61,7 @@ class JUnitConsoleSmokeTest extends CiVisibilitySmokeTest {
6161

6262
def additionalDynamicTags = ["content.meta.['_dd.debug.error.6.snapshot_id']", "content.meta.['_dd.debug.error.exception_id']"]
6363
verifyEventsAndCoverages(projectName, "junit-console", "headless", mockBackend.waitForEvents(5), mockBackend.waitForCoverages(0), additionalDynamicTags)
64-
//TODO: add verification of the logs payload
65-
//mockBackend.waitForLogs(8)
64+
verifySnapshotLogs(mockBackend.waitForLogs(4), 1)
6665

6766
where:
6867
projectName = "test_junit_console_failed_test_replay"

dd-smoke-tests/maven/src/test/groovy/datadog/smoketest/MavenSmokeTest.groovy

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -250,8 +250,7 @@ class MavenSmokeTest extends CiVisibilitySmokeTest {
250250

251251
def additionalDynamicTags = ["content.meta.['_dd.debug.error.3.snapshot_id']", "content.meta.['_dd.debug.error.exception_id']"]
252252
verifyEventsAndCoverages(projectName, "maven", mavenVersion, mockBackend.waitForEvents(5), mockBackend.waitForCoverages(0), additionalDynamicTags)
253-
//TODO: add verification of the logs payload
254-
//mockBackend.waitForLogs(8)
253+
verifySnapshotLogs(mockBackend.waitForLogs(4), 1)
255254

256255
where:
257256
projectName | mavenVersion

dd-trace-api/src/main/java/datadog/trace/api/ConfigDefaults.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -189,7 +189,7 @@ public final class ConfigDefaults {
189189
static final boolean DEFAULT_DYNAMIC_INSTRUMENTATION_ENABLED = false;
190190
static final int DEFAULT_DYNAMIC_INSTRUMENTATION_UPLOAD_TIMEOUT = 30; // seconds
191191
static final int DEFAULT_DYNAMIC_INSTRUMENTATION_UPLOAD_FLUSH_INTERVAL = 0; // ms, 0 = dynamic
192-
static final int DEFAULT_FAILED_TEST_REPLAY_UPLOAD_FLUSH_INTERVAL = 200; // ms, 0 = dynamic
192+
static final int DEFAULT_FAILED_TEST_REPLAY_UPLOAD_FLUSH_INTERVAL = 1000; // ms, 0 = dynamic
193193
static final boolean DEFAULT_DYNAMIC_INSTRUMENTATION_CLASSFILE_DUMP_ENABLED = false;
194194
static final int DEFAULT_DYNAMIC_INSTRUMENTATION_POLL_INTERVAL = 1; // seconds
195195
static final int DEFAULT_DYNAMIC_INSTRUMENTATION_DIAGNOSTICS_INTERVAL = 60 * 60; // seconds

internal-api/src/main/java/datadog/trace/api/civisibility/InstrumentationTestBridge.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,11 +38,19 @@ public static void fireBeforeTestEnd(TestContext testContext) {
3838
}
3939
}
4040

41+
public static void fireBeforeSuiteEnd() {
42+
for (TestListener testListener : TEST_LISTENERS) {
43+
testListener.beforeSuiteEnd();
44+
}
45+
}
46+
4147
public static void registerListener(TestListener listener) {
4248
TEST_LISTENERS.addIfAbsent(listener);
4349
}
4450

4551
public interface TestListener {
4652
void beforeTestEnd(TestContext testContext);
53+
54+
void beforeSuiteEnd();
4755
}
4856
}

0 commit comments

Comments
 (0)