From 38b4fd4fc875da7b0a01cd4ee0b9c47410f1d4cb Mon Sep 17 00:00:00 2001 From: Krishnan Mahadevan Date: Mon, 29 Jan 2024 22:57:33 +0530 Subject: [PATCH] Support ITestNGFactory customisation Closes #3059 --- CHANGES.txt | 1 + .../main/java/org/testng/CommandLineArgs.java | 5 +++ .../src/main/java/org/testng/TestNG.java | 18 +++++++- .../src/main/java/org/testng/TestRunner.java | 5 ++- .../org/testng/internal/Configuration.java | 13 ++++++ .../org/testng/internal/IConfiguration.java | 4 ++ .../testng/internal/TestListenerHelper.java | 2 + .../objects/pojo/CreationAttributes.java | 2 - .../listeners/factory/ExampleListener.java | 18 ++++++++ .../listeners/factory/SampleTestCase.java | 8 ++++ .../listeners/factory/SampleTestFactory.java | 30 ++++++++++++++ .../listeners/factory/TestNGFactoryTest.java | 41 +++++++++++++++++++ testng-core/src/test/resources/testng.xml | 1 + 13 files changed, 143 insertions(+), 5 deletions(-) create mode 100644 testng-core/src/test/java/test/listeners/factory/ExampleListener.java create mode 100644 testng-core/src/test/java/test/listeners/factory/SampleTestCase.java create mode 100644 testng-core/src/test/java/test/listeners/factory/SampleTestFactory.java create mode 100644 testng-core/src/test/java/test/listeners/factory/TestNGFactoryTest.java diff --git a/CHANGES.txt b/CHANGES.txt index f21303d439..3cd7b0f7ef 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,5 +1,6 @@ Current 7.10.0 +Fixed: GITHUB-3059: Support the ability to inject custom listener factory (Krishnan Mahadevan) Fixed: GITHUB-3038: java.lang.IllegalStateException: Results per method should NOT have been empty (Krishnan Mahadevan) Fixed: GITHUB-3022: Remove deprecated JUnit related support in TestNG (Krishnan Mahadevan) diff --git a/testng-core/src/main/java/org/testng/CommandLineArgs.java b/testng-core/src/main/java/org/testng/CommandLineArgs.java index 7f2cce1ca3..c59431accb 100644 --- a/testng-core/src/main/java/org/testng/CommandLineArgs.java +++ b/testng-core/src/main/java/org/testng/CommandLineArgs.java @@ -166,6 +166,11 @@ public class CommandLineArgs { description = "The factory used to create tests") public String testRunnerFactory; + public static final String LISTENER_FACTORY = "-listenerfactory"; + + @Parameter(names = LISTENER_FACTORY, description = "The factory used to create TestNG listeners") + public String listenerFactory; + public static final String METHODS = "-methods"; @Parameter(names = METHODS, description = "Comma separated of test methods") diff --git a/testng-core/src/main/java/org/testng/TestNG.java b/testng-core/src/main/java/org/testng/TestNG.java index 5a0cfb4611..053f7fd1d5 100644 --- a/testng-core/src/main/java/org/testng/TestNG.java +++ b/testng-core/src/main/java/org/testng/TestNG.java @@ -26,6 +26,7 @@ import org.testng.collections.Sets; import org.testng.internal.ClassHelper; import org.testng.internal.Configuration; +import org.testng.internal.DefaultListenerFactory; import org.testng.internal.DynamicGraph; import org.testng.internal.ExitCode; import org.testng.internal.IConfiguration; @@ -682,8 +683,12 @@ public void setObjectFactory(ITestObjectFactory factory) { * IReporter */ public void setListenerClasses(List> classes) { + ITestNGListenerFactory factory = m_configuration.getListenerFactory(); + if (factory == null) { + factory = new DefaultListenerFactory(m_objectFactory, null); + } for (Class cls : classes) { - addListener(m_objectFactory.newInstance(cls)); + addListener(factory.createListener(cls)); } } @@ -835,6 +840,10 @@ public void setExecutorFactoryClass(String clazzName) { this.m_executorFactory = createExecutorFactoryInstanceUsing(clazzName); } + public void setListenerFactory(ITestNGListenerFactory factory) { + this.m_configuration.setListenerFactory(factory); + } + public void setGenerateResultsPerSuite(boolean generateResultsPerSuite) { this.m_generateResultsPerSuite = generateResultsPerSuite; } @@ -1466,6 +1475,13 @@ protected void configure(CommandLineArgs cla) { .ifPresent(value -> propagateDataProviderFailureAsTestFailure()); setReportAllDataDrivenTestsAsSkipped(cla.includeAllDataDrivenTestsWhenSkipping); + Optional.ofNullable(cla.listenerFactory) + .map(ClassHelper::forName) + .filter(ITestNGListenerFactory.class::isAssignableFrom) + .map(it -> m_objectFactory.newInstance(it)) + .map(it -> (ITestNGListenerFactory) it) + .ifPresent(this::setListenerFactory); + Optional.ofNullable(cla.generateResultsPerSuite).ifPresent(this::setGenerateResultsPerSuite); if (cla.verbose != null) { diff --git a/testng-core/src/main/java/org/testng/TestRunner.java b/testng-core/src/main/java/org/testng/TestRunner.java index f7aa0f549e..35b063ea0d 100644 --- a/testng-core/src/main/java/org/testng/TestRunner.java +++ b/testng-core/src/main/java/org/testng/TestRunner.java @@ -11,6 +11,7 @@ import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.Optional; import java.util.Set; import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingQueue; @@ -377,8 +378,8 @@ private void initListeners() { // ITestNGListenerFactory factory = - TestListenerHelper.createListenerFactory( - m_objectFactory, m_testClassFinder, listenerFactoryClass, this); + Optional.ofNullable(m_configuration.getListenerFactory()) + .orElse(new DefaultListenerFactory(m_objectFactory, this)); // Instantiate all the listeners for (Class c : listenerClasses) { diff --git a/testng-core/src/main/java/org/testng/internal/Configuration.java b/testng-core/src/main/java/org/testng/internal/Configuration.java index 4dcabdf1f6..c309cd9459 100644 --- a/testng-core/src/main/java/org/testng/internal/Configuration.java +++ b/testng-core/src/main/java/org/testng/internal/Configuration.java @@ -7,6 +7,7 @@ import org.testng.IExecutionListener; import org.testng.IHookable; import org.testng.IInjectorFactory; +import org.testng.ITestNGListenerFactory; import org.testng.ITestObjectFactory; import org.testng.collections.Lists; import org.testng.collections.Maps; @@ -24,6 +25,8 @@ public class Configuration implements IConfiguration { private IHookable m_hookable; private IConfigurable m_configurable; + private ITestNGListenerFactory m_listenerFactory; + private boolean shareThreadPoolForDataProviders = false; private final Map, IExecutionListener> m_executionListeners = Maps.newLinkedHashMap(); @@ -63,6 +66,16 @@ public void setAnnotationFinder(IAnnotationFinder finder) { m_annotationFinder = finder; } + @Override + public void setListenerFactory(ITestNGListenerFactory testNGListenerFactory) { + this.m_listenerFactory = testNGListenerFactory; + } + + @Override + public ITestNGListenerFactory getListenerFactory() { + return m_listenerFactory; + } + @Override public ITestObjectFactory getObjectFactory() { return m_objectFactory; diff --git a/testng-core/src/main/java/org/testng/internal/IConfiguration.java b/testng-core/src/main/java/org/testng/internal/IConfiguration.java index 26bed3c0f6..81c011a1ca 100644 --- a/testng-core/src/main/java/org/testng/internal/IConfiguration.java +++ b/testng-core/src/main/java/org/testng/internal/IConfiguration.java @@ -10,6 +10,10 @@ public interface IConfiguration { void setAnnotationFinder(IAnnotationFinder finder); + void setListenerFactory(ITestNGListenerFactory testNGListenerFactory); + + ITestNGListenerFactory getListenerFactory(); + ITestObjectFactory getObjectFactory(); void setObjectFactory(ITestObjectFactory m_objectFactory); diff --git a/testng-core/src/main/java/org/testng/internal/TestListenerHelper.java b/testng-core/src/main/java/org/testng/internal/TestListenerHelper.java index 775b8ed9c1..a2626b850d 100644 --- a/testng-core/src/main/java/org/testng/internal/TestListenerHelper.java +++ b/testng-core/src/main/java/org/testng/internal/TestListenerHelper.java @@ -161,6 +161,8 @@ public static ListenerHolder findAllListeners(Class cls, IAnnotationFinder fi return result; } + /** @deprecated - This method stands deprecated as of TestNG version 7.10.0 */ + @Deprecated public static ITestNGListenerFactory createListenerFactory( ITestObjectFactory objectFactory, TestNGClassFinder finder, diff --git a/testng-core/src/main/java/org/testng/internal/objects/pojo/CreationAttributes.java b/testng-core/src/main/java/org/testng/internal/objects/pojo/CreationAttributes.java index 8fafb8c453..68e89483da 100644 --- a/testng-core/src/main/java/org/testng/internal/objects/pojo/CreationAttributes.java +++ b/testng-core/src/main/java/org/testng/internal/objects/pojo/CreationAttributes.java @@ -1,6 +1,5 @@ package org.testng.internal.objects.pojo; -import java.util.Objects; import org.testng.ITestContext; import org.testng.internal.invokers.objects.GuiceContext; @@ -13,7 +12,6 @@ public class CreationAttributes { private final GuiceContext suiteContext; public CreationAttributes(ITestContext ctx, BasicAttributes basic, DetailedAttributes detailed) { - Objects.requireNonNull(ctx); this.basic = basic; this.detailed = detailed; this.context = ctx; diff --git a/testng-core/src/test/java/test/listeners/factory/ExampleListener.java b/testng-core/src/test/java/test/listeners/factory/ExampleListener.java new file mode 100644 index 0000000000..bca6c6a3c2 --- /dev/null +++ b/testng-core/src/test/java/test/listeners/factory/ExampleListener.java @@ -0,0 +1,18 @@ +package test.listeners.factory; + +import org.testng.IExecutionListener; + +public class ExampleListener implements IExecutionListener { + + public static ExampleListener instance; + + public ExampleListener() { + setInstance(this); + } + + private static synchronized void setInstance(ExampleListener instance) { + if (ExampleListener.instance == null) { + ExampleListener.instance = instance; + } + } +} diff --git a/testng-core/src/test/java/test/listeners/factory/SampleTestCase.java b/testng-core/src/test/java/test/listeners/factory/SampleTestCase.java new file mode 100644 index 0000000000..e96d57d69e --- /dev/null +++ b/testng-core/src/test/java/test/listeners/factory/SampleTestCase.java @@ -0,0 +1,8 @@ +package test.listeners.factory; + +import org.testng.annotations.Test; + +public class SampleTestCase { + @Test + public void testMethod() {} +} diff --git a/testng-core/src/test/java/test/listeners/factory/SampleTestFactory.java b/testng-core/src/test/java/test/listeners/factory/SampleTestFactory.java new file mode 100644 index 0000000000..98ca978ae5 --- /dev/null +++ b/testng-core/src/test/java/test/listeners/factory/SampleTestFactory.java @@ -0,0 +1,30 @@ +package test.listeners.factory; + +import org.testng.ITestNGListener; +import org.testng.ITestNGListenerFactory; +import org.testng.internal.objects.InstanceCreator; + +public class SampleTestFactory implements ITestNGListenerFactory { + + public static ITestNGListenerFactory instance; + + private boolean invoked = false; + + public boolean isInvoked() { + return invoked; + } + + public SampleTestFactory() { + setInstance(this); + } + + private static void setInstance(ITestNGListenerFactory instance) { + SampleTestFactory.instance = instance; + } + + @Override + public ITestNGListener createListener(Class listenerClass) { + invoked = true; + return InstanceCreator.newInstance(listenerClass); + } +} diff --git a/testng-core/src/test/java/test/listeners/factory/TestNGFactoryTest.java b/testng-core/src/test/java/test/listeners/factory/TestNGFactoryTest.java new file mode 100644 index 0000000000..87d4398b4c --- /dev/null +++ b/testng-core/src/test/java/test/listeners/factory/TestNGFactoryTest.java @@ -0,0 +1,41 @@ +package test.listeners.factory; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; + +import java.util.List; +import org.testng.CommandLineArgs; +import org.testng.TestNG; +import org.testng.annotations.Test; +import test.SimpleBaseTest; + +public class TestNGFactoryTest extends SimpleBaseTest { + + @Test(description = "GITHUB-3059") + public void testListenerFactoryViaConfigurationArg() { + String[] args = + new String[] { + CommandLineArgs.LISTENER_FACTORY, + SampleTestFactory.class.getName(), + CommandLineArgs.TEST_CLASS, + SampleTestCase.class.getName(), + CommandLineArgs.LISTENER, + ExampleListener.class.getName() + }; + TestNG testng = TestNG.privateMain(args, null); + assertThat(SampleTestFactory.instance).isNotNull(); + assertThat(ExampleListener.instance).isNotNull(); + assertThat(testng.getStatus()).isZero(); + } + + @Test(description = "GITHUB-3059") + public void testListenerFactoryViaTestNGApi() { + TestNG testng = new TestNG(); + SampleTestFactory factory = new SampleTestFactory(); + testng.setListenerFactory(factory); + testng.setListenerClasses(List.of(ExampleListener.class)); + testng.setTestClasses(new Class[] {SampleTestCase.class}); + testng.run(); + assertThat(testng.getStatus()).isZero(); + assertThat(factory.isInvoked()).isTrue(); + } +} diff --git a/testng-core/src/test/resources/testng.xml b/testng-core/src/test/resources/testng.xml index 430637b164..8b27e69553 100644 --- a/testng-core/src/test/resources/testng.xml +++ b/testng-core/src/test/resources/testng.xml @@ -192,6 +192,7 @@ +