|
28 | 28 | import kafka.test.annotation.Type;
|
29 | 29 |
|
30 | 30 | import org.apache.kafka.server.common.Features;
|
| 31 | +import org.apache.kafka.test.TestUtils; |
31 | 32 |
|
| 33 | +import org.junit.jupiter.api.extension.AfterEachCallback; |
| 34 | +import org.junit.jupiter.api.extension.BeforeEachCallback; |
32 | 35 | import org.junit.jupiter.api.extension.ExtensionContext;
|
| 36 | +import org.junit.jupiter.api.extension.ExtensionContext.Namespace; |
| 37 | +import org.junit.jupiter.api.extension.ExtensionContext.Store; |
33 | 38 | import org.junit.jupiter.api.extension.TestTemplateInvocationContext;
|
34 | 39 | import org.junit.jupiter.api.extension.TestTemplateInvocationContextProvider;
|
35 | 40 | import org.junit.platform.commons.util.ReflectionUtils;
|
36 | 41 |
|
37 | 42 | import java.lang.reflect.Method;
|
38 | 43 | import java.util.ArrayList;
|
39 | 44 | import java.util.Arrays;
|
| 45 | +import java.util.Collections; |
40 | 46 | import java.util.HashSet;
|
41 | 47 | import java.util.List;
|
42 | 48 | import java.util.Map;
|
43 | 49 | import java.util.Optional;
|
| 50 | +import java.util.Set; |
| 51 | +import java.util.concurrent.atomic.AtomicReference; |
44 | 52 | import java.util.function.Function;
|
45 | 53 | import java.util.stream.Collectors;
|
46 | 54 | import java.util.stream.Stream;
|
|
82 | 90 | * SomeIntegrationTest will be instantiated, lifecycle methods (before/after) will be run, and "someTest" will be invoked.
|
83 | 91 | *
|
84 | 92 | */
|
85 |
| -public class ClusterTestExtensions implements TestTemplateInvocationContextProvider { |
| 93 | +public class ClusterTestExtensions implements TestTemplateInvocationContextProvider, BeforeEachCallback, AfterEachCallback { |
| 94 | + private static final String METRICS_METER_TICK_THREAD_PREFIX = "metrics-meter-tick-thread"; |
| 95 | + private static final String SCALA_THREAD_PREFIX = "scala-"; |
| 96 | + private static final String FORK_JOIN_POOL_THREAD_PREFIX = "ForkJoinPool"; |
| 97 | + private static final String JUNIT_THREAD_PREFIX = "junit-"; |
| 98 | + private static final String ATTACH_LISTENER_THREAD_PREFIX = "Attach Listener"; |
| 99 | + private static final String PROCESS_REAPER_THREAD_PREFIX = "process reaper"; |
| 100 | + private static final String RMI_THREAD_PREFIX = "RMI"; |
| 101 | + private static final String DETECT_THREAD_LEAK_KEY = "detectThreadLeak"; |
| 102 | + private static final Set<String> SKIPPED_THREAD_PREFIX = Collections.unmodifiableSet(Stream.of( |
| 103 | + METRICS_METER_TICK_THREAD_PREFIX, SCALA_THREAD_PREFIX, FORK_JOIN_POOL_THREAD_PREFIX, JUNIT_THREAD_PREFIX, |
| 104 | + ATTACH_LISTENER_THREAD_PREFIX, PROCESS_REAPER_THREAD_PREFIX, RMI_THREAD_PREFIX) |
| 105 | + .collect(Collectors.toSet())); |
| 106 | + |
86 | 107 | @Override
|
87 | 108 | public boolean supportsTestTemplate(ExtensionContext context) {
|
88 | 109 | return true;
|
@@ -119,7 +140,31 @@ public Stream<TestTemplateInvocationContext> provideTestTemplateInvocationContex
|
119 | 140 | return generatedContexts.stream();
|
120 | 141 | }
|
121 | 142 |
|
| 143 | + @Override |
| 144 | + public void beforeEach(ExtensionContext context) { |
| 145 | + DetectThreadLeak detectThreadLeak = DetectThreadLeak.of(thread -> |
| 146 | + SKIPPED_THREAD_PREFIX.stream().noneMatch(prefix -> thread.getName().startsWith(prefix))); |
| 147 | + getStore(context).put(DETECT_THREAD_LEAK_KEY, detectThreadLeak); |
| 148 | + } |
| 149 | + |
| 150 | + @Override |
| 151 | + public void afterEach(ExtensionContext context) throws InterruptedException { |
| 152 | + DetectThreadLeak detectThreadLeak = getStore(context).remove(DETECT_THREAD_LEAK_KEY, DetectThreadLeak.class); |
| 153 | + if (detectThreadLeak == null) { |
| 154 | + return; |
| 155 | + } |
| 156 | + AtomicReference<List<Thread>> lastThread = new AtomicReference<>(Collections.emptyList()); |
| 157 | + TestUtils.waitForCondition(() -> { |
| 158 | + List<Thread> threads = detectThreadLeak.newThreads(); |
| 159 | + lastThread.set(threads); |
| 160 | + return threads.isEmpty(); |
| 161 | + }, () -> "Thread leak detected: " + |
| 162 | + lastThread.get().stream().map(Thread::getName).collect(Collectors.joining(", "))); |
| 163 | + } |
122 | 164 |
|
| 165 | + private Store getStore(ExtensionContext context) { |
| 166 | + return context.getStore(Namespace.create(context.getUniqueId())); |
| 167 | + } |
123 | 168 |
|
124 | 169 | List<TestTemplateInvocationContext> processClusterTemplate(ExtensionContext context, ClusterTemplate annot) {
|
125 | 170 | if (annot.value().trim().isEmpty()) {
|
|
0 commit comments