diff --git a/CHANGES.txt b/CHANGES.txt index 8afa9a25b0..4929c7740c 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,4 +1,5 @@ Current +Fixed: GITHUB-2771: After upgrading to TestNG 7.5.0, setting ITestResult.status to FAILURE doesn't fail the test anymore (Julien Herr & Krishnan Mahadevan) Fixed: GITHUB-2796: Option for onAfterClass to run after @AfterClass Fixed: GITHUB-2857: XmlTest index is not set for test suites invoked with YAML diff --git a/testng-collections/src/main/java/org/testng/collections/Lists.java b/testng-collections/src/main/java/org/testng/collections/Lists.java index f2daa94ad0..61ac5e114e 100644 --- a/testng-collections/src/main/java/org/testng/collections/Lists.java +++ b/testng-collections/src/main/java/org/testng/collections/Lists.java @@ -76,10 +76,4 @@ public static List merge(List l1, BiPredicate condition, List }); return result; } - - public static List newReversedArrayList(Collection c) { - List list = newArrayList(c); - Collections.reverse(list); - return list; - } } diff --git a/testng-core-api/src/main/java/org/testng/internal/RuntimeBehavior.java b/testng-core-api/src/main/java/org/testng/internal/RuntimeBehavior.java index ba0ea60504..4879e57623 100644 --- a/testng-core-api/src/main/java/org/testng/internal/RuntimeBehavior.java +++ b/testng-core-api/src/main/java/org/testng/internal/RuntimeBehavior.java @@ -1,5 +1,8 @@ package org.testng.internal; +import java.util.Arrays; +import java.util.List; +import java.util.Optional; import java.util.TimeZone; /** This class houses handling all JVM arguments by TestNG */ @@ -16,6 +19,7 @@ public final class RuntimeBehavior { public static final String TESTNG_DEFAULT_VERBOSE = "testng.default.verbose"; public static final String IGNORE_CALLBACK_INVOCATION_SKIPS = "testng.ignore.callback.skip"; public static final String SYMMETRIC_LISTENER_EXECUTION = "testng.listener.execution.symmetric"; + public static final String PREFERENTIAL_LISTENERS = "testng.preferential.listeners.package"; private RuntimeBehavior() {} @@ -23,6 +27,17 @@ public static boolean ignoreCallbackInvocationSkips() { return Boolean.getBoolean(IGNORE_CALLBACK_INVOCATION_SKIPS); } + /** + * @return - A comma separated list of packages that represent special listeners which users will + * expect to be executed after executing the regular listeners. Here special listeners can be + * anything that a user feels should be executed ALWAYS at the end. + */ + public static List getPreferentialListeners() { + String packages = + Optional.ofNullable(System.getProperty(PREFERENTIAL_LISTENERS)).orElse("com.intellij.rt.*"); + return Arrays.asList(packages.split(",")); + } + public static boolean strictParallelism() { return Boolean.getBoolean(STRICTLY_HONOUR_PARALLEL_MODE); } diff --git a/testng-core/src/main/java/org/testng/SuiteRunner.java b/testng-core/src/main/java/org/testng/SuiteRunner.java index 9bf007572b..1fb00342f5 100644 --- a/testng-core/src/main/java/org/testng/SuiteRunner.java +++ b/testng-core/src/main/java/org/testng/SuiteRunner.java @@ -69,6 +69,7 @@ public class SuiteRunner implements ISuite, IInvokedMethodListener { private final SuiteRunState suiteState = new SuiteRunState(); private final IAttributes attributes = new Attributes(); private final Set visualisers = Sets.newHashSet(); + private ITestListener exitCodeListener; public SuiteRunner( IConfiguration configuration, @@ -94,7 +95,7 @@ public SuiteRunner( useDefaultListeners, new ArrayList<>() /* method interceptor */, null /* invoked method listeners */, - null /* test listeners */, + new TestListenersContainer() /* test listeners */, null /* class listeners */, new DataProviderHolder(), comparator); @@ -108,7 +109,7 @@ protected SuiteRunner( boolean useDefaultListeners, List methodInterceptors, Collection invokedMethodListeners, - Collection testListeners, + TestListenersContainer container, Collection classListeners, DataProviderHolder holder, Comparator comparator) { @@ -120,7 +121,7 @@ protected SuiteRunner( useDefaultListeners, methodInterceptors, invokedMethodListeners, - testListeners, + container, classListeners, holder, comparator); @@ -134,7 +135,7 @@ private void init( boolean useDefaultListeners, List methodInterceptors, Collection invokedMethodListener, - Collection testListeners, + TestListenersContainer container, Collection classListeners, DataProviderHolder attribs, Comparator comparator) { @@ -146,6 +147,7 @@ private void init( this.xmlSuite = suite; this.useDefaultListeners = useDefaultListeners; this.tmpRunnerFactory = runnerFactory; + this.exitCodeListener = container.exitCodeListener; List localMethodInterceptors = Optional.ofNullable(methodInterceptors).orElse(Lists.newArrayList()); setOutputDir(outputDir); @@ -206,9 +208,7 @@ public T newInstance(Constructor constructor, Object... parameters) { } skipFailedInvocationCounts = suite.skipFailedInvocationCounts(); - if (null != testListeners) { - this.testListeners.addAll(testListeners); - } + this.testListeners.addAll(container.listeners); for (IClassListener classListener : Optional.ofNullable(classListeners).orElse(Collections.emptyList())) { this.classListeners.put(classListener.getClass(), classListener); @@ -257,13 +257,19 @@ public void setReportResults(boolean reportResults) { useDefaultListeners = reportResults; } + ITestListener getExitCodeListener() { + return exitCodeListener; + } + private void invokeListeners(boolean start) { if (start) { - for (ISuiteListener sl : Lists.newArrayList(listeners.values())) { + for (ISuiteListener sl : + ListenerOrderDeterminer.order(Lists.newArrayList(listeners.values()))) { sl.onStart(this); } } else { - List suiteListenersReversed = Lists.newReversedArrayList(listeners.values()); + List suiteListenersReversed = + ListenerOrderDeterminer.reversedOrder(listeners.values()); for (ISuiteListener sl : suiteListenersReversed) { sl.onFinish(this); } @@ -813,4 +819,19 @@ public List getAllMethods() { .flatMap(tr -> Arrays.stream(tr.getAllTestMethods())) .collect(Collectors.toList()); } + + static class TestListenersContainer { + private final List listeners = Lists.newArrayList(); + private final ITestListener exitCodeListener; + + TestListenersContainer() { + this(Collections.emptyList(), null); + } + + TestListenersContainer(List listeners, ITestListener exitCodeListener) { + this.listeners.addAll(listeners); + this.exitCodeListener = + Objects.requireNonNullElseGet(exitCodeListener, () -> new ITestListener() {}); + } + } } diff --git a/testng-core/src/main/java/org/testng/TestNG.java b/testng-core/src/main/java/org/testng/TestNG.java index 8ddbf093dd..2fa11a71c7 100644 --- a/testng-core/src/main/java/org/testng/TestNG.java +++ b/testng-core/src/main/java/org/testng/TestNG.java @@ -20,6 +20,7 @@ import java.util.Set; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.TimeUnit; +import org.testng.SuiteRunner.TestListenersContainer; import org.testng.annotations.ITestAnnotation; import org.testng.collections.Lists; import org.testng.collections.Maps; @@ -29,6 +30,7 @@ import org.testng.internal.DynamicGraph; import org.testng.internal.ExitCode; import org.testng.internal.IConfiguration; +import org.testng.internal.ListenerOrderDeterminer; import org.testng.internal.OverrideProcessor; import org.testng.internal.ReporterConfig; import org.testng.internal.RuntimeBehavior; @@ -924,7 +926,6 @@ private void initializeDefaultListeners() { if (m_failIfAllTestsSkipped) { this.exitCodeListener.failIfAllTestsSkipped(); } - addListener(this.exitCodeListener); if (m_useDefaultListeners) { addReporter(SuiteHTMLReporter.class); addReporter(Main.class); @@ -1106,17 +1107,22 @@ private void runSuiteAlterationListeners() { } private void runExecutionListeners(boolean start) { - List executionListeners = m_configuration.getExecutionListeners(); + List executionListeners = + ListenerOrderDeterminer.order(m_configuration.getExecutionListeners()); if (start) { for (IExecutionListener l : executionListeners) { l.onExecutionStart(); } + // Invoke our exit code listener after all the user's listeners have run. + exitCodeListener.onExecutionStart(); } else { List executionListenersReversed = - Lists.newReversedArrayList(executionListeners); + ListenerOrderDeterminer.reversedOrder(executionListeners); for (IExecutionListener l : executionListenersReversed) { l.onExecutionFinish(); } + // Invoke our exit code listener after all the user's listeners have run. + exitCodeListener.onExecutionFinish(); } } @@ -1128,7 +1134,11 @@ private static void usage() { } private void generateReports(List suiteRunners) { - for (IReporter reporter : m_reporters.values()) { + List reporters = new ArrayList<>(m_reporters.values()); + // Add our Exit code listener as the last of the reporter so that we can still accommodate + // whatever changes were done by a user's reporting listener + reporters.add(exitCodeListener); + for (IReporter reporter : reporters) { try { long start = System.currentTimeMillis(); reporter.generateReport(m_suites, suiteRunners, m_outputDir); @@ -1334,6 +1344,8 @@ private SuiteRunner createSuiteRunner(XmlSuite xmlSuite) { DataProviderHolder holder = new DataProviderHolder(); holder.addListeners(m_dataProviderListeners.values()); holder.addInterceptors(m_dataProviderInterceptors.values()); + TestListenersContainer container = + new TestListenersContainer(getTestListeners(), this.exitCodeListener); SuiteRunner result = new SuiteRunner( getConfiguration(), @@ -1343,7 +1355,7 @@ private SuiteRunner createSuiteRunner(XmlSuite xmlSuite) { m_useDefaultListeners, m_methodInterceptors, m_invokedMethodListeners.values(), - m_testListeners.values(), + container, m_classListeners.values(), holder, Systematiser.getComparator()); diff --git a/testng-core/src/main/java/org/testng/TestRunner.java b/testng-core/src/main/java/org/testng/TestRunner.java index c3c6f47bd7..ba78b1387b 100644 --- a/testng-core/src/main/java/org/testng/TestRunner.java +++ b/testng-core/src/main/java/org/testng/TestRunner.java @@ -10,6 +10,7 @@ import java.util.Date; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Set; import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingQueue; @@ -36,6 +37,7 @@ import org.testng.internal.IContainer; import org.testng.internal.ITestClassConfigInfo; import org.testng.internal.ITestResultNotifier; +import org.testng.internal.ListenerOrderDeterminer; import org.testng.internal.MethodGroupsHelper; import org.testng.internal.MethodHelper; import org.testng.internal.ResultMap; @@ -118,6 +120,8 @@ public class TestRunner // The XML method selector (groups/methods included/excluded in XML) private final XmlMethodSelector m_xmlMethodSelector = new XmlMethodSelector(); + private ITestListener exitCodeListener; + // // These next fields contain all the configuration methods found on this class. // At initialization time, they just contain all the various @Configuration methods @@ -252,6 +256,13 @@ private void init( m_injectorFactory = m_configuration.getInjectorFactory(); m_objectFactory = suite.getObjectFactory(); setVerbose(test.getVerbose()); + if (suiteRunner == null) { + if (suite instanceof SuiteRunner) { + setExitCodeListener(((SuiteRunner) suite).getExitCodeListener()); + } + } else { + setExitCodeListener(suiteRunner.getExitCodeListener()); + } boolean preserveOrder = test.getPreserveOrder(); IMethodInterceptor builtinInterceptor = @@ -963,15 +974,18 @@ private void logStart() { */ private void fireEvent(boolean isStart) { if (isStart) { - for (ITestListener itl : m_testListeners) { + for (ITestListener itl : ListenerOrderDeterminer.order(m_testListeners)) { itl.onStart(this); } + this.exitCodeListener.onStart(this); } else { - List testListenersReversed = Lists.newReversedArrayList(m_testListeners); + List testListenersReversed = + ListenerOrderDeterminer.reversedOrder(m_testListeners); for (ITestListener itl : testListenersReversed) { itl.onFinish(this); } + this.exitCodeListener.onFinish(this); } if (!isStart) { MethodHelper.clear(methods(this.getPassedConfigurations())); @@ -1238,6 +1252,15 @@ void addConfigurationListener(IConfigurationListener icl) { } } + private void setExitCodeListener(ITestListener exitCodeListener) { + this.exitCodeListener = exitCodeListener; + } + + @Override + public ITestListener getExitCodeListener() { + return Objects.requireNonNull(exitCodeListener, "ExitCodeListener cannot be null."); + } + private void dumpInvokedMethods() { MethodHelper.dumpInvokedMethodInfoToConsole(getAllTestMethods(), getVerbose()); } diff --git a/testng-core/src/main/java/org/testng/internal/ListenerOrderDeterminer.java b/testng-core/src/main/java/org/testng/internal/ListenerOrderDeterminer.java new file mode 100644 index 0000000000..4af7ea9967 --- /dev/null +++ b/testng-core/src/main/java/org/testng/internal/ListenerOrderDeterminer.java @@ -0,0 +1,82 @@ +package org.testng.internal; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Objects; +import java.util.function.Predicate; +import java.util.stream.Collectors; +import org.testng.collections.Lists; +import org.testng.internal.collections.Pair; + +/** + * A Utility that helps us differentiate between a user's listener and preferential Listener. + * + *

When dealing with TestNG listeners we would need to ensure that the user created listeners are + * invoked first followed by Preferential listeners. This is required so that we always honour any + * state changes that a user's listener may have done to the internal state of objects that can + * affect the outcome of the execution (for e.g.,{@link org.testng.ITestResult}) + * + *

The ordering must be done such that, when dealing with "beforeXXX|afterXXX" we group all the + * IDE listeners at the end. That way, we can always ensure that the preferential listeners (for + * e.g., IDE listeners) always honour the changes that were done to the TestNG internal states and + * give a consistent experience for users. + */ +public final class ListenerOrderDeterminer { + + private ListenerOrderDeterminer() { + // Defeat instantiation + } + + private static final List PREFERENTIAL_PACKAGES = + RuntimeBehavior.getPreferentialListeners().stream() + .map(each -> each.replaceAll("\\Q.*\\E", "")) + .collect(Collectors.toList()); + + private static final Predicate> SHOULD_ADD_AT_END = + clazz -> + PREFERENTIAL_PACKAGES.stream() + .anyMatch(each -> clazz.getPackage().getName().contains(each)); + + /** + * @param original - The original collection of listeners + * @return - A re-ordered collection wherein preferential listeners are added at the end + */ + public static List order(Collection original) { + Pair, List> ordered = arrange(original); + List ideListeners = ordered.first(); + List regularListeners = ordered.second(); + return Lists.merge(regularListeners, ideListeners); + } + + /** + * @param original - The original collection of listeners + * @return - A reversed ordered list wherein the user listeners are found in reverse order + * followed by preferential listeners also in reverse order. + */ + public static List reversedOrder(Collection original) { + Pair, List> ordered = arrange(original); + List preferentialListeners = ordered.first(); + List regularListeners = ordered.second(); + Collections.reverse(regularListeners); + Collections.reverse(preferentialListeners); + return Lists.merge(regularListeners, preferentialListeners); + } + + private static Pair, List> arrange(Collection original) { + List preferentialListeners = new ArrayList<>(); + List regularListeners = new ArrayList<>(); + original.stream() + .filter(Objects::nonNull) + .forEach( + each -> { + if (SHOULD_ADD_AT_END.test(each.getClass())) { + preferentialListeners.add(each); + } else { + regularListeners.add(each); + } + }); + return new Pair<>(preferentialListeners, regularListeners); + } +} 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 9b3ba1cc11..775b8ed9c1 100644 --- a/testng-core/src/main/java/org/testng/internal/TestListenerHelper.java +++ b/testng-core/src/main/java/org/testng/internal/TestListenerHelper.java @@ -42,7 +42,8 @@ public static void runPostConfigurationListeners( ITestNGMethod tm, List listeners, IConfigurationListener internal) { - List listenersreversed = Lists.newReversedArrayList(listeners); + List listenersreversed = + ListenerOrderDeterminer.reversedOrder(listeners); listenersreversed.add(internal); for (IConfigurationListener icl : listenersreversed) { switch (tr.getStatus()) { diff --git a/testng-core/src/main/java/org/testng/internal/invokers/BaseInvoker.java b/testng-core/src/main/java/org/testng/internal/invokers/BaseInvoker.java index dcdc272d8c..f54e678f04 100644 --- a/testng-core/src/main/java/org/testng/internal/invokers/BaseInvoker.java +++ b/testng-core/src/main/java/org/testng/internal/invokers/BaseInvoker.java @@ -11,10 +11,10 @@ import org.testng.SkipException; import org.testng.SuiteRunState; import org.testng.SuiteRunner; -import org.testng.collections.Lists; import org.testng.collections.Maps; import org.testng.internal.IConfiguration; import org.testng.internal.ITestResultNotifier; +import org.testng.internal.ListenerOrderDeterminer; import org.testng.internal.Utils; import org.testng.internal.annotations.IAnnotationFinder; @@ -70,8 +70,8 @@ protected void runInvokedMethodListeners( boolean isAfterInvocation = InvokedMethodListenerMethod.AFTER_INVOCATION == listenerMethod; Collection listeners = isAfterInvocation - ? Lists.newReversedArrayList(m_invokedMethodListeners) - : m_invokedMethodListeners; + ? ListenerOrderDeterminer.reversedOrder(m_invokedMethodListeners) + : ListenerOrderDeterminer.order(m_invokedMethodListeners); if (!isAfterInvocation) { suiteRunner.beforeInvocation(invokedMethod, testResult); } diff --git a/testng-core/src/main/java/org/testng/internal/invokers/TestInvoker.java b/testng-core/src/main/java/org/testng/internal/invokers/TestInvoker.java index 24110dfc5e..89c0fdffb5 100644 --- a/testng-core/src/main/java/org/testng/internal/invokers/TestInvoker.java +++ b/testng-core/src/main/java/org/testng/internal/invokers/TestInvoker.java @@ -279,9 +279,11 @@ public void runTestResultListener(ITestResult tr) { boolean isFinished = tr.getStatus() != ITestResult.STARTED; List listeners = isFinished - ? Lists.newReversedArrayList(m_notifier.getTestListeners()) - : m_notifier.getTestListeners(); + ? ListenerOrderDeterminer.reversedOrder(m_notifier.getTestListeners()) + : ListenerOrderDeterminer.order(m_notifier.getTestListeners()); TestListenerHelper.runTestListeners(tr, listeners); + TestListenerHelper.runTestListeners( + tr, Collections.singletonList(m_notifier.getExitCodeListener())); } private Collection dataProviderListeners() { diff --git a/testng-core/src/main/java/org/testng/junit/JUnit4TestRunner.java b/testng-core/src/main/java/org/testng/junit/JUnit4TestRunner.java index daf84d2f3c..2b7a938960 100644 --- a/testng-core/src/main/java/org/testng/junit/JUnit4TestRunner.java +++ b/testng-core/src/main/java/org/testng/junit/JUnit4TestRunner.java @@ -30,11 +30,13 @@ public class JUnit4TestRunner implements IJUnitTestRunner { private final List m_methods = Lists.newArrayList(); private Collection m_invokeListeners = Lists.newArrayList(); private final Map m_foundMethods = new WeakHashMap<>(); + private final ITestListener m_exitCodeListener; public JUnit4TestRunner(ITestObjectFactory objectFactory, ITestResultNotifier tr) { this.objectFactory = objectFactory; m_parentRunner = tr; m_listeners = m_parentRunner.getTestListeners(); + m_exitCodeListener = m_parentRunner.getExitCodeListener(); } /** @@ -128,6 +130,7 @@ public void testAssumptionFailure(Failure failure) { for (ITestListener l : m_listeners) { l.onTestSkipped(tr); } + m_exitCodeListener.onTestSkipped(tr); } @Override @@ -163,6 +166,7 @@ public void testFailure(Failure failure) throws Exception { for (ITestListener l : m_listeners) { l.onTestFailure(tr); } + m_exitCodeListener.onTestFailure(tr); } } @@ -178,6 +182,7 @@ public void testFinished(Description description) throws Exception { for (ITestListener l : m_listeners) { l.onTestSuccess(tr); } + m_exitCodeListener.onTestSuccess(tr); } m_methods.add(tr.getMethod()); } @@ -196,6 +201,7 @@ public void testIgnored(Description description) throws Exception { for (ITestListener l : m_listeners) { l.onTestSkipped(tr); } + m_exitCodeListener.onTestSkipped(tr); } } @@ -212,6 +218,7 @@ public void testStarted(Description description) throws Exception { for (ITestListener l : m_listeners) { l.onTestStart(tr); } + m_exitCodeListener.onTestStart(tr); } private void runAfterInvocationListeners(ITestResult tr) { diff --git a/testng-core/src/main/java/org/testng/junit/JUnitTestRunner.java b/testng-core/src/main/java/org/testng/junit/JUnitTestRunner.java index ca8264e624..69756dcb41 100644 --- a/testng-core/src/main/java/org/testng/junit/JUnitTestRunner.java +++ b/testng-core/src/main/java/org/testng/junit/JUnitTestRunner.java @@ -17,6 +17,7 @@ import org.testng.*; import org.testng.collections.Lists; import org.testng.internal.ITestResultNotifier; +import org.testng.internal.ListenerOrderDeterminer; import org.testng.internal.TestListenerHelper; import org.testng.internal.invokers.InvokedMethod; @@ -94,8 +95,8 @@ public void endTest(Test test) { boolean isFinished = tr.getStatus() != ITestResult.STARTED; List listeners = isFinished - ? Lists.newReversedArrayList(m_parentRunner.getTestListeners()) - : m_parentRunner.getTestListeners(); + ? ListenerOrderDeterminer.reversedOrder(m_parentRunner.getTestListeners()) + : ListenerOrderDeterminer.order(m_parentRunner.getTestListeners()); TestListenerHelper.runTestListeners(tr, listeners); } diff --git a/testng-core/src/test/java/test/listeners/ListenersTest.java b/testng-core/src/test/java/test/listeners/ListenersTest.java index 2c961d3954..2e51221f2f 100644 --- a/testng-core/src/test/java/test/listeners/ListenersTest.java +++ b/testng-core/src/test/java/test/listeners/ListenersTest.java @@ -14,6 +14,7 @@ import org.testng.TestNG; import org.testng.annotations.DataProvider; import org.testng.annotations.Test; +import org.testng.internal.ExitCode; import org.testng.xml.XmlSuite; import test.SimpleBaseTest; import test.listeners.issue2638.DummyInvokedMethodListener; @@ -23,6 +24,7 @@ import test.listeners.issue2685.SampleTestFailureListener; import test.listeners.issue2752.ListenerSample; import test.listeners.issue2752.TestClassSample; +import test.listeners.issue2771.TestCaseSample; public class ListenersTest extends SimpleBaseTest { @@ -92,6 +94,13 @@ public void testWiringInOfListenersInMultipleTestTagsWithListenerInSuite() { assertThat(logs.get("Xml_Test_2")).containsAll(expected); } + @Test(description = "GITHUB-2771") + public void testEnsureNativeListenersAreRunAlwaysAtEnd() { + TestNG testng = create(TestCaseSample.class); + testng.run(); + assertThat(testng.getStatus()).isEqualTo(ExitCode.FAILED); + } + private void setupTest(boolean addExplicitListener) { TestNG testng = new TestNG(); XmlSuite xmlSuite = createXmlSuite("Xml_Suite"); diff --git a/testng-core/src/test/java/test/listeners/issue2771/CustomSoftAssert.java b/testng-core/src/test/java/test/listeners/issue2771/CustomSoftAssert.java new file mode 100644 index 0000000000..a6b57d9534 --- /dev/null +++ b/testng-core/src/test/java/test/listeners/issue2771/CustomSoftAssert.java @@ -0,0 +1,12 @@ +package test.listeners.issue2771; + +import org.testng.ITestResult; +import org.testng.reporters.ExitCodeListener; + +public class CustomSoftAssert extends ExitCodeListener { + @Override + public void onTestSuccess(ITestResult result) { + result.setStatus(ITestResult.FAILURE); + result.setThrowable(new AssertionError("There have been some failed soft asserts")); + } +} diff --git a/testng-core/src/test/java/test/listeners/issue2771/TestCaseSample.java b/testng-core/src/test/java/test/listeners/issue2771/TestCaseSample.java new file mode 100644 index 0000000000..87682b63e6 --- /dev/null +++ b/testng-core/src/test/java/test/listeners/issue2771/TestCaseSample.java @@ -0,0 +1,10 @@ +package test.listeners.issue2771; + +import org.testng.annotations.Listeners; +import org.testng.annotations.Test; + +@Listeners(CustomSoftAssert.class) +public class TestCaseSample { + @Test + public void someCustomSoftAsserts() {} +} diff --git a/testng-core/src/test/java/test/reports/UniqueReporterInjectionTest.java b/testng-core/src/test/java/test/reports/UniqueReporterInjectionTest.java index 73b768d1c2..dd664c7b72 100644 --- a/testng-core/src/test/java/test/reports/UniqueReporterInjectionTest.java +++ b/testng-core/src/test/java/test/reports/UniqueReporterInjectionTest.java @@ -19,9 +19,8 @@ public void testPruningOfDuplicateReporter() { tng.addListener((ITestNGListener) new ReporterListenerForIssue1227()); tng.run(); // Since we have another reporting listener that is injected via the service loader file - // reporting listeners size will now have to be three (because the ExitCodeListener is also a - // reporter backed listener). - Assert.assertEquals(tng.getReporters().size(), 3); + // reporting listeners size will now have to be two + Assert.assertEquals(tng.getReporters().size(), 2); Assert.assertEquals(ReporterListenerForIssue1227.counter, 1); } diff --git a/testng-runner-api/src/main/java/org/testng/internal/ITestResultNotifier.java b/testng-runner-api/src/main/java/org/testng/internal/ITestResultNotifier.java index 1bdd55cc09..5d0fd211bf 100644 --- a/testng-runner-api/src/main/java/org/testng/internal/ITestResultNotifier.java +++ b/testng-runner-api/src/main/java/org/testng/internal/ITestResultNotifier.java @@ -33,4 +33,6 @@ public interface ITestResultNotifier { List getTestListeners(); List getConfigurationListeners(); + + ITestListener getExitCodeListener(); }