7
7
import com .google .auto .service .AutoService ;
8
8
import datadog .trace .agent .tooling .Instrumenter ;
9
9
import datadog .trace .agent .tooling .InstrumenterModule ;
10
+ import datadog .trace .bootstrap .CallDepthThreadLocalMap ;
10
11
import java .util .Set ;
11
12
import net .bytebuddy .asm .Advice ;
13
+ import org .scalatest .Reporter ;
12
14
import org .scalatest .events .Event ;
13
15
14
16
@ AutoService (InstrumenterModule .class )
15
17
public class ScalatestInstrumentation extends InstrumenterModule .CiVisibility
16
- implements Instrumenter .ForSingleType , Instrumenter .HasMethodAdvice {
18
+ implements Instrumenter .ForKnownTypes , Instrumenter .HasMethodAdvice {
17
19
18
20
public ScalatestInstrumentation () {
19
21
super ("ci-visibility" , "scalatest" );
@@ -25,8 +27,10 @@ public boolean isApplicable(Set<TargetSystem> enabledSystems) {
25
27
}
26
28
27
29
@ 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
+ };
30
34
}
31
35
32
36
@ Override
@@ -46,13 +50,21 @@ public void methodAdvice(MethodTransformer transformer) {
46
50
.and (takesArguments (1 ))
47
51
.and (takesArgument (0 , named ("org.scalatest.events.Event" ))),
48
52
ScalatestInstrumentation .class .getName () + "$DispatchEventAdvice" );
53
+ transformer .applyAdvice (
54
+ named ("fireReadyEvents" ),
55
+ ScalatestInstrumentation .class .getName () + "$SuppressAsyncEventsAdvice" );
49
56
}
50
57
51
58
public static class DispatchEventAdvice {
52
59
@ Advice .OnMethodEnter
53
60
public static void onDispatchEvent (@ Advice .Argument (value = 0 ) Event event ) {
61
+ if (CallDepthThreadLocalMap .incrementCallDepth (Reporter .class ) != 0 ) {
62
+ // nested call
63
+ return ;
64
+ }
65
+
54
66
// Instead of registering our reporter using Scalatest's standard "-C" argument,
55
- // we hook into internal dispatch reporter.
67
+ // we hook into internal reporter.
56
68
// The reason is that Scalatest invokes registered reporters in a separate thread,
57
69
// while we need to process events in the thread where they originate.
58
70
// 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) {
61
73
// could be properly associated with it.
62
74
DatadogReporter .handle (event );
63
75
}
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
+ }
64
99
}
65
100
}
0 commit comments