This is an automated email from the ASF dual-hosted git repository.
lhotari pushed a commit to branch branch-3.0
in repository https://gitbox.apache.org/repos/asf/pulsar.git
The following commit(s) were added to refs/heads/branch-3.0 by this push:
new 4c816c15ecb [fix][test] Simplify BetweenTestClassesListenerAdapter and
fix issue with BeforeTest/AfterTest annotations (#24304)
4c816c15ecb is described below
commit 4c816c15ecbc70d598df33b0a1fbd6a38f707a52
Author: Lari Hotari <[email protected]>
AuthorDate: Thu May 15 07:16:27 2025 +0300
[fix][test] Simplify BetweenTestClassesListenerAdapter and fix issue with
BeforeTest/AfterTest annotations (#24304)
It wasn't clear how test class changes should be detected with TestNG.
The solution was recently revisited in #24258. However problems remain
after adding that solution. BeforeTest/AfterTest annotation usage won't work
and cause false reports for detected leaks.
It turns out that it's possible to detect a test change with
ITestListener's `onFinish` callback. In TestNG, it's possible to group multiple
test classes into a single test, but this is only when using TestNG's XML
configuration for test suites. maven-surefire-plugin will create a separate
TestNG test for each test class, so that's why it's the correct solution for
BetweenTestClassesListenerAdapter.
- Remove previous logic to detect test class change and instead rely on
ITestListener's `onFinish` callback
- Adapt the current listener implementations to the new base class
(cherry picked from commit 965ef5c14c93ca896ef4c8f34520066285fcf047)
---
.../tests/BetweenTestClassesListenerAdapter.java | 85 ++------
.../tests/FastThreadLocalCleanupListener.java | 5 +-
.../pulsar/tests/MockitoCleanupListener.java | 5 +-
.../pulsar/tests/SingletonCleanerListener.java | 5 +-
.../pulsar/tests/ThreadLeakDetectorListener.java | 242 +++++++++++++++++++--
.../BetweenTestClassesListenerAdapterTest.java | 23 +-
6 files changed, 264 insertions(+), 101 deletions(-)
diff --git
a/buildtools/src/main/java/org/apache/pulsar/tests/BetweenTestClassesListenerAdapter.java
b/buildtools/src/main/java/org/apache/pulsar/tests/BetweenTestClassesListenerAdapter.java
index 370ec7aa377..cd2639b7bc6 100644
---
a/buildtools/src/main/java/org/apache/pulsar/tests/BetweenTestClassesListenerAdapter.java
+++
b/buildtools/src/main/java/org/apache/pulsar/tests/BetweenTestClassesListenerAdapter.java
@@ -19,88 +19,33 @@
package org.apache.pulsar.tests;
import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import org.testng.IClass;
-import org.testng.IClassListener;
-import org.testng.IConfigurationListener;
import org.testng.ITestClass;
+import org.testng.ITestContext;
+import org.testng.ITestListener;
import org.testng.ITestNGMethod;
-import org.testng.ITestResult;
/**
- * TestNG listener adapter that detects when execution finishes in a test
class, including AfterClass methods.
- * TestNG's IClassListener.onAfterClass method is called before AfterClass
methods are executed,
- * which is why this solution is needed.
+ * TestNG listener adapter that detects when execution finishes for a test
class,
+ * assuming that a single test class is run in each context.
+ * This is the case when running tests with maven-surefire-plugin.
*/
-abstract class BetweenTestClassesListenerAdapter implements IClassListener,
IConfigurationListener {
+abstract class BetweenTestClassesListenerAdapter implements ITestListener {
private static final Logger log =
LoggerFactory.getLogger(BetweenTestClassesListenerAdapter.class);
- volatile Class<?> currentTestClass;
- volatile int remainingAfterClassMethodCount;
@Override
- public final void onBeforeClass(ITestClass testClass) {
- // for parameterized tests for the same class, the onBeforeClass
method is called for each instance
- // so we need to check if the test class is the same as for the
previous call before resetting the counter
- if (testClass.getRealClass() != currentTestClass) {
- // find out how many parameterized instances of the test class are
expected
- Object[] instances = testClass.getInstances(false);
- int instanceCount = instances != null && instances.length != 0 ?
instances.length : 1;
- // expect invocations of all annotated and enabled after class
methods
- int annotatedAfterClassMethodCount = (int)
Arrays.stream(testClass.getAfterClassMethods())
- .filter(ITestNGMethod::getEnabled)
- .count();
- // additionally expect invocations of the "onAfterClass" listener
method in this class
- int expectedMethodCountForEachInstance = 1 +
annotatedAfterClassMethodCount;
- // multiple by the number of instances
- remainingAfterClassMethodCount = instanceCount *
expectedMethodCountForEachInstance;
- currentTestClass = testClass.getRealClass();
- }
- }
-
- @Override
- public final void onAfterClass(ITestClass testClass) {
- handleAfterClassMethodCalled(testClass);
- }
-
- @Override
- public final void onConfigurationSuccess(ITestResult tr) {
- handleAfterClassConfigurationMethodCompletion(tr);
- }
-
- @Override
- public final void onConfigurationSkip(ITestResult tr) {
- handleAfterClassConfigurationMethodCompletion(tr);
- }
-
- @Override
- public final void onConfigurationFailure(ITestResult tr) {
- handleAfterClassConfigurationMethodCompletion(tr);
- }
-
- private void handleAfterClassConfigurationMethodCompletion(ITestResult tr)
{
- if (tr.getMethod().isAfterClassConfiguration() && !tr.wasRetried()) {
- handleAfterClassMethodCalled(tr.getTestClass());
- }
- }
-
- private void handleAfterClassMethodCalled(IClass testClass) {
- if (currentTestClass != testClass.getRealClass()) {
- log.error("Unexpected test class: {}. Expected: {}",
testClass.getRealClass(), currentTestClass);
- return;
- }
- remainingAfterClassMethodCount--;
- if (remainingAfterClassMethodCount == 0) {
- onBetweenTestClasses(testClass);
- } else if (remainingAfterClassMethodCount < 0) {
- // unexpected case, log it for easier debugging if this causes
test failures
- log.error("Remaining after class method count is negative: {} for
test class: {}",
- remainingAfterClassMethodCount, testClass.getRealClass());
- }
+ public final void onFinish(ITestContext context) {
+ List<ITestClass> testClasses =
+
Arrays.stream(context.getAllTestMethods()).map(ITestNGMethod::getTestClass).distinct()
+ .collect(Collectors.toList());
+ onBetweenTestClasses(testClasses);
}
/**
- * Call back hook for adding logic when test execution has completely
finished for a test class.
+ * Call back hook for adding logic when test execution has completely
finished for one or many test classes.
*/
- protected abstract void onBetweenTestClasses(IClass testClass);
+ protected abstract void onBetweenTestClasses(List<ITestClass> testClasses);
}
diff --git
a/buildtools/src/main/java/org/apache/pulsar/tests/FastThreadLocalCleanupListener.java
b/buildtools/src/main/java/org/apache/pulsar/tests/FastThreadLocalCleanupListener.java
index d1d205982d8..bdabf327dbb 100644
---
a/buildtools/src/main/java/org/apache/pulsar/tests/FastThreadLocalCleanupListener.java
+++
b/buildtools/src/main/java/org/apache/pulsar/tests/FastThreadLocalCleanupListener.java
@@ -18,9 +18,10 @@
*/
package org.apache.pulsar.tests;
+import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import org.testng.IClass;
+import org.testng.ITestClass;
/**
* Cleanup Thread Local state attach to Netty's FastThreadLocal.
@@ -49,7 +50,7 @@ public class FastThreadLocalCleanupListener extends
BetweenTestClassesListenerAd
});
@Override
- protected void onBetweenTestClasses(IClass testClass) {
+ protected void onBetweenTestClasses(List<ITestClass> testClasses) {
if (FAST_THREAD_LOCAL_CLEANUP_ENABLED &&
FastThreadLocalStateCleaner.isEnabled()) {
LOG.info("Cleaning up FastThreadLocal thread local state.");
CLEANER.cleanupAllFastThreadLocals((thread, value) -> {
diff --git
a/buildtools/src/main/java/org/apache/pulsar/tests/MockitoCleanupListener.java
b/buildtools/src/main/java/org/apache/pulsar/tests/MockitoCleanupListener.java
index b5a3dbe8e4c..ad8870a13e5 100644
---
a/buildtools/src/main/java/org/apache/pulsar/tests/MockitoCleanupListener.java
+++
b/buildtools/src/main/java/org/apache/pulsar/tests/MockitoCleanupListener.java
@@ -18,10 +18,11 @@
*/
package org.apache.pulsar.tests;
+import java.util.List;
import org.mockito.Mockito;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import org.testng.IClass;
+import org.testng.ITestClass;
/**
* Cleanup Mockito's Thread Local state that leaks memory
@@ -40,7 +41,7 @@ public class MockitoCleanupListener extends
BetweenTestClassesListenerAdapter {
"Cleaning up Mockito's
ThreadSafeMockingProgress.MOCKING_PROGRESS_PROVIDER thread local state.";
@Override
- protected void onBetweenTestClasses(IClass testClass) {
+ protected void onBetweenTestClasses(List<ITestClass> testClasses) {
if (MOCKITO_CLEANUP_ENABLED) {
try {
if (MockitoThreadLocalStateCleaner.INSTANCE.isEnabled()) {
diff --git
a/buildtools/src/main/java/org/apache/pulsar/tests/SingletonCleanerListener.java
b/buildtools/src/main/java/org/apache/pulsar/tests/SingletonCleanerListener.java
index 8db6c095566..2260ad090d4 100644
---
a/buildtools/src/main/java/org/apache/pulsar/tests/SingletonCleanerListener.java
+++
b/buildtools/src/main/java/org/apache/pulsar/tests/SingletonCleanerListener.java
@@ -21,10 +21,11 @@ package org.apache.pulsar.tests;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
+import java.util.List;
import org.apache.commons.lang3.ClassUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import org.testng.IClass;
+import org.testng.ITestClass;
/**
* This TestNG listener contains cleanup for some singletons or caches.
@@ -77,7 +78,7 @@ public class SingletonCleanerListener extends
BetweenTestClassesListenerAdapter
}
@Override
- protected void onBetweenTestClasses(IClass testClass) {
+ protected void onBetweenTestClasses(List<ITestClass> testClasses) {
objectMapperFactoryClearCaches();
jsonSchemaClearCaches();
}
diff --git
a/buildtools/src/main/java/org/apache/pulsar/tests/ThreadLeakDetectorListener.java
b/buildtools/src/main/java/org/apache/pulsar/tests/ThreadLeakDetectorListener.java
index 6ab2e3440cb..d28eabf238b 100644
---
a/buildtools/src/main/java/org/apache/pulsar/tests/ThreadLeakDetectorListener.java
+++
b/buildtools/src/main/java/org/apache/pulsar/tests/ThreadLeakDetectorListener.java
@@ -16,68 +16,280 @@
* specific language governing permissions and limitations
* under the License.
*/
+
package org.apache.pulsar.tests;
+import com.google.common.base.Charsets;
+import com.google.common.io.Files;
+import java.io.File;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.lang.reflect.Field;
+import java.time.ZonedDateTime;
+import java.time.format.DateTimeFormatter;
import java.util.Collections;
import java.util.LinkedHashSet;
+import java.util.List;
import java.util.Objects;
import java.util.Set;
+import java.util.concurrent.ForkJoinWorkerThread;
import java.util.stream.Collectors;
import org.apache.commons.lang3.ThreadUtils;
+import org.apache.commons.lang3.mutable.MutableBoolean;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import org.testng.IClass;
import org.testng.ISuite;
import org.testng.ISuiteListener;
+import org.testng.ITestClass;
/**
- * Detects new threads that have been created during the test execution.
+ * Detects new threads that have been created during the test execution. This
is useful to detect thread leaks.
+ * Will create files to the configured directory if new threads are detected
and THREAD_LEAK_DETECTOR_WAIT_MILLIS
+ * is set to a positive value. A recommended value is 10000 for
THREAD_LEAK_DETECTOR_WAIT_MILLIS. This will ensure
+ * that any asynchronous operations should have completed before the detector
determines that it has found a leak.
*/
public class ThreadLeakDetectorListener extends
BetweenTestClassesListenerAdapter implements ISuiteListener {
private static final Logger LOG =
LoggerFactory.getLogger(ThreadLeakDetectorListener.class);
+ private static final long WAIT_FOR_THREAD_TERMINATION_MILLIS =
+
Long.parseLong(System.getenv().getOrDefault("THREAD_LEAK_DETECTOR_WAIT_MILLIS",
"0"));
+ private static final File DUMP_DIR =
+ new File(System.getenv().getOrDefault("THREAD_LEAK_DETECTOR_DIR",
"target/thread-leak-dumps"));
+ private static final long THREAD_TERMINATION_POLL_INTERVAL =
+
Long.parseLong(System.getenv().getOrDefault("THREAD_LEAK_DETECTOR_POLL_INTERVAL",
"250"));
+ private static final boolean COLLECT_THREADDUMP =
+
Boolean.parseBoolean(System.getenv().getOrDefault("THREAD_LEAK_DETECTOR_COLLECT_THREADDUMP",
"true"));
private Set<ThreadKey> capturedThreadKeys;
+ private static final Field THREAD_TARGET_FIELD;
+ static {
+ Field targetField = null;
+ try {
+ targetField = Thread.class.getDeclaredField("target");
+ targetField.setAccessible(true);
+ } catch (NoSuchFieldException e) {
+ // ignore this error. on Java 21, the field is not present
+ // TODO: add support for extracting the Runnable target on Java 21
+ }
+ THREAD_TARGET_FIELD = targetField;
+ }
+
@Override
public void onStart(ISuite suite) {
// capture the initial set of threads
- detectLeakedThreads(null);
+ detectLeakedThreads(Collections.emptyList());
}
@Override
- protected void onBetweenTestClasses(IClass testClass) {
- detectLeakedThreads(testClass.getRealClass());
+ protected void onBetweenTestClasses(List<ITestClass> testClasses) {
+ detectLeakedThreads(testClasses);
+ }
+
+ private static String joinTestClassNames(List<ITestClass> testClasses) {
+ return testClasses.stream()
+ .map(ITestClass::getRealClass)
+ .map(Class::getName)
+ .collect(Collectors.joining(", "));
+ }
+
+ private static String joinSimpleTestClassNames(List<ITestClass>
testClasses) {
+ return testClasses.stream()
+ .map(ITestClass::getRealClass)
+ .map(Class::getSimpleName)
+ .collect(Collectors.joining(", "));
+ }
+
+ private static String firstTestClassName(List<ITestClass> testClasses) {
+ return testClasses.stream()
+ .findFirst()
+ .orElseThrow()
+ .getRealClass().getName();
}
- private void detectLeakedThreads(Class<?> endedTestClass) {
+ private void detectLeakedThreads(List<ITestClass> testClasses) {
LOG.info("Capturing identifiers of running threads.");
- capturedThreadKeys = compareThreads(capturedThreadKeys,
endedTestClass);
+ MutableBoolean differenceDetected = new MutableBoolean();
+ Set<ThreadKey> currentThreadKeys =
+ compareThreads(capturedThreadKeys, testClasses,
WAIT_FOR_THREAD_TERMINATION_MILLIS <= 0,
+ differenceDetected, null);
+ if (WAIT_FOR_THREAD_TERMINATION_MILLIS > 0 && !testClasses.isEmpty()
&& differenceDetected.booleanValue()) {
+ LOG.info("Difference detected in active threads. Waiting up to {}
ms for threads to terminate.",
+ WAIT_FOR_THREAD_TERMINATION_MILLIS);
+ long endTime = System.currentTimeMillis() +
WAIT_FOR_THREAD_TERMINATION_MILLIS;
+ while (System.currentTimeMillis() < endTime) {
+ try {
+ Thread.sleep(THREAD_TERMINATION_POLL_INTERVAL);
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ }
+ differenceDetected.setFalse();
+ currentThreadKeys = compareThreads(capturedThreadKeys,
testClasses, false, differenceDetected, null);
+ if (!differenceDetected.booleanValue()) {
+ break;
+ }
+ }
+ if (differenceDetected.booleanValue()) {
+ String datetimePart =
+
DateTimeFormatter.ofPattern("yyyyMMdd-HHmmss.SSS").format(ZonedDateTime.now());
+ PrintWriter out = null;
+ String firstTestClassName = firstTestClassName(testClasses);
+ try {
+ if (!DUMP_DIR.exists()) {
+ DUMP_DIR.mkdirs();
+ }
+ File threadleakdumpFile =
+ new File(DUMP_DIR, "threadleak" + datetimePart +
firstTestClassName + ".txt");
+ out = new PrintWriter(threadleakdumpFile);
+ } catch (IOException e) {
+ LOG.error("Cannot write thread leak dump", e);
+ }
+ currentThreadKeys = compareThreads(capturedThreadKeys,
testClasses, true, null, out);
+ if (out != null) {
+ out.close();
+ }
+ if (COLLECT_THREADDUMP) {
+ File threaddumpFile =
+ new File(DUMP_DIR, "threaddump" + datetimePart +
firstTestClassName + ".txt");
+ try {
+ Files.asCharSink(threaddumpFile, Charsets.UTF_8)
+
.write(ThreadDumpUtil.buildThreadDiagnosticString());
+ } catch (IOException e) {
+ LOG.error("Cannot write thread dump", e);
+ }
+ }
+ }
+ }
+ capturedThreadKeys = currentThreadKeys;
}
- private static Set<ThreadKey> compareThreads(Set<ThreadKey>
previousThreadKeys, Class<?> endedTestClass) {
+ private static Set<ThreadKey> compareThreads(Set<ThreadKey>
previousThreadKeys, List<ITestClass> testClasses,
+ boolean logDifference,
MutableBoolean differenceDetected,
+ PrintWriter out) {
Set<ThreadKey> threadKeys =
Collections.unmodifiableSet(ThreadUtils.getAllThreads().stream()
+ .filter(thread -> !shouldSkipThread(thread))
.map(ThreadKey::of)
.collect(Collectors.<ThreadKey,
Set<ThreadKey>>toCollection(LinkedHashSet::new)));
- if (endedTestClass != null && previousThreadKeys != null) {
+ if (!testClasses.isEmpty() && previousThreadKeys != null) {
int newThreadsCounter = 0;
- LOG.info("Checking for new threads created by {}.",
endedTestClass.getName());
for (ThreadKey threadKey : threadKeys) {
if (!previousThreadKeys.contains(threadKey)) {
newThreadsCounter++;
- LOG.warn("Tests in class {} created thread id {} with name
'{}'", endedTestClass.getSimpleName(),
- threadKey.getThreadId(),
threadKey.getThreadName());
+ if (differenceDetected != null) {
+ differenceDetected.setTrue();
+ }
+ if (logDifference || out != null) {
+ String message = String.format("Tests in class %s
created thread id %d with name '%s'",
+ joinSimpleTestClassNames(testClasses),
+ threadKey.getThreadId(),
threadKey.getThreadName());
+ if (logDifference) {
+ LOG.warn(message);
+ }
+ if (out != null) {
+ out.println(message);
+ }
+ }
}
}
- if (newThreadsCounter > 0) {
- LOG.warn("Summary: Tests in class {} created {} new threads",
endedTestClass.getName(),
- newThreadsCounter);
+ if (newThreadsCounter > 0 && (logDifference || out != null)) {
+ String message = String.format(
+ "Summary: Tests in class %s created %d new threads.
There are now %d threads in total.",
+ joinTestClassNames(testClasses), newThreadsCounter,
threadKeys.size());
+ if (logDifference) {
+ LOG.warn(message);
+ }
+ if (out != null) {
+ out.println(message);
+ }
}
}
return threadKeys;
}
+ private static boolean shouldSkipThread(Thread thread) {
+ // skip ForkJoinPool threads
+ if (thread instanceof ForkJoinWorkerThread) {
+ return true;
+ }
+ // skip Testcontainers threads
+ final ThreadGroup threadGroup = thread.getThreadGroup();
+ if (threadGroup != null &&
"testcontainers".equals(threadGroup.getName())) {
+ return true;
+ }
+ String threadName = thread.getName();
+ if (threadName != null) {
+ // skip ClientTestFixtures.SCHEDULER threads
+ if (threadName.startsWith("ClientTestFixtures-SCHEDULER-")) {
+ return true;
+ }
+ // skip JVM internal threads related to java.lang.Process
+ if (threadName.equals("process reaper")) {
+ return true;
+ }
+ // skip JVM internal thread related to agent attach
+ if (threadName.equals("Attach Listener")) {
+ return true;
+ }
+ // skip JVM internal thread used for
CompletableFuture.delayedExecutor
+ if (threadName.equals("CompletableFutureDelayScheduler")) {
+ return true;
+ }
+ // skip threadpool created in
dev.failsafe.internal.util.DelegatingScheduler
+ if (threadName.equals("FailsafeDelayScheduler")) {
+ return true;
+ }
+ // skip Okio Watchdog thread and interrupt it
+ if (threadName.equals("Okio Watchdog")) {
+ return true;
+ }
+ // skip OkHttp TaskRunner thread
+ if (threadName.equals("OkHttp TaskRunner")) {
+ return true;
+ }
+ // skip JNA background thread
+ if (threadName.equals("JNA Cleaner")) {
+ return true;
+ }
+ // skip org.glassfish.grizzly.http.server.DefaultSessionManager
thread pool
+ if (threadName.equals("Grizzly-HttpSession-Expirer")) {
+ return true;
+ }
+ // Testcontainers AbstractWaitStrategy.EXECUTOR
+ if (threadName.startsWith("testcontainers-wait-")) {
+ return true;
+ }
+ // org.rnorth.ducttape.timeouts.Timeouts.EXECUTOR_SERVICE thread
pool, used by Testcontainers
+ if (threadName.startsWith("ducttape-")) {
+ return true;
+ }
+ }
+ Runnable target = extractRunnableTarget(thread);
+ if (target != null) {
+ String targetClassName = target.getClass().getName();
+ // ignore threads that contain a Runnable class under
org.testcontainers package
+ if (targetClassName.startsWith("org.testcontainers.")) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ // use reflection to extract the Runnable target from a thread so that we
can detect threads created by
+ // Testcontainers based on the Runnable's class name.
+ private static Runnable extractRunnableTarget(Thread thread) {
+ if (THREAD_TARGET_FIELD == null) {
+ return null;
+ }
+ Runnable target = null;
+ try {
+ target = (Runnable) THREAD_TARGET_FIELD.get(thread);
+ } catch (IllegalAccessException e) {
+ LOG.warn("Cannot access target field in Thread.class", e);
+ }
+ return target;
+ }
+
/**
* Unique key for a thread
* Based on thread id and it's identity hash code
diff --git
a/buildtools/src/test/java/org/apache/pulsar/tests/BetweenTestClassesListenerAdapterTest.java
b/buildtools/src/test/java/org/apache/pulsar/tests/BetweenTestClassesListenerAdapterTest.java
index c7467b206a5..c25f751d741 100644
---
a/buildtools/src/test/java/org/apache/pulsar/tests/BetweenTestClassesListenerAdapterTest.java
+++
b/buildtools/src/test/java/org/apache/pulsar/tests/BetweenTestClassesListenerAdapterTest.java
@@ -27,6 +27,7 @@ import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;
import org.testng.Assert;
import org.testng.IClass;
+import org.testng.ITestClass;
import org.testng.ITestListener;
import org.testng.ITestResult;
import org.testng.TestNG;
@@ -143,14 +144,14 @@ public class BetweenTestClassesListenerAdapterTest {
XmlSuite suite = new XmlSuite();
suite.setName("Programmatic Suite");
- XmlTest test = new XmlTest(suite);
- test.setName("Programmatic Test");
-
- List<XmlClass> xmlClasses = new ArrayList<>();
for (Class<?> cls : testClasses) {
+ // create a new XmlTest for each class so that this simulates the
behavior of maven-surefire-plugin
+ XmlTest test = new XmlTest(suite);
+ test.setName("Programmatic Test for " + cls.getName());
+ List<XmlClass> xmlClasses = new ArrayList<>();
xmlClasses.add(new XmlClass(cls));
+ test.setXmlClasses(xmlClasses);
}
- test.setXmlClasses(xmlClasses);
List<XmlSuite> suites = new ArrayList<>();
suites.add(suite);
@@ -174,16 +175,18 @@ public class BetweenTestClassesListenerAdapterTest {
// Test implementation of the abstract listener
private class TestBetweenTestClassesListener extends
BetweenTestClassesListenerAdapter {
- private final List<IClass> classesCalled = new ArrayList<>();
+ private final List<ITestClass> classesCalled = new ArrayList<>();
@Override
- protected void onBetweenTestClasses(IClass testClass) {
- System.out.println("onBetweenTestClasses " + testClass.getName());
+ protected void onBetweenTestClasses(List<ITestClass> testClasses) {
+ assertEquals(testClasses.size(), 1);
+ ITestClass testClass = testClasses.get(0);
+ System.out.println("onBetweenTestClasses " + testClass);
classesCalled.add(testClass);
closeTestInstance(testClass);
}
- private void closeTestInstance(IClass testClass) {
+ private void closeTestInstance(ITestClass testClass) {
Arrays.stream(testClass.getInstances(false))
.map(instance -> instance instanceof IParameterInfo
? ((IParameterInfo) instance).getInstance() :
instance)
@@ -198,7 +201,7 @@ public class BetweenTestClassesListenerAdapterTest {
});
}
- public List<IClass> getClassesCalled() {
+ public List<ITestClass> getClassesCalled() {
return classesCalled;
}