Skip to content

Commit ee22dc0

Browse files
authored
Merge pull request #3072 from krmahadevan/feature/2916
Support ordering of listeners
2 parents 998e17b + a091f0d commit ee22dc0

38 files changed

+1660
-75
lines changed

CHANGES.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
Current (7.10.0)
2-
Fixed: GITHUB-3033: Moved ant support under own repository https://github.com/testng-team/testng-ant
2+
New: GITHUB-2916: Allow users to define ordering for TestNG listeners (Krishnan Mahadevan)
3+
Fixed: GITHUB-3033: Moved ant support under own repository https://github.com/testng-team/testng-ant (Julien Herr)
34
Fixed: GITHUB-3064: TestResult lost if failure creating RetryAnalyzer (Krishnan Mahadevan)
45
Fixed: GITHUB-3048: ConcurrentModificationException when injecting values (Krishnan Mahadevan)
56
Fixed: GITHUB-3050: Race condition when creating Guice Modules (Krishnan Mahadevan)

testng-core/src/main/java/org/testng/CommandLineArgs.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,14 @@ public class CommandLineArgs {
5454
+ " implementing ITestListener or ISuiteListener")
5555
public String listener;
5656

57+
public static final String LISTENER_COMPARATOR = "-listenercomparator";
58+
59+
@Parameter(
60+
names = LISTENER_COMPARATOR,
61+
description =
62+
"An implementation of ListenerComparator that will be used by TestNG to determine order of execution for listeners")
63+
public String listenerComparator;
64+
5765
public static final String METHOD_SELECTORS = "-methodselectors";
5866

5967
@Parameter(

testng-core/src/main/java/org/testng/DataProviderHolder.java

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
package org.testng;
22

3+
import static org.testng.ListenerComparator.*;
4+
35
import java.util.Collection;
4-
import java.util.Collections;
56
import java.util.Map;
7+
import java.util.Objects;
68
import org.testng.collections.Maps;
79
import org.testng.collections.Sets;
10+
import org.testng.internal.IConfiguration;
811

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

1518
private final Map<Class<?>, IDataProviderListener> listeners = Maps.newConcurrentMap();
1619
private final Collection<IDataProviderInterceptor> interceptors = Sets.newHashSet();
20+
private final ListenerComparator listenerComparator;
21+
22+
public DataProviderHolder(IConfiguration configuration) {
23+
this.listenerComparator = Objects.requireNonNull(configuration).getListenerComparator();
24+
}
1725

1826
public Collection<IDataProviderListener> getListeners() {
19-
return Collections.unmodifiableCollection(listeners.values());
27+
return sort(listeners.values(), listenerComparator);
2028
}
2129

2230
public Collection<IDataProviderInterceptor> getInterceptors() {
23-
return Collections.unmodifiableCollection(interceptors);
31+
return sort(interceptors, listenerComparator);
2432
}
2533

2634
public void addListeners(Collection<IDataProviderListener> listeners) {
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package org.testng;
2+
3+
import java.util.Collection;
4+
import java.util.Collections;
5+
import java.util.Comparator;
6+
import java.util.List;
7+
import org.testng.collections.Lists;
8+
9+
/**
10+
* Listener interface that can be used to determine listener execution order. This interface will
11+
* NOT be used to determine execution order for {@link IReporter} implementations.
12+
*
13+
* <p>An implementation can be plugged into TestNG either via:
14+
*
15+
* <ol>
16+
* <li>{@link TestNG#setListenerComparator(ListenerComparator)} if you are using the {@link
17+
* TestNG} APIs.
18+
* <li>Via the configuration parameter <code>-listenercomparator</code> if you are using a build
19+
* tool
20+
* </ol>
21+
*/
22+
@FunctionalInterface
23+
public interface ListenerComparator extends Comparator<ITestNGListener> {
24+
static <T extends ITestNGListener> List<T> sort(List<T> list, ListenerComparator comparator) {
25+
if (comparator == null) {
26+
return Collections.unmodifiableList(list);
27+
}
28+
List<T> original = Lists.newArrayList(list);
29+
original.sort(comparator);
30+
return Collections.unmodifiableList(original);
31+
}
32+
33+
static <T extends ITestNGListener> Collection<T> sort(
34+
Collection<T> list, ListenerComparator comparator) {
35+
if (comparator == null) {
36+
return Collections.unmodifiableCollection(list);
37+
}
38+
List<T> original = Lists.newArrayList(list);
39+
original.sort(comparator);
40+
return Collections.unmodifiableCollection(original);
41+
}
42+
}

testng-core/src/main/java/org/testng/SuiteRunner.java

Lines changed: 22 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -41,14 +41,14 @@ public class SuiteRunner implements ISuite, ISuiteRunnerListener {
4141
Maps.newLinkedHashMap();
4242

4343
private String outputDir;
44-
private XmlSuite xmlSuite;
44+
private final XmlSuite xmlSuite;
4545
private Injector parentInjector;
4646

4747
private final List<ITestListener> testListeners = Lists.newArrayList();
4848
private final Map<Class<? extends IClassListener>, IClassListener> classListeners =
4949
Maps.newLinkedHashMap();
50-
private ITestRunnerFactory tmpRunnerFactory;
51-
private final DataProviderHolder holder = new DataProviderHolder();
50+
private final ITestRunnerFactory tmpRunnerFactory;
51+
private final DataProviderHolder holder;
5252

5353
private boolean useDefaultListeners = true;
5454

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

5858
// The configuration
5959
// Note: adjust test.multiplelisteners.SimpleReporter#generateReport test if renaming the field
60-
private IConfiguration configuration;
60+
private final IConfiguration configuration;
6161

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

66-
private Map<Class<? extends IInvokedMethodListener>, IInvokedMethodListener>
66+
private final Map<Class<? extends IInvokedMethodListener>, IInvokedMethodListener>
6767
invokedMethodListeners;
6868

6969
private final SuiteRunState suiteState = new SuiteRunState();
7070
private final IAttributes attributes = new Attributes();
7171
private final Set<IExecutionVisualiser> visualisers = Sets.newHashSet();
72-
private ITestListener exitCodeListener;
72+
private final ITestListener exitCodeListener;
7373

7474
public SuiteRunner(
7575
IConfiguration configuration,
@@ -97,37 +97,11 @@ public SuiteRunner(
9797
null /* invoked method listeners */,
9898
new TestListenersContainer() /* test listeners */,
9999
null /* class listeners */,
100-
new DataProviderHolder(),
100+
new DataProviderHolder(configuration),
101101
comparator);
102102
}
103103

104104
protected SuiteRunner(
105-
IConfiguration configuration,
106-
XmlSuite suite,
107-
String outputDir,
108-
ITestRunnerFactory runnerFactory,
109-
boolean useDefaultListeners,
110-
List<IMethodInterceptor> methodInterceptors,
111-
Collection<IInvokedMethodListener> invokedMethodListeners,
112-
TestListenersContainer container,
113-
Collection<IClassListener> classListeners,
114-
DataProviderHolder holder,
115-
Comparator<ITestNGMethod> comparator) {
116-
init(
117-
configuration,
118-
suite,
119-
outputDir,
120-
runnerFactory,
121-
useDefaultListeners,
122-
methodInterceptors,
123-
invokedMethodListeners,
124-
container,
125-
classListeners,
126-
holder,
127-
comparator);
128-
}
129-
130-
private void init(
131105
IConfiguration configuration,
132106
XmlSuite suite,
133107
String outputDir,
@@ -137,12 +111,12 @@ private void init(
137111
Collection<IInvokedMethodListener> invokedMethodListener,
138112
TestListenersContainer container,
139113
Collection<IClassListener> classListeners,
140-
DataProviderHolder attribs,
114+
DataProviderHolder holder,
141115
Comparator<ITestNGMethod> comparator) {
142116
if (comparator == null) {
143117
throw new IllegalArgumentException("comparator must not be null");
144118
}
145-
this.holder.merge(attribs);
119+
this.holder = holder;
146120
this.configuration = configuration;
147121
this.xmlSuite = suite;
148122
this.useDefaultListeners = useDefaultListeners;
@@ -264,12 +238,14 @@ public ITestListener getExitCodeListener() {
264238
private void invokeListeners(boolean start) {
265239
if (start) {
266240
for (ISuiteListener sl :
267-
ListenerOrderDeterminer.order(Lists.newArrayList(listeners.values()))) {
241+
ListenerOrderDeterminer.order(
242+
listeners.values(), this.configuration.getListenerComparator())) {
268243
sl.onStart(this);
269244
}
270245
} else {
271246
List<ISuiteListener> suiteListenersReversed =
272-
ListenerOrderDeterminer.reversedOrder(listeners.values());
247+
ListenerOrderDeterminer.reversedOrder(
248+
listeners.values(), this.configuration.getListenerComparator());
273249
for (ISuiteListener sl : suiteListenersReversed) {
274250
sl.onFinish(this);
275251
}
@@ -298,7 +274,8 @@ private ITestRunnerFactory buildRunnerFactory(Comparator<ITestNGMethod> comparat
298274
this);
299275
} else {
300276
factory =
301-
new ProxyTestRunnerFactory(testListeners.toArray(new ITestListener[0]), tmpRunnerFactory);
277+
new ProxyTestRunnerFactory(
278+
testListeners.toArray(new ITestListener[0]), tmpRunnerFactory, configuration);
302279
}
303280

304281
return factory;
@@ -627,7 +604,7 @@ public TestRunner newTestRunner(
627604
Collection<IInvokedMethodListener> listeners,
628605
List<IClassListener> classListeners,
629606
Map<Class<? extends IDataProviderListener>, IDataProviderListener> dataProviderListeners) {
630-
DataProviderHolder holder = new DataProviderHolder();
607+
DataProviderHolder holder = new DataProviderHolder(this.configuration);
631608
holder.addListeners(dataProviderListeners.values());
632609
return newTestRunner(suite, test, listeners, classListeners, holder);
633610
}
@@ -684,9 +661,13 @@ private static class ProxyTestRunnerFactory implements ITestRunnerFactory {
684661
private final ITestListener[] failureGenerators;
685662
private final ITestRunnerFactory target;
686663

687-
public ProxyTestRunnerFactory(ITestListener[] failureListeners, ITestRunnerFactory target) {
664+
private final IConfiguration configuration;
665+
666+
public ProxyTestRunnerFactory(
667+
ITestListener[] failureListeners, ITestRunnerFactory target, IConfiguration configuration) {
688668
failureGenerators = failureListeners;
689669
this.target = target;
670+
this.configuration = configuration;
690671
}
691672

692673
@Override
@@ -705,7 +686,7 @@ public TestRunner newTestRunner(
705686
Collection<IInvokedMethodListener> listeners,
706687
List<IClassListener> classListeners,
707688
Map<Class<? extends IDataProviderListener>, IDataProviderListener> dataProviderListeners) {
708-
DataProviderHolder holder = new DataProviderHolder();
689+
DataProviderHolder holder = new DataProviderHolder(configuration);
709690
holder.addListeners(dataProviderListeners.values());
710691
return newTestRunner(suite, test, listeners, classListeners, holder);
711692
}

testng-core/src/main/java/org/testng/TestNG.java

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package org.testng;
22

3+
import static org.testng.ListenerComparator.sort;
34
import static org.testng.internal.Utils.defaultIfStringEmpty;
45
import static org.testng.internal.Utils.isStringEmpty;
56
import static org.testng.internal.Utils.isStringNotEmpty;
@@ -273,6 +274,14 @@ public void setUseDefaultListeners(boolean useDefaultListeners) {
273274
m_useDefaultListeners = useDefaultListeners;
274275
}
275276

277+
public void setListenerComparator(ListenerComparator listenerComparator) {
278+
this.m_configuration.setListenerComparator(listenerComparator);
279+
}
280+
281+
public ListenerComparator getListenerComparator() {
282+
return m_configuration.getListenerComparator();
283+
}
284+
276285
/**
277286
* Sets a jar containing a testng.xml file.
278287
*
@@ -1130,14 +1139,17 @@ protected List<ISuite> runSuites() {
11301139
}
11311140

11321141
private void runSuiteAlterationListeners() {
1133-
for (IAlterSuiteListener l : m_alterSuiteListeners.values()) {
1142+
Collection<IAlterSuiteListener> original =
1143+
sort(m_alterSuiteListeners.values(), m_configuration.getListenerComparator());
1144+
for (IAlterSuiteListener l : original) {
11341145
l.alter(m_suites);
11351146
}
11361147
}
11371148

11381149
private void runExecutionListeners(boolean start) {
11391150
List<IExecutionListener> executionListeners =
1140-
ListenerOrderDeterminer.order(m_configuration.getExecutionListeners());
1151+
ListenerOrderDeterminer.order(
1152+
m_configuration.getExecutionListeners(), m_configuration.getListenerComparator());
11411153
if (start) {
11421154
for (IExecutionListener l : executionListeners) {
11431155
l.onExecutionStart();
@@ -1146,7 +1158,8 @@ private void runExecutionListeners(boolean start) {
11461158
exitCodeListener.onExecutionStart();
11471159
} else {
11481160
List<IExecutionListener> executionListenersReversed =
1149-
ListenerOrderDeterminer.reversedOrder(executionListeners);
1161+
ListenerOrderDeterminer.reversedOrder(
1162+
m_configuration.getExecutionListeners(), m_configuration.getListenerComparator());
11501163
for (IExecutionListener l : executionListenersReversed) {
11511164
l.onExecutionFinish();
11521165
}
@@ -1367,7 +1380,7 @@ private void createSuiteRunners(SuiteRunnerMap suiteRunnerMap /* OUT */, XmlSuit
13671380

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

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

1500+
Optional.ofNullable(cla.listenerComparator)
1501+
.map(ClassHelper::forName)
1502+
.filter(ListenerComparator.class::isAssignableFrom)
1503+
.map(it -> m_objectFactory.newInstance(it))
1504+
.map(it -> (ListenerComparator) it)
1505+
.ifPresent(this::setListenerComparator);
1506+
14871507
if (cla.verbose != null) {
14881508
setVerbose(cla.verbose);
14891509
}

0 commit comments

Comments
 (0)