Skip to content

Commit 8ff37c3

Browse files
Fix Scalatest tracing for tests that are reported asynchronously (#8444)
1 parent 28c315b commit 8ff37c3

File tree

1 file changed

+39
-4
lines changed

1 file changed

+39
-4
lines changed

dd-java-agent/instrumentation/scalatest/src/main/java/datadog/trace/instrumentation/scalatest/ScalatestInstrumentation.java

+39-4
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,15 @@
77
import com.google.auto.service.AutoService;
88
import datadog.trace.agent.tooling.Instrumenter;
99
import datadog.trace.agent.tooling.InstrumenterModule;
10+
import datadog.trace.bootstrap.CallDepthThreadLocalMap;
1011
import java.util.Set;
1112
import net.bytebuddy.asm.Advice;
13+
import org.scalatest.Reporter;
1214
import org.scalatest.events.Event;
1315

1416
@AutoService(InstrumenterModule.class)
1517
public class ScalatestInstrumentation extends InstrumenterModule.CiVisibility
16-
implements Instrumenter.ForSingleType, Instrumenter.HasMethodAdvice {
18+
implements Instrumenter.ForKnownTypes, Instrumenter.HasMethodAdvice {
1719

1820
public ScalatestInstrumentation() {
1921
super("ci-visibility", "scalatest");
@@ -25,8 +27,10 @@ public boolean isApplicable(Set<TargetSystem> enabledSystems) {
2527
}
2628

2729
@Override
28-
public String instrumentedType() {
29-
return "org.scalatest.DispatchReporter";
30+
public String[] knownMatchingTypes() {
31+
return new String[] {
32+
"org.scalatest.DispatchReporter", "org.scalatest.tools.TestSortingReporter",
33+
};
3034
}
3135

3236
@Override
@@ -46,13 +50,21 @@ public void methodAdvice(MethodTransformer transformer) {
4650
.and(takesArguments(1))
4751
.and(takesArgument(0, named("org.scalatest.events.Event"))),
4852
ScalatestInstrumentation.class.getName() + "$DispatchEventAdvice");
53+
transformer.applyAdvice(
54+
named("fireReadyEvents"),
55+
ScalatestInstrumentation.class.getName() + "$SuppressAsyncEventsAdvice");
4956
}
5057

5158
public static class DispatchEventAdvice {
5259
@Advice.OnMethodEnter
5360
public static void onDispatchEvent(@Advice.Argument(value = 0) Event event) {
61+
if (CallDepthThreadLocalMap.incrementCallDepth(Reporter.class) != 0) {
62+
// nested call
63+
return;
64+
}
65+
5466
// Instead of registering our reporter using Scalatest's standard "-C" argument,
55-
// we hook into internal dispatch reporter.
67+
// we hook into internal reporter.
5668
// The reason is that Scalatest invokes registered reporters in a separate thread,
5769
// while we need to process events in the thread where they originate.
5870
// This is required because test span has to be active in the thread where
@@ -61,5 +73,28 @@ public static void onDispatchEvent(@Advice.Argument(value = 0) Event event) {
6173
// could be properly associated with it.
6274
DatadogReporter.handle(event);
6375
}
76+
77+
@Advice.OnMethodExit
78+
public static void afterDispatchEvent() {
79+
CallDepthThreadLocalMap.decrementCallDepth(Reporter.class);
80+
}
81+
}
82+
83+
/**
84+
* {@link org.scalatest.tools.TestSortingReporter#fireReadyEvents} is triggered asynchronously. It
85+
* fires some events that are then delegated to other reporters. We need to suppress them (by
86+
* increasing the call depth so that {@link DispatchEventAdvice} is aborted) as the same events
87+
* are reported earlier synchronously from {@link org.scalatest.tools.TestSortingReporter#apply}
88+
*/
89+
public static class SuppressAsyncEventsAdvice {
90+
@Advice.OnMethodEnter
91+
public static void onAsyncEventsTrigger() {
92+
CallDepthThreadLocalMap.incrementCallDepth(Reporter.class);
93+
}
94+
95+
@Advice.OnMethodExit
96+
public static void afterAsyncEventsTrigger() {
97+
CallDepthThreadLocalMap.decrementCallDepth(Reporter.class);
98+
}
6499
}
65100
}

0 commit comments

Comments
 (0)