Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit 3fe1b2d

Browse files
Added test suite level visibility for JUnit 3.8 test cases (DataDog#6320)
1 parent 4d0b113 commit 3fe1b2d

File tree

7 files changed

+322
-109
lines changed

7 files changed

+322
-109
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
package datadog.trace.instrumentation.junit4;
2+
3+
import static datadog.trace.agent.tooling.bytebuddy.matcher.NameMatchers.named;
4+
import static net.bytebuddy.matcher.ElementMatchers.takesArgument;
5+
6+
import com.google.auto.service.AutoService;
7+
import datadog.trace.agent.tooling.Instrumenter;
8+
import java.util.List;
9+
import junit.framework.TestCase;
10+
import net.bytebuddy.asm.Advice;
11+
import org.junit.rules.RuleChain;
12+
import org.junit.runner.Runner;
13+
import org.junit.runner.notification.RunListener;
14+
import org.junit.runner.notification.RunNotifier;
15+
16+
/** Supports suite started/finished events for {@link TestCase} subclasses. */
17+
@AutoService(Instrumenter.class)
18+
public class JUnit38SuiteEventsInstrumentation extends Instrumenter.CiVisibility
19+
implements Instrumenter.ForSingleType {
20+
21+
public JUnit38SuiteEventsInstrumentation() {
22+
super("ci-visibility", "junit-4", "junit-38");
23+
}
24+
25+
@Override
26+
public String instrumentedType() {
27+
return "org.junit.internal.runners.JUnit38ClassRunner";
28+
}
29+
30+
@Override
31+
public String[] helperClassNames() {
32+
return new String[] {
33+
packageName + ".TestEventsHandlerHolder",
34+
packageName + ".SkippedByItr",
35+
packageName + ".JUnit4Utils",
36+
packageName + ".TracingListener",
37+
packageName + ".JUnit4TracingListener",
38+
};
39+
}
40+
41+
@Override
42+
public void adviceTransformations(AdviceTransformation transformation) {
43+
transformation.applyAdvice(
44+
named("run").and(takesArgument(0, named("org.junit.runner.notification.RunNotifier"))),
45+
JUnit38SuiteEventsInstrumentation.class.getName() + "$JUnit38SuiteEventsAdvice");
46+
}
47+
48+
public static class JUnit38SuiteEventsAdvice {
49+
@Advice.OnMethodEnter(suppress = Throwable.class)
50+
public static void fireSuiteStartedEvent(
51+
@Advice.Argument(0) final RunNotifier runNotifier, @Advice.This final Runner runner) {
52+
final List<RunListener> runListeners = JUnit4Utils.runListenersFromRunNotifier(runNotifier);
53+
if (runListeners == null) {
54+
return;
55+
}
56+
57+
for (final RunListener listener : runListeners) {
58+
TracingListener tracingListener = JUnit4Utils.toTracingListener(listener);
59+
if (tracingListener != null) {
60+
tracingListener.testSuiteStarted(runner.getDescription());
61+
}
62+
}
63+
}
64+
65+
@Advice.OnMethodExit(suppress = Throwable.class)
66+
public static void fireSuiteFinishedEvent(
67+
@Advice.Argument(0) final RunNotifier runNotifier, @Advice.This final Runner runner) {
68+
final List<RunListener> runListeners = JUnit4Utils.runListenersFromRunNotifier(runNotifier);
69+
if (runListeners == null) {
70+
return;
71+
}
72+
73+
for (final RunListener listener : runListeners) {
74+
TracingListener tracingListener = JUnit4Utils.toTracingListener(listener);
75+
if (tracingListener != null) {
76+
tracingListener.testSuiteFinished(runner.getDescription());
77+
}
78+
}
79+
}
80+
81+
// JUnit 4.10 and above
82+
public static void muzzleCheck(final RuleChain ruleChain) {
83+
ruleChain.apply(null, null);
84+
}
85+
}
86+
}

‎dd-java-agent/instrumentation/junit-4.10/src/main/java/datadog/trace/instrumentation/junit4/JUnit4Utils.java

Lines changed: 30 additions & 107 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,27 @@
11
package datadog.trace.instrumentation.junit4;
22

33
import datadog.trace.api.civisibility.config.SkippableTest;
4+
import datadog.trace.util.MethodHandles;
45
import datadog.trace.util.Strings;
56
import java.lang.annotation.Annotation;
67
import java.lang.invoke.MethodHandle;
7-
import java.lang.invoke.MethodHandles;
8-
import java.lang.reflect.Field;
98
import java.lang.reflect.Method;
109
import java.util.ArrayList;
1110
import java.util.Collection;
1211
import java.util.List;
1312
import java.util.regex.Matcher;
1413
import java.util.regex.Pattern;
1514
import javax.annotation.Nullable;
15+
import junit.framework.TestCase;
1616
import org.junit.Test;
1717
import org.junit.experimental.categories.Category;
1818
import org.junit.runner.Description;
1919
import org.junit.runner.notification.RunListener;
2020
import org.junit.runner.notification.RunNotifier;
2121
import org.junit.runners.ParentRunner;
22-
import org.slf4j.Logger;
23-
import org.slf4j.LoggerFactory;
2422

2523
public abstract class JUnit4Utils {
2624

27-
private static final Logger log = LoggerFactory.getLogger(JUnit4Utils.class);
2825
private static final String SYNCHRONIZED_LISTENER =
2926
"org.junit.runner.notification.SynchronizedRunListener";
3027

@@ -34,91 +31,37 @@ public abstract class JUnit4Utils {
3431
private static final Pattern METHOD_AND_CLASS_NAME_PATTERN =
3532
Pattern.compile("([\\s\\S]*)\\((.*)\\)");
3633

37-
private static final MethodHandle PARENT_RUNNER_DESCRIBE_CHILD;
38-
private static final MethodHandle RUN_NOTIFIER_LISTENERS;
39-
private static final MethodHandle INNER_SYNCHRONIZED_LISTENER;
40-
private static final MethodHandle DESCRIPTION_UNIQUE_ID;
41-
42-
static {
43-
MethodHandles.Lookup lookup = MethodHandles.lookup();
44-
PARENT_RUNNER_DESCRIBE_CHILD = accessDescribeChildMethodInParentRunner(lookup);
45-
RUN_NOTIFIER_LISTENERS = accessListenersFieldInRunNotifier(lookup);
46-
INNER_SYNCHRONIZED_LISTENER = accessListenerFieldInSynchronizedListener(lookup);
47-
DESCRIPTION_UNIQUE_ID = accessUniqueIdInDescription(lookup);
48-
}
49-
50-
private static MethodHandle accessDescribeChildMethodInParentRunner(MethodHandles.Lookup lookup) {
51-
try {
52-
Method describeChild = ParentRunner.class.getDeclaredMethod("describeChild", Object.class);
53-
describeChild.setAccessible(true);
54-
return lookup.unreflect(describeChild);
55-
} catch (Exception e) {
56-
return null;
57-
}
58-
}
59-
60-
private static MethodHandle accessListenersFieldInRunNotifier(MethodHandles.Lookup lookup) {
61-
try {
62-
Field listeners;
63-
try {
64-
// Since JUnit 4.12, the field is called "listeners"
65-
listeners = RunNotifier.class.getDeclaredField("listeners");
66-
} catch (final NoSuchFieldException e) {
67-
// Before JUnit 4.12, the field is called "fListeners"
68-
listeners = RunNotifier.class.getDeclaredField("fListeners");
69-
}
70-
71-
listeners.setAccessible(true);
72-
return lookup.unreflectGetter(listeners);
73-
} catch (final Throwable e) {
74-
log.debug("Could not get runListeners for JUnit4Advice", e);
75-
return null;
34+
private static final MethodHandles METHOD_HANDLES =
35+
new MethodHandles(ParentRunner.class.getClassLoader());
36+
private static final MethodHandle PARENT_RUNNER_DESCRIBE_CHILD =
37+
METHOD_HANDLES.method(ParentRunner.class, "describeChild", Object.class);
38+
private static final MethodHandle RUN_NOTIFIER_LISTENERS = accessListenersFieldInRunNotifier();
39+
private static final MethodHandle INNER_SYNCHRONIZED_LISTENER =
40+
accessListenerFieldInSynchronizedListener();
41+
private static final MethodHandle DESCRIPTION_UNIQUE_ID =
42+
METHOD_HANDLES.privateFieldGetter(Description.class, "fUniqueId");
43+
44+
private static MethodHandle accessListenersFieldInRunNotifier() {
45+
MethodHandle listeners = METHOD_HANDLES.privateFieldGetter(RunNotifier.class, "listeners");
46+
if (listeners != null) {
47+
return listeners;
7648
}
49+
// Before JUnit 4.12, the field is called "fListeners"
50+
return METHOD_HANDLES.privateFieldGetter(RunNotifier.class, "fListeners");
7751
}
7852

79-
private static MethodHandle accessListenerFieldInSynchronizedListener(
80-
MethodHandles.Lookup lookup) {
81-
ClassLoader classLoader = RunListener.class.getClassLoader();
82-
MethodHandle handle = accessListenerFieldInSynchronizedListener(lookup, classLoader);
53+
private static MethodHandle accessListenerFieldInSynchronizedListener() {
54+
MethodHandle handle = METHOD_HANDLES.privateFieldGetter(SYNCHRONIZED_LISTENER, "listener");
8355
if (handle != null) {
8456
return handle;
85-
} else {
86-
ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
87-
return accessListenerFieldInSynchronizedListener(lookup, contextClassLoader);
88-
}
89-
}
90-
91-
private static MethodHandle accessListenerFieldInSynchronizedListener(
92-
MethodHandles.Lookup lookup, ClassLoader classLoader) {
93-
try {
94-
Class<?> synchronizedListenerClass = classLoader.loadClass(SYNCHRONIZED_LISTENER);
95-
final Field innerListenerField = synchronizedListenerClass.getDeclaredField("listener");
96-
innerListenerField.setAccessible(true);
97-
return lookup.unreflectGetter(innerListenerField);
98-
} catch (Exception e) {
99-
return null;
100-
}
101-
}
102-
103-
private static MethodHandle accessUniqueIdInDescription(MethodHandles.Lookup lookup) {
104-
try {
105-
final Field uniqueIdField = Description.class.getDeclaredField("fUniqueId");
106-
uniqueIdField.setAccessible(true);
107-
return lookup.unreflectGetter(uniqueIdField);
108-
} catch (Throwable throwable) {
109-
return null;
11057
}
58+
ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
59+
return new MethodHandles(contextClassLoader)
60+
.privateFieldGetter(SYNCHRONIZED_LISTENER, "listeners");
11161
}
11262

11363
public static List<RunListener> runListenersFromRunNotifier(final RunNotifier runNotifier) {
114-
try {
115-
if (RUN_NOTIFIER_LISTENERS != null) {
116-
return (List<RunListener>) RUN_NOTIFIER_LISTENERS.invoke(runNotifier);
117-
}
118-
} catch (final Throwable e) {
119-
log.debug("Could not get runListeners for JUnit4Advice", e);
120-
}
121-
return null;
64+
return METHOD_HANDLES.invoke(RUN_NOTIFIER_LISTENERS, runNotifier);
12265
}
12366

12467
public static TracingListener toTracingListener(final RunListener listener) {
@@ -128,15 +71,9 @@ public static TracingListener toTracingListener(final RunListener listener) {
12871

12972
// Since JUnit 4.12, the RunListener are wrapped by a SynchronizedRunListener object.
13073
if (SYNCHRONIZED_LISTENER.equals(listener.getClass().getName())) {
131-
try {
132-
if (INNER_SYNCHRONIZED_LISTENER != null) {
133-
Object innerListener = INNER_SYNCHRONIZED_LISTENER.invoke(listener);
134-
if (innerListener instanceof TracingListener) {
135-
return (TracingListener) innerListener;
136-
}
137-
}
138-
} catch (final Throwable e) {
139-
log.debug("Could not get inner listener from SynchronizedRunListener", e);
74+
RunListener innerListener = METHOD_HANDLES.invoke(INNER_SYNCHRONIZED_LISTENER, listener);
75+
if (innerListener instanceof TracingListener) {
76+
return (TracingListener) innerListener;
14077
}
14178
}
14279
return null;
@@ -301,18 +238,11 @@ public static boolean isSuiteContainingChildren(final Description description) {
301238
return true;
302239
}
303240
}
304-
return false;
241+
return TestCase.class.isAssignableFrom(testClass);
305242
}
306243

307244
public static Object getUniqueId(final Description description) {
308-
try {
309-
if (DESCRIPTION_UNIQUE_ID != null) {
310-
return DESCRIPTION_UNIQUE_ID.invoke(description);
311-
}
312-
} catch (Throwable e) {
313-
log.error("Could not get unique ID from descriptions: " + description, e);
314-
}
315-
return null;
245+
return METHOD_HANDLES.invoke(DESCRIPTION_UNIQUE_ID, description);
316246
}
317247

318248
public static String getSuiteName(final Class<?> testClass, final Description description) {
@@ -332,14 +262,7 @@ public static List<Method> getTestMethods(final Class<?> testClass) {
332262
}
333263

334264
public static Description getDescription(ParentRunner<?> runner, Object child) {
335-
try {
336-
if (PARENT_RUNNER_DESCRIBE_CHILD != null) {
337-
return (Description) PARENT_RUNNER_DESCRIBE_CHILD.invokeWithArguments(runner, child);
338-
}
339-
} catch (Throwable e) {
340-
log.error("Could not describe child: " + child, e);
341-
}
342-
return null;
265+
return METHOD_HANDLES.invoke(PARENT_RUNNER_DESCRIBE_CHILD, runner, child);
343266
}
344267

345268
public static Description getSkippedDescription(Description description) {

‎dd-java-agent/instrumentation/junit-4.10/src/test/groovy/JUnit4Test.groovy

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import org.example.TestSkipped
1717
import org.example.TestSkippedClass
1818
import org.example.TestSucceed
1919
import org.example.TestSucceedAndSkipped
20+
import org.example.TestSucceedLegacy
2021
import org.example.TestSucceedSuite
2122
import org.example.TestSucceedUnskippable
2223
import org.example.TestSucceedUnskippableSuite
@@ -66,6 +67,7 @@ class JUnit4Test extends CiVisibilityInstrumentationTest {
6667
"test-itr-unskippable" | [TestSucceedUnskippable] | 2 | [new SkippableTest("org.example.TestSucceedUnskippable", "test_succeed", null, null)]
6768
"test-itr-unskippable-suite" | [TestSucceedUnskippableSuite] | 2 | [new SkippableTest("org.example.TestSucceedUnskippableSuite", "test_succeed", null, null)]
6869
"test-itr-unskippable-not-skipped" | [TestSucceedUnskippable] | 2 | []
70+
"test-legacy" | [TestSucceedLegacy] | 2 | []
6971
}
7072

7173
private void runTests(Collection<Class<?>> tests) {
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package org.example;
2+
3+
import junit.framework.TestCase;
4+
5+
public class TestSucceedLegacy extends TestCase {
6+
7+
public void test_succeed() {
8+
assertTrue(true);
9+
}
10+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
[ ]

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /