Skip to content

Commit 7536862

Browse files
committed
feat(test): Extract config cleanup assertions into dedicated JUnit extension
Move DD_* environment variable and dd.* system property validation logic from DDJavaSpecification into a reusable CleanConfigStateExtension. Improves error reporting with detailed leak information.
1 parent b37c48c commit 7536862

2 files changed

Lines changed: 85 additions & 29 deletions

File tree

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
package datadog.trace.test.util;
2+
3+
import static org.junit.jupiter.api.AssertionFailureBuilder.assertionFailure;
4+
5+
import datadog.environment.EnvironmentVariables;
6+
import de.thetaphi.forbiddenapis.SuppressForbidden;
7+
import java.util.Arrays;
8+
import java.util.List;
9+
import java.util.Map;
10+
import java.util.Map.Entry;
11+
import java.util.TreeMap;
12+
import java.util.function.Predicate;
13+
import java.util.stream.Collectors;
14+
import org.junit.jupiter.api.extension.AfterAllCallback;
15+
import org.junit.jupiter.api.extension.BeforeAllCallback;
16+
import org.junit.jupiter.api.extension.ExtensionContext;
17+
18+
/**
19+
* Asserts that no {@code DD_*} environment variable and no {@code dd.*} system property (minus a
20+
* small allowlist) is set around a test class.
21+
*/
22+
@SuppressForbidden
23+
public class CleanConfigStateExtension implements BeforeAllCallback, AfterAllCallback {
24+
25+
private static final List<String> ALLOWED_SYS_PROPS =
26+
Arrays.asList(
27+
"dd.appsec.enabled", "dd.iast.enabled", "dd.integration.grizzly-filterchain.enabled");
28+
29+
private static final Predicate<String> DATADOG_ENV_VAR_FILTER = k -> k.startsWith("DD_");
30+
private static final Predicate<Object> DATADOG_SYS_PROPERTIES_FILTER =
31+
o -> {
32+
String key = (String) o;
33+
return key.startsWith("DD_") && !ALLOWED_SYS_PROPS.contains(key);
34+
};
35+
36+
@Override
37+
public void beforeAll(ExtensionContext context) {
38+
assertClean("before");
39+
}
40+
41+
@Override
42+
public void afterAll(ExtensionContext context) {
43+
assertClean("after");
44+
}
45+
46+
private static void assertClean(String phase) {
47+
Map<String, String> leakedEnv =
48+
filterMap(EnvironmentVariables.getAll(), DATADOG_ENV_VAR_FILTER);
49+
Map<Object, Object> leakedSys =
50+
filterMap(System.getProperties(), DATADOG_SYS_PROPERTIES_FILTER);
51+
if (!leakedEnv.isEmpty() || !leakedSys.isEmpty()) {
52+
assertionFailure()
53+
.message("Leaked Datadog configuration detected " + phase + " test class")
54+
.reason(formatLeaks(leakedEnv, leakedSys))
55+
.buildAndThrow();
56+
}
57+
}
58+
59+
private static <T> Map<T, T> filterMap(Map<T, T> map, Predicate<T> keyFilter) {
60+
return map.entrySet().stream()
61+
.filter(e -> keyFilter.test(e.getKey()))
62+
.collect(Collectors.toMap(Entry::getKey, Entry::getValue, (a, b) -> a, TreeMap::new));
63+
}
64+
65+
private static String formatLeaks(Map<String, String> env, Map<Object, Object> sys) {
66+
StringBuilder sb = new StringBuilder();
67+
if (!env.isEmpty()) {
68+
sb.append("environment variables:");
69+
env.forEach((k, v) -> sb.append("\n ").append(k).append('=').append(v));
70+
}
71+
if (!sys.isEmpty()) {
72+
if (sb.length() > 0) {
73+
sb.append('\n');
74+
}
75+
sb.append("system properties:");
76+
sys.forEach((k, v) -> sb.append("\n ").append(k).append('=').append(v));
77+
}
78+
return sb.toString();
79+
}
80+
}

utils/test-utils/src/main/java/datadog/trace/test/util/DDJavaSpecification.java

Lines changed: 5 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,21 @@
11
package datadog.trace.test.util;
22

3-
import static org.junit.jupiter.api.Assertions.assertTrue;
4-
5-
import datadog.environment.EnvironmentVariables;
63
import datadog.trace.junit.utils.config.WithConfigExtension;
74
import datadog.trace.junit.utils.context.AllowContextTestingExtension;
85
import de.thetaphi.forbiddenapis.SuppressForbidden;
9-
import java.util.Arrays;
106
import java.util.List;
11-
import java.util.Map;
127
import java.util.Set;
138
import java.util.stream.Collectors;
149
import org.junit.jupiter.api.AfterAll;
1510
import org.junit.jupiter.api.AfterEach;
1611
import org.junit.jupiter.api.BeforeAll;
1712
import org.junit.jupiter.api.extension.ExtendWith;
1813

19-
@ExtendWith({WithConfigExtension.class, AllowContextTestingExtension.class})
14+
@ExtendWith({
15+
CleanConfigStateExtension.class,
16+
WithConfigExtension.class,
17+
AllowContextTestingExtension.class
18+
})
2019
@SuppressForbidden
2120
public class DDJavaSpecification {
2221

@@ -27,13 +26,6 @@ public class DDJavaSpecification {
2726

2827
@BeforeAll
2928
static void beforeAll() {
30-
assertTrue(
31-
EnvironmentVariables.getAll().entrySet().stream()
32-
.noneMatch(e -> e.getKey().startsWith("DD_")));
33-
assertTrue(
34-
systemPropertiesExceptAllowed().entrySet().stream()
35-
.noneMatch(e -> e.getKey().toString().startsWith("dd.")));
36-
3729
if (getDDThreads().isEmpty()) {
3830
ignoreThreadCleanup = false;
3931
} else {
@@ -45,25 +37,9 @@ static void beforeAll() {
4537

4638
@AfterAll
4739
static void afterAll() {
48-
assertTrue(
49-
EnvironmentVariables.getAll().entrySet().stream()
50-
.noneMatch(e -> e.getKey().startsWith("DD_")));
51-
assertTrue(
52-
systemPropertiesExceptAllowed().entrySet().stream()
53-
.noneMatch(e -> e.getKey().toString().startsWith("dd.")));
54-
5540
checkThreads();
5641
}
5742

58-
private static Map<Object, Object> systemPropertiesExceptAllowed() {
59-
List<String> allowlist =
60-
Arrays.asList(
61-
"dd.appsec.enabled", "dd.iast.enabled", "dd.integration.grizzly-filterchain.enabled");
62-
return System.getProperties().entrySet().stream()
63-
.filter(e -> !allowlist.contains(String.valueOf(e.getKey())))
64-
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
65-
}
66-
6743
@AfterEach
6844
void cleanup() {
6945
if (assertThreadsEachCleanup) {

0 commit comments

Comments
 (0)