Skip to content

Commit

Permalink
Merge pull request #3072 from krmahadevan/feature/2916
Browse files Browse the repository at this point in the history
Support ordering of listeners
  • Loading branch information
krmahadevan committed Feb 18, 2024
2 parents 998e17b + a091f0d commit ee22dc0
Show file tree
Hide file tree
Showing 38 changed files with 1,660 additions and 75 deletions.
3 changes: 2 additions & 1 deletion CHANGES.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
Current (7.10.0)
Fixed: GITHUB-3033: Moved ant support under own repository https://github.com/testng-team/testng-ant
New: GITHUB-2916: Allow users to define ordering for TestNG listeners (Krishnan Mahadevan)
Fixed: GITHUB-3033: Moved ant support under own repository https://github.com/testng-team/testng-ant (Julien Herr)
Fixed: GITHUB-3064: TestResult lost if failure creating RetryAnalyzer (Krishnan Mahadevan)
Fixed: GITHUB-3048: ConcurrentModificationException when injecting values (Krishnan Mahadevan)
Fixed: GITHUB-3050: Race condition when creating Guice Modules (Krishnan Mahadevan)
Expand Down
8 changes: 8 additions & 0 deletions testng-core/src/main/java/org/testng/CommandLineArgs.java
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,14 @@ public class CommandLineArgs {
+ " implementing ITestListener or ISuiteListener")
public String listener;

public static final String LISTENER_COMPARATOR = "-listenercomparator";

@Parameter(
names = LISTENER_COMPARATOR,
description =
"An implementation of ListenerComparator that will be used by TestNG to determine order of execution for listeners")
public String listenerComparator;

public static final String METHOD_SELECTORS = "-methodselectors";

@Parameter(
Expand Down
14 changes: 11 additions & 3 deletions testng-core/src/main/java/org/testng/DataProviderHolder.java
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
package org.testng;

import static org.testng.ListenerComparator.*;

import java.util.Collection;
import java.util.Collections;
import java.util.Map;
import java.util.Objects;
import org.testng.collections.Maps;
import org.testng.collections.Sets;
import org.testng.internal.IConfiguration;

/**
* A holder class that is aimed at acting as a container for holding various different aspects of a
Expand All @@ -14,13 +17,18 @@ public class DataProviderHolder {

private final Map<Class<?>, IDataProviderListener> listeners = Maps.newConcurrentMap();
private final Collection<IDataProviderInterceptor> interceptors = Sets.newHashSet();
private final ListenerComparator listenerComparator;

public DataProviderHolder(IConfiguration configuration) {
this.listenerComparator = Objects.requireNonNull(configuration).getListenerComparator();
}

public Collection<IDataProviderListener> getListeners() {
return Collections.unmodifiableCollection(listeners.values());
return sort(listeners.values(), listenerComparator);
}

public Collection<IDataProviderInterceptor> getInterceptors() {
return Collections.unmodifiableCollection(interceptors);
return sort(interceptors, listenerComparator);
}

public void addListeners(Collection<IDataProviderListener> listeners) {
Expand Down
42 changes: 42 additions & 0 deletions testng-core/src/main/java/org/testng/ListenerComparator.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package org.testng;

import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import org.testng.collections.Lists;

/**
* Listener interface that can be used to determine listener execution order. This interface will
* NOT be used to determine execution order for {@link IReporter} implementations.
*
* <p>An implementation can be plugged into TestNG either via:
*
* <ol>
* <li>{@link TestNG#setListenerComparator(ListenerComparator)} if you are using the {@link
* TestNG} APIs.
* <li>Via the configuration parameter <code>-listenercomparator</code> if you are using a build
* tool
* </ol>
*/
@FunctionalInterface
public interface ListenerComparator extends Comparator<ITestNGListener> {
static <T extends ITestNGListener> List<T> sort(List<T> list, ListenerComparator comparator) {
if (comparator == null) {
return Collections.unmodifiableList(list);
}
List<T> original = Lists.newArrayList(list);
original.sort(comparator);
return Collections.unmodifiableList(original);
}

static <T extends ITestNGListener> Collection<T> sort(
Collection<T> list, ListenerComparator comparator) {
if (comparator == null) {
return Collections.unmodifiableCollection(list);
}
List<T> original = Lists.newArrayList(list);
original.sort(comparator);
return Collections.unmodifiableCollection(original);
}
}
63 changes: 22 additions & 41 deletions testng-core/src/main/java/org/testng/SuiteRunner.java
Original file line number Diff line number Diff line change
Expand Up @@ -41,14 +41,14 @@ public class SuiteRunner implements ISuite, ISuiteRunnerListener {
Maps.newLinkedHashMap();

private String outputDir;
private XmlSuite xmlSuite;
private final XmlSuite xmlSuite;
private Injector parentInjector;

private final List<ITestListener> testListeners = Lists.newArrayList();
private final Map<Class<? extends IClassListener>, IClassListener> classListeners =
Maps.newLinkedHashMap();
private ITestRunnerFactory tmpRunnerFactory;
private final DataProviderHolder holder = new DataProviderHolder();
private final ITestRunnerFactory tmpRunnerFactory;
private final DataProviderHolder holder;

private boolean useDefaultListeners = true;

Expand All @@ -57,19 +57,19 @@ public class SuiteRunner implements ISuite, ISuiteRunnerListener {

// The configuration
// Note: adjust test.multiplelisteners.SimpleReporter#generateReport test if renaming the field
private IConfiguration configuration;
private final IConfiguration configuration;

private ITestObjectFactory objectFactory;
private Boolean skipFailedInvocationCounts = Boolean.FALSE;
private final List<IReporter> reporters = Lists.newArrayList();

private Map<Class<? extends IInvokedMethodListener>, IInvokedMethodListener>
private final Map<Class<? extends IInvokedMethodListener>, IInvokedMethodListener>
invokedMethodListeners;

private final SuiteRunState suiteState = new SuiteRunState();
private final IAttributes attributes = new Attributes();
private final Set<IExecutionVisualiser> visualisers = Sets.newHashSet();
private ITestListener exitCodeListener;
private final ITestListener exitCodeListener;

public SuiteRunner(
IConfiguration configuration,
Expand Down Expand Up @@ -97,37 +97,11 @@ public SuiteRunner(
null /* invoked method listeners */,
new TestListenersContainer() /* test listeners */,
null /* class listeners */,
new DataProviderHolder(),
new DataProviderHolder(configuration),
comparator);
}

protected SuiteRunner(
IConfiguration configuration,
XmlSuite suite,
String outputDir,
ITestRunnerFactory runnerFactory,
boolean useDefaultListeners,
List<IMethodInterceptor> methodInterceptors,
Collection<IInvokedMethodListener> invokedMethodListeners,
TestListenersContainer container,
Collection<IClassListener> classListeners,
DataProviderHolder holder,
Comparator<ITestNGMethod> comparator) {
init(
configuration,
suite,
outputDir,
runnerFactory,
useDefaultListeners,
methodInterceptors,
invokedMethodListeners,
container,
classListeners,
holder,
comparator);
}

private void init(
IConfiguration configuration,
XmlSuite suite,
String outputDir,
Expand All @@ -137,12 +111,12 @@ private void init(
Collection<IInvokedMethodListener> invokedMethodListener,
TestListenersContainer container,
Collection<IClassListener> classListeners,
DataProviderHolder attribs,
DataProviderHolder holder,
Comparator<ITestNGMethod> comparator) {
if (comparator == null) {
throw new IllegalArgumentException("comparator must not be null");
}
this.holder.merge(attribs);
this.holder = holder;
this.configuration = configuration;
this.xmlSuite = suite;
this.useDefaultListeners = useDefaultListeners;
Expand Down Expand Up @@ -264,12 +238,14 @@ public ITestListener getExitCodeListener() {
private void invokeListeners(boolean start) {
if (start) {
for (ISuiteListener sl :
ListenerOrderDeterminer.order(Lists.newArrayList(listeners.values()))) {
ListenerOrderDeterminer.order(
listeners.values(), this.configuration.getListenerComparator())) {
sl.onStart(this);
}
} else {
List<ISuiteListener> suiteListenersReversed =
ListenerOrderDeterminer.reversedOrder(listeners.values());
ListenerOrderDeterminer.reversedOrder(
listeners.values(), this.configuration.getListenerComparator());
for (ISuiteListener sl : suiteListenersReversed) {
sl.onFinish(this);
}
Expand Down Expand Up @@ -298,7 +274,8 @@ private ITestRunnerFactory buildRunnerFactory(Comparator<ITestNGMethod> comparat
this);
} else {
factory =
new ProxyTestRunnerFactory(testListeners.toArray(new ITestListener[0]), tmpRunnerFactory);
new ProxyTestRunnerFactory(
testListeners.toArray(new ITestListener[0]), tmpRunnerFactory, configuration);
}

return factory;
Expand Down Expand Up @@ -627,7 +604,7 @@ public TestRunner newTestRunner(
Collection<IInvokedMethodListener> listeners,
List<IClassListener> classListeners,
Map<Class<? extends IDataProviderListener>, IDataProviderListener> dataProviderListeners) {
DataProviderHolder holder = new DataProviderHolder();
DataProviderHolder holder = new DataProviderHolder(this.configuration);
holder.addListeners(dataProviderListeners.values());
return newTestRunner(suite, test, listeners, classListeners, holder);
}
Expand Down Expand Up @@ -684,9 +661,13 @@ private static class ProxyTestRunnerFactory implements ITestRunnerFactory {
private final ITestListener[] failureGenerators;
private final ITestRunnerFactory target;

public ProxyTestRunnerFactory(ITestListener[] failureListeners, ITestRunnerFactory target) {
private final IConfiguration configuration;

public ProxyTestRunnerFactory(
ITestListener[] failureListeners, ITestRunnerFactory target, IConfiguration configuration) {
failureGenerators = failureListeners;
this.target = target;
this.configuration = configuration;
}

@Override
Expand All @@ -705,7 +686,7 @@ public TestRunner newTestRunner(
Collection<IInvokedMethodListener> listeners,
List<IClassListener> classListeners,
Map<Class<? extends IDataProviderListener>, IDataProviderListener> dataProviderListeners) {
DataProviderHolder holder = new DataProviderHolder();
DataProviderHolder holder = new DataProviderHolder(configuration);
holder.addListeners(dataProviderListeners.values());
return newTestRunner(suite, test, listeners, classListeners, holder);
}
Expand Down
28 changes: 24 additions & 4 deletions testng-core/src/main/java/org/testng/TestNG.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package org.testng;

import static org.testng.ListenerComparator.sort;
import static org.testng.internal.Utils.defaultIfStringEmpty;
import static org.testng.internal.Utils.isStringEmpty;
import static org.testng.internal.Utils.isStringNotEmpty;
Expand Down Expand Up @@ -273,6 +274,14 @@ public void setUseDefaultListeners(boolean useDefaultListeners) {
m_useDefaultListeners = useDefaultListeners;
}

public void setListenerComparator(ListenerComparator listenerComparator) {
this.m_configuration.setListenerComparator(listenerComparator);
}

public ListenerComparator getListenerComparator() {
return m_configuration.getListenerComparator();
}

/**
* Sets a jar containing a testng.xml file.
*
Expand Down Expand Up @@ -1130,14 +1139,17 @@ protected List<ISuite> runSuites() {
}

private void runSuiteAlterationListeners() {
for (IAlterSuiteListener l : m_alterSuiteListeners.values()) {
Collection<IAlterSuiteListener> original =
sort(m_alterSuiteListeners.values(), m_configuration.getListenerComparator());
for (IAlterSuiteListener l : original) {
l.alter(m_suites);
}
}

private void runExecutionListeners(boolean start) {
List<IExecutionListener> executionListeners =
ListenerOrderDeterminer.order(m_configuration.getExecutionListeners());
ListenerOrderDeterminer.order(
m_configuration.getExecutionListeners(), m_configuration.getListenerComparator());
if (start) {
for (IExecutionListener l : executionListeners) {
l.onExecutionStart();
Expand All @@ -1146,7 +1158,8 @@ private void runExecutionListeners(boolean start) {
exitCodeListener.onExecutionStart();
} else {
List<IExecutionListener> executionListenersReversed =
ListenerOrderDeterminer.reversedOrder(executionListeners);
ListenerOrderDeterminer.reversedOrder(
m_configuration.getExecutionListeners(), m_configuration.getListenerComparator());
for (IExecutionListener l : executionListenersReversed) {
l.onExecutionFinish();
}
Expand Down Expand Up @@ -1367,7 +1380,7 @@ private void createSuiteRunners(SuiteRunnerMap suiteRunnerMap /* OUT */, XmlSuit

/** Creates a suite runner and configures its initial state */
private SuiteRunner createSuiteRunner(XmlSuite xmlSuite) {
DataProviderHolder holder = new DataProviderHolder();
DataProviderHolder holder = new DataProviderHolder(m_configuration);
holder.addListeners(m_dataProviderListeners.values());
holder.addInterceptors(m_dataProviderInterceptors.values());
TestListenersContainer container =
Expand Down Expand Up @@ -1484,6 +1497,13 @@ protected void configure(CommandLineArgs cla) {

Optional.ofNullable(cla.generateResultsPerSuite).ifPresent(this::setGenerateResultsPerSuite);

Optional.ofNullable(cla.listenerComparator)
.map(ClassHelper::forName)
.filter(ListenerComparator.class::isAssignableFrom)
.map(it -> m_objectFactory.newInstance(it))
.map(it -> (ListenerComparator) it)
.ifPresent(this::setListenerComparator);

if (cla.verbose != null) {
setVerbose(cla.verbose);
}
Expand Down

0 comments on commit ee22dc0

Please sign in to comment.