Skip to content

Commit 5adf6e3

Browse files
mccullsdevflow.devflow-routing-intake
andauthored
Add ClassDefining tests and ignore extraneous observer calls. (#11644)
Add ClassDefining tests and ignore extraneous observer calls. Co-authored-by: devflow.devflow-routing-intake <devflow.devflow-routing-intake@kubernetes.us1.ddbuild.io>
1 parent 26573a6 commit 5adf6e3

2 files changed

Lines changed: 101 additions & 1 deletion

File tree

dd-java-agent/agent-bootstrap/src/main/java/datadog/trace/bootstrap/instrumentation/classloading/ClassDefining.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,17 @@
11
package datadog.trace.bootstrap.instrumentation.classloading;
22

3+
import java.util.concurrent.atomic.AtomicBoolean;
4+
35
/** Provides a way for a single optional observer to be notified before a class is defined. */
46
public final class ClassDefining {
7+
private static final AtomicBoolean HAS_OBSERVER = new AtomicBoolean();
58
private static volatile Observer OBSERVER = (loader, bytecode, offset, length) -> {};
69

710
/** Registers the given observer to get notifications about class definitions. */
811
public static void observe(Observer observer) {
9-
OBSERVER = observer;
12+
if (HAS_OBSERVER.compareAndSet(false, true)) {
13+
OBSERVER = observer; // set once in premain
14+
}
1015
}
1116

1217
/** Called from advice added to j.l.ClassLoader by DefineClassInstrumentation. */
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
package datadog.trace.bootstrap.instrumentation.classloading;
2+
3+
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
4+
import static org.junit.jupiter.api.Assertions.assertEquals;
5+
import static org.junit.jupiter.api.Assertions.assertSame;
6+
7+
import java.lang.reflect.Field;
8+
import java.util.concurrent.atomic.AtomicBoolean;
9+
import java.util.concurrent.atomic.AtomicInteger;
10+
import org.junit.jupiter.api.BeforeEach;
11+
import org.junit.jupiter.api.Test;
12+
13+
class ClassDefiningTest {
14+
15+
@BeforeEach
16+
void resetStaticState() throws Exception {
17+
Field hasObserver = ClassDefining.class.getDeclaredField("HAS_OBSERVER");
18+
hasObserver.setAccessible(true);
19+
((AtomicBoolean) hasObserver.get(null)).set(false);
20+
21+
Field observer = ClassDefining.class.getDeclaredField("OBSERVER");
22+
observer.setAccessible(true);
23+
observer.set(null, (ClassDefining.Observer) (loader, bytecode, offset, length) -> {});
24+
}
25+
26+
@Test
27+
void beginWithNoObserverIsNoOp() {
28+
assertDoesNotThrow(() -> ClassDefining.begin(null, new byte[10], 0, 10));
29+
}
30+
31+
@Test
32+
void beginCallsRegisteredObserverOnEachInvocation() {
33+
AtomicInteger calls = new AtomicInteger();
34+
ClassDefining.observe((loader, bytecode, offset, length) -> calls.incrementAndGet());
35+
36+
ClassDefining.begin(null, new byte[4], 0, 4);
37+
ClassDefining.begin(null, new byte[4], 0, 4);
38+
39+
assertEquals(2, calls.get());
40+
}
41+
42+
@Test
43+
void observerReceivesCorrectArguments() {
44+
ClassLoader loader = ClassLoader.getSystemClassLoader();
45+
byte[] bytecode = {1, 2, 3, 4, 5};
46+
47+
ClassLoader[] capturedLoader = new ClassLoader[1];
48+
byte[][] capturedBytecode = new byte[1][];
49+
int[] capturedOffset = new int[1];
50+
int[] capturedLength = new int[1];
51+
52+
ClassDefining.observe(
53+
(l, b, o, len) -> {
54+
capturedLoader[0] = l;
55+
capturedBytecode[0] = b;
56+
capturedOffset[0] = o;
57+
capturedLength[0] = len;
58+
});
59+
60+
ClassDefining.begin(loader, bytecode, 1, 3);
61+
62+
assertSame(loader, capturedLoader[0]);
63+
assertSame(bytecode, capturedBytecode[0]);
64+
assertEquals(1, capturedOffset[0]);
65+
assertEquals(3, capturedLength[0]);
66+
}
67+
68+
@Test
69+
void secondObserveCallIsIgnoredFirstObserverRemains() {
70+
AtomicInteger firstCalls = new AtomicInteger();
71+
AtomicInteger secondCalls = new AtomicInteger();
72+
73+
ClassDefining.observe((loader, bytecode, offset, length) -> firstCalls.incrementAndGet());
74+
ClassDefining.observe((loader, bytecode, offset, length) -> secondCalls.incrementAndGet());
75+
76+
ClassDefining.begin(null, new byte[4], 0, 4);
77+
78+
assertEquals(1, firstCalls.get());
79+
assertEquals(0, secondCalls.get());
80+
}
81+
82+
@Test
83+
void observeIsIdempotentWhenCalledWithSameObserverRepeatedly() {
84+
AtomicInteger calls = new AtomicInteger();
85+
ClassDefining.Observer observer = (l, b, o, len) -> calls.incrementAndGet();
86+
87+
for (int i = 0; i < 5; i++) {
88+
ClassDefining.observe(observer);
89+
}
90+
91+
ClassDefining.begin(null, new byte[1], 0, 1);
92+
93+
assertEquals(1, calls.get());
94+
}
95+
}

0 commit comments

Comments
 (0)