Skip to content

Commit 69dc232

Browse files
committed
Honour inheritance when parsing listener factories
Closes #3095
1 parent 554cfa6 commit 69dc232

File tree

11 files changed

+119
-44
lines changed

11 files changed

+119
-44
lines changed

CHANGES.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
Current (7.10.0)
22

3+
Fixed: GITHUB-3095: Super class annotated with ITestNGListenerFactory makes derived test class throw TestNGException on execution (Krishnan Mahadevan)
34
Fixed: GITHUB-3081: Discrepancy with combination of (Shared Thread pool and Method Interceptor) (Krishnan Mahadevan)
45
Fixed: GITHUB-2381: Controlling the inclusion of the listener at runtime (Krishnan Mahadevan)
56
Fixed: GITHUB-3082: IInvokedMethodListener Iinvoked method does not return correct instance during @BeforeMethod, @AfterMethod and @AfterClass (Krishnan Mahadevan)

testng-core/src/main/java/org/testng/internal/TestListenerHelper.java

Lines changed: 37 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
package org.testng.internal;
22

3+
import java.util.ArrayList;
4+
import java.util.Arrays;
35
import java.util.List;
6+
import java.util.Optional;
47
import org.testng.IClass;
58
import org.testng.IConfigurationListener;
69
import org.testng.ITestContext;
@@ -13,7 +16,6 @@
1316
import org.testng.ListenerComparator;
1417
import org.testng.TestNGException;
1518
import org.testng.annotations.IListenersAnnotation;
16-
import org.testng.collections.Lists;
1719
import org.testng.internal.annotations.IAnnotationFinder;
1820

1921
/** A helper class that internally houses some of the listener related actions support. */
@@ -129,38 +131,16 @@ public static void runTestListeners(ITestResult tr, List<ITestListener> listener
129131
* @return all the @Listeners annotations found in the current class and its superclasses and
130132
* inherited interfaces.
131133
*/
132-
@SuppressWarnings("unchecked")
133134
public static ListenerHolder findAllListeners(Class<?> cls, IAnnotationFinder finder) {
134135
ListenerHolder result = new ListenerHolder();
135-
result.listenerClasses = Lists.newArrayList();
136136

137137
while (cls != Object.class) {
138138
List<IListenersAnnotation> annotations =
139139
finder.findInheritedAnnotations(cls, IListenersAnnotation.class);
140-
IListenersAnnotation l = finder.findAnnotation(cls, IListenersAnnotation.class);
141-
if (l != null) {
142-
annotations.add(l);
143-
}
144-
annotations.forEach(
145-
anno -> {
146-
Class<? extends ITestNGListener>[] classes = anno.getValue();
147-
for (Class<? extends ITestNGListener> c : classes) {
148-
result.listenerClasses.add(c);
149-
150-
if (ITestNGListenerFactory.class.isAssignableFrom(c)) {
151-
if (result.listenerFactoryClass == null) {
152-
result.listenerFactoryClass = (Class<? extends ITestNGListenerFactory>) c;
153-
} else {
154-
throw new TestNGException(
155-
"Found more than one class implementing "
156-
+ "ITestNGListenerFactory:"
157-
+ c
158-
+ " and "
159-
+ result.listenerFactoryClass);
160-
}
161-
}
162-
}
163-
});
140+
Optional.ofNullable(finder.findAnnotation(cls, IListenersAnnotation.class))
141+
.ifPresent(annotations::add);
142+
143+
annotations.stream().flatMap(it -> Arrays.stream(it.getValue())).forEach(result::addListener);
164144
cls = cls.getSuperclass();
165145
}
166146
return result;
@@ -196,9 +176,38 @@ public static ITestNGListenerFactory createListenerFactory(
196176
}
197177

198178
public static class ListenerHolder {
199-
private List<Class<? extends ITestNGListener>> listenerClasses;
179+
private final List<Class<? extends ITestNGListener>> listenerClasses = new ArrayList<>();
200180
private Class<? extends ITestNGListenerFactory> listenerFactoryClass;
201181

182+
@SuppressWarnings("unchecked")
183+
public void addListener(Class<? extends ITestNGListener> c) {
184+
// @Listener annotation is now inheritable. So let's add it ONLY
185+
// if it wasn't added already
186+
if (!listenerClasses.contains(c)) {
187+
listenerClasses.add(c);
188+
}
189+
if (ITestNGListenerFactory.class.isAssignableFrom(c)) {
190+
setListenerFactoryClass((Class<? extends ITestNGListenerFactory>) c);
191+
}
192+
}
193+
194+
private void setListenerFactoryClass(Class<? extends ITestNGListenerFactory> c) {
195+
if (c.equals(listenerFactoryClass)) {
196+
return;
197+
}
198+
if (listenerFactoryClass != null) {
199+
// Let's say we already know of a ListenerFactoryClass called `MyFactory`.
200+
// Now we are stumbling into another ListenerFactoryClass called `YourFactory`
201+
// We need to throw an exception, because we can ONLY deal with 1 listener factory.
202+
throw new TestNGException(
203+
"Found more than one class implementing ITestNGListenerFactory:"
204+
+ c
205+
+ " and "
206+
+ listenerFactoryClass);
207+
}
208+
listenerFactoryClass = c;
209+
}
210+
202211
public List<Class<? extends ITestNGListener>> getListenerClasses() {
203212
return listenerClasses;
204213
}

testng-core/src/test/java/org/testng/internal/TestListenerHelperTest.java

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,11 @@
1414
import org.testng.internal.annotations.DefaultAnnotationTransformer;
1515
import org.testng.internal.annotations.IAnnotationFinder;
1616
import org.testng.internal.annotations.JDK15AnnotationFinder;
17-
import org.testng.internal.listeners.DummyListenerFactory;
17+
import org.testng.internal.listeners.ListenerFactoryContainer;
18+
import org.testng.internal.listeners.TestClassContainer;
1819
import org.testng.internal.listeners.TestClassDoublingupAsListenerFactory;
1920
import org.testng.internal.listeners.TestClassWithCompositeListener;
2021
import org.testng.internal.listeners.TestClassWithListener;
21-
import org.testng.internal.listeners.TestClassWithMultipleListenerFactories;
2222
import org.testng.internal.paramhandler.FakeTestContext;
2323
import org.testng.xml.XmlClass;
2424
import test.SimpleBaseTest;
@@ -45,13 +45,25 @@ public Object[][] getTestData() {
4545
};
4646
}
4747

48+
@Test(description = "GITHUB-3095")
49+
public void testFindAllListenersDuplicateListenerFactories() {
50+
TestListenerHelper.ListenerHolder result =
51+
TestListenerHelper.findAllListeners(
52+
TestClassContainer.TestClassWithDuplicateListenerFactories.class, finder);
53+
assertThat(result.getListenerFactoryClass()).isNotNull();
54+
}
55+
4856
@Test(
57+
description = "GITHUB-3095",
4958
expectedExceptions = TestNGException.class,
5059
expectedExceptionsMessageRegExp =
5160
"\nFound more than one class implementing ITestNGListenerFactory:class "
52-
+ "org.testng.internal.listeners.DummyListenerFactory and class org.testng.internal.listeners.DummyListenerFactory")
61+
+ "org.testng.internal.listeners.ListenerFactoryContainer\\$DummyListenerFactory2 "
62+
+ "and class org.testng.internal.listeners"
63+
+ ".ListenerFactoryContainer\\$DummyListenerFactory")
5364
public void testFindAllListenersErrorCondition() {
54-
TestListenerHelper.findAllListeners(TestClassWithMultipleListenerFactories.class, finder);
65+
TestListenerHelper.findAllListeners(
66+
TestClassContainer.TestClassWithMultipleUniqueListenerFactories.class, finder);
5567
}
5668

5769
@Test(dataProvider = "getFactoryTestData")
@@ -73,7 +85,7 @@ public void testCreateListenerFactory(
7385
@DataProvider(name = "getFactoryTestData")
7486
public Object[][] getFactoryTestData() {
7587
return new Object[][] {
76-
{TestClassWithListener.class, DummyListenerFactory.class},
88+
{TestClassWithListener.class, ListenerFactoryContainer.DummyListenerFactory.class},
7789
{TestClassDoublingupAsListenerFactory.class, TestClassDoublingupAsListenerFactory.class}
7890
};
7991
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package org.testng.internal.listeners;
2+
3+
import org.testng.IExecutionListener;
4+
import org.testng.ITestNGListener;
5+
import org.testng.ITestNGListenerFactory;
6+
7+
public class ListenerFactoryContainer {
8+
public static class DummyListenerFactory implements ITestNGListenerFactory, IExecutionListener {
9+
@Override
10+
public ITestNGListener createListener(Class<? extends ITestNGListener> listenerClass) {
11+
return this;
12+
}
13+
}
14+
15+
public static class DummyListenerFactory2 implements ITestNGListenerFactory, IExecutionListener {
16+
@Override
17+
public ITestNGListener createListener(Class<? extends ITestNGListener> listenerClass) {
18+
return this;
19+
}
20+
}
21+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package org.testng.internal.listeners;
2+
3+
import org.testng.annotations.Listeners;
4+
5+
public class TestClassContainer {
6+
@Listeners({
7+
ListenerFactoryContainer.DummyListenerFactory.class,
8+
ListenerFactoryContainer.DummyListenerFactory.class
9+
})
10+
public static class TestClassWithDuplicateListenerFactories {}
11+
12+
@Listeners({
13+
ListenerFactoryContainer.DummyListenerFactory.class,
14+
ListenerFactoryContainer.DummyListenerFactory2.class
15+
})
16+
public static class TestClassWithMultipleUniqueListenerFactories {}
17+
}

testng-core/src/test/java/org/testng/internal/listeners/TestClassWithCompositeListener.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import org.testng.annotations.Listeners;
44
import org.testng.annotations.Test;
55

6-
@Listeners(DummyListenerFactory.class)
6+
@Listeners(ListenerFactoryContainer.DummyListenerFactory.class)
77
public class TestClassWithCompositeListener {
88
@Test
99
public void testMethod() {}

testng-core/src/test/java/org/testng/internal/listeners/TestClassWithMultipleListenerFactories.java

Lines changed: 0 additions & 6 deletions
This file was deleted.

testng-core/src/test/java/test/listeners/ListenersTest.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@
5454
import test.listeners.issue3064.SampleTestCase;
5555
import test.listeners.issue3082.ObjectRepository;
5656
import test.listeners.issue3082.ObjectTrackingMethodListener;
57+
import test.listeners.issue3095.ChildClassSample;
5758

5859
public class ListenersTest extends SimpleBaseTest {
5960
public static final String[] github2638ExpectedList =
@@ -588,6 +589,13 @@ public void ensureListenerWorksWithCorrectTestClassInstance() {
588589
softly.assertAll();
589590
}
590591

592+
@Test(description = "GITHUB-3095")
593+
public void ensureInheritanceIsHandledWhenDealingWithListeners() {
594+
TestNG testng = create(ChildClassSample.class);
595+
testng.run();
596+
assertThat(testng.getStatus()).isZero();
597+
}
598+
591599
private void setupTest(boolean addExplicitListener) {
592600
TestNG testng = new TestNG();
593601
XmlSuite xmlSuite = createXmlSuite("Xml_Suite");
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package test.listeners.issue3095;
2+
3+
import org.testng.annotations.Test;
4+
5+
public class ChildClassSample extends SuperClassSample {
6+
@Test
7+
public void childTestMethod() {}
8+
}
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
1-
package org.testng.internal.listeners;
1+
package test.listeners.issue3095;
22

3-
import org.testng.IExecutionListener;
43
import org.testng.ITestNGListener;
54
import org.testng.ITestNGListenerFactory;
65

7-
public class DummyListenerFactory implements ITestNGListenerFactory, IExecutionListener {
6+
public class MyTestNgFactory implements ITestNGListener, ITestNGListenerFactory {
87
@Override
98
public ITestNGListener createListener(Class<? extends ITestNGListener> listenerClass) {
10-
return this;
9+
return null;
1110
}
1211
}

0 commit comments

Comments
 (0)