Skip to content

Commit 35ebb15

Browse files
feat: lazy start of suites for bazel runner
1 parent 8fc6885 commit 35ebb15

1 file changed

Lines changed: 76 additions & 0 deletions

File tree

  • dd-java-agent/instrumentation/junit/junit-4/junit-4.10/src/main/java/datadog/trace/instrumentation/junit4

dd-java-agent/instrumentation/junit/junit-4/junit-4.10/src/main/java/datadog/trace/instrumentation/junit4/JUnit4TracingListener.java

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,11 @@
88
import datadog.trace.bootstrap.ContextStore;
99
import java.lang.reflect.Method;
1010
import java.util.List;
11+
import java.util.Set;
12+
import java.util.concurrent.ConcurrentHashMap;
1113
import org.junit.Ignore;
1214
import org.junit.runner.Description;
15+
import org.junit.runner.Result;
1316
import org.junit.runner.notification.Failure;
1417

1518
public class JUnit4TracingListener extends TracingListener {
@@ -19,6 +22,26 @@ public class JUnit4TracingListener extends TracingListener {
1922

2023
private final ContextStore<Description, TestExecutionTracker> executionTrackers;
2124

25+
/**
26+
* Suites for which {@code onTestSuiteStart} has been fired (from either the normal
27+
* ParentRunner-based flow or via lazy-registration in {@link #testStarted}). Used to keep
28+
* lifecycle events idempotent and to know which auto-started suite still needs closing.
29+
*/
30+
private final Set<TestSuiteDescriptor> startedSuites = ConcurrentHashMap.newKeySet();
31+
32+
/**
33+
* Last suite lazy-started from {@link #testStarted} because no {@link #testSuiteStarted} event
34+
* was observed for it first. This has been seen under {@code
35+
* com.google.testing.junit.runner.BazelTestRunner}, where the suite-start advice in {@code
36+
* JUnit4SuiteEventsInstrumentation} does not fire for reasons still to be pinpointed (likely a
37+
* classloader or runner-wrapping quirk specific to the Bazel test launcher). Closed when the next
38+
* test belongs to a different suite, or when the whole test run finishes.
39+
*
40+
* <p>TODO: investigate the exact cause under {@code BazelTestRunner} and add a dedicated
41+
* instrumentation that emits proper suite-lifecycle events instead of relying on this fallback.
42+
*/
43+
private volatile TestSuiteDescriptor autoStartedSuite;
44+
2245
public JUnit4TracingListener(ContextStore<Description, TestExecutionTracker> executionTrackers) {
2346
this.executionTrackers = executionTrackers;
2447
}
@@ -32,6 +55,9 @@ public void testSuiteStarted(final Description description) {
3255
}
3356

3457
TestSuiteDescriptor suiteDescriptor = JUnit4Utils.toSuiteDescriptor(description);
58+
if (!startedSuites.add(suiteDescriptor)) {
59+
return; // already started (idempotent vs. lazy-registration or duplicate events)
60+
}
3561
Class<?> testClass = description.getTestClass();
3662
String testSuiteName = JUnit4Utils.getSuiteName(testClass, description);
3763
List<String> categories = JUnit4Utils.getCategories(testClass, null);
@@ -58,6 +84,9 @@ public void testSuiteFinished(final Description description) {
5884
}
5985

6086
TestSuiteDescriptor suiteDescriptor = JUnit4Utils.toSuiteDescriptor(description);
87+
if (!startedSuites.remove(suiteDescriptor)) {
88+
return; // never started
89+
}
6190
TestEventsHandlerHolder.HANDLERS
6291
.get(TestFrameworkInstrumentation.JUNIT4)
6392
.onTestSuiteFinish(suiteDescriptor, null);
@@ -73,6 +102,8 @@ public void testStarted(final Description description) {
73102
TestDescriptor testDescriptor = JUnit4Utils.toTestDescriptor(description);
74103
TestSourceData testSourceData = JUnit4Utils.toTestSourceData(description);
75104

105+
lazyStartSuiteIfNeeded(suiteDescriptor, description, testSourceData);
106+
76107
String testName = JUnit4Utils.getTestName(description, testSourceData.getTestMethod());
77108
String testParameters = JUnit4Utils.getParameters(description);
78109
List<String> categories =
@@ -93,6 +124,51 @@ public void testStarted(final Description description) {
93124
executionTrackers.get(description));
94125
}
95126

127+
@Override
128+
public void testRunFinished(Result result) {
129+
closeAutoStartedSuite();
130+
}
131+
132+
private void lazyStartSuiteIfNeeded(
133+
TestSuiteDescriptor newSuite, Description description, TestSourceData testSourceData) {
134+
if (startedSuites.contains(newSuite)) {
135+
return; // suite already started normally or by a prior lazy-registration for this same suite
136+
}
137+
// Close any previous auto-started suite (typical when Bazel runs multiple classes in one JVM).
138+
closeAutoStartedSuite();
139+
140+
Class<?> testClass = testSourceData.getTestClass();
141+
String testSuiteName = JUnit4Utils.getSuiteName(testClass, description);
142+
List<String> categories = JUnit4Utils.getCategories(testClass, null);
143+
TestEventsHandlerHolder.HANDLERS
144+
.get(TestFrameworkInstrumentation.JUNIT4)
145+
.onTestSuiteStart(
146+
newSuite,
147+
testSuiteName,
148+
FRAMEWORK_NAME,
149+
FRAMEWORK_VERSION,
150+
testClass,
151+
categories,
152+
false,
153+
TestFrameworkInstrumentation.JUNIT4,
154+
null);
155+
startedSuites.add(newSuite);
156+
autoStartedSuite = newSuite;
157+
}
158+
159+
private void closeAutoStartedSuite() {
160+
TestSuiteDescriptor suite = autoStartedSuite;
161+
if (suite == null) {
162+
return;
163+
}
164+
autoStartedSuite = null;
165+
if (startedSuites.remove(suite)) {
166+
TestEventsHandlerHolder.HANDLERS
167+
.get(TestFrameworkInstrumentation.JUNIT4)
168+
.onTestSuiteFinish(suite, null);
169+
}
170+
}
171+
96172
@Override
97173
public void testFinished(final Description description) {
98174
if (JUnit4Utils.isJUnitPlatformRunnerTest(description)) {

0 commit comments

Comments
 (0)