From d84933e70a7fe18009c18500b48daee5d14b03fe Mon Sep 17 00:00:00 2001 From: Krishnan Mahadevan Date: Tue, 10 Jan 2023 21:02:17 +0530 Subject: [PATCH] Support class level config failure policy Closes #2862 --- CHANGES.txt | 5 +- .../annotations/DisregardConfigFailure.java | 18 +++ .../internal/invokers/ConfigInvoker.java | 108 +++++++++---- .../internal/invokers/IConfigInvoker.java | 7 + .../FailurePolicyTest.java | 148 +++++++++++++++++- ...tionAtClassLevelForMethodConfigSample.java | 46 ++++++ ...ionAtMethodLevelForMethodConfigSample.java | 46 ++++++ ...ParentClassLevelForMethodConfigSample.java | 50 ++++++ ...arentClassLevelForMethodConfigSample2.java | 34 ++++ ...arentClassLevelForMethodConfigSample3.java | 34 ++++ .../ConfigFailurePolicyAwareReporter.java | 50 ++++++ 11 files changed, 513 insertions(+), 33 deletions(-) create mode 100644 testng-core-api/src/main/java/org/testng/annotations/DisregardConfigFailure.java create mode 100644 testng-core/src/test/java/test/configurationfailurepolicy/issue2862/AnnotationAtClassLevelForMethodConfigSample.java create mode 100644 testng-core/src/test/java/test/configurationfailurepolicy/issue2862/AnnotationAtMethodLevelForMethodConfigSample.java create mode 100644 testng-core/src/test/java/test/configurationfailurepolicy/issue2862/AnnotationAtParentClassLevelForMethodConfigSample.java create mode 100644 testng-core/src/test/java/test/configurationfailurepolicy/issue2862/AnnotationAtParentClassLevelForMethodConfigSample2.java create mode 100644 testng-core/src/test/java/test/configurationfailurepolicy/issue2862/AnnotationAtParentClassLevelForMethodConfigSample3.java create mode 100644 testng-core/src/test/java/test/configurationfailurepolicy/issue2862/ConfigFailurePolicyAwareReporter.java diff --git a/CHANGES.txt b/CHANGES.txt index 8afa9a25b0..84dfcfa9a6 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,6 +1,7 @@ Current -Fixed: GITHUB-2796: Option for onAfterClass to run after @AfterClass -Fixed: GITHUB-2857: XmlTest index is not set for test suites invoked with YAML +Fixed: GITHUB-2862: Allow test classes to define "configfailurepolicy" at a per class level (Krishnan Mahadevan) +Fixed: GITHUB-2796: Option for onAfterClass to run after @AfterClass (Oliver Hughes) +Fixed: GITHUB-2857: XmlTest index is not set for test suites invoked with YAML (Sergei Baranov) 7.7.1 Fixed: GITHUB-2854: overloaded assertEquals methods do not work from Groovy (Krishnan Mahadevan) diff --git a/testng-core-api/src/main/java/org/testng/annotations/DisregardConfigFailure.java b/testng-core-api/src/main/java/org/testng/annotations/DisregardConfigFailure.java new file mode 100644 index 0000000000..da04499cb7 --- /dev/null +++ b/testng-core-api/src/main/java/org/testng/annotations/DisregardConfigFailure.java @@ -0,0 +1,18 @@ +package org.testng.annotations; + +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.TYPE; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +/** + * A marker annotation that can be used to inform TestNG to ignore configuration failures for a + * specific class. This will have the last say in case the config failure policy was specified to be + * continue via the TestNG suite xml file. + * + *

This annotation can be added at a class level or at a method level. + */ +@Retention(java.lang.annotation.RetentionPolicy.RUNTIME) +@Target({METHOD, TYPE}) +public @interface DisregardConfigFailure {} diff --git a/testng-core/src/main/java/org/testng/internal/invokers/ConfigInvoker.java b/testng-core/src/main/java/org/testng/internal/invokers/ConfigInvoker.java index 9ecb0a4a45..a44a82c0df 100644 --- a/testng-core/src/main/java/org/testng/internal/invokers/ConfigInvoker.java +++ b/testng-core/src/main/java/org/testng/internal/invokers/ConfigInvoker.java @@ -11,16 +11,19 @@ import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; +import java.util.stream.Stream; import org.testng.ConfigurationNotInvokedException; import org.testng.IClass; import org.testng.IConfigurable; import org.testng.IInvokedMethodListener; +import org.testng.ITestClass; import org.testng.ITestContext; import org.testng.ITestNGMethod; import org.testng.ITestResult; import org.testng.Reporter; import org.testng.SuiteRunState; import org.testng.TestNGException; +import org.testng.annotations.DisregardConfigFailure; import org.testng.annotations.IConfigurationAnnotation; import org.testng.collections.Maps; import org.testng.collections.Sets; @@ -72,34 +75,49 @@ public ConfigInvoker( */ public boolean hasConfigurationFailureFor( ITestNGMethod testNGMethod, String[] groups, IClass testClass, Object instance) { + return hasConfigurationFailureFor(null, testNGMethod, groups, testClass, instance); + } + + @Override + public boolean hasConfigurationFailureFor( + ITestNGMethod configMethod, + ITestNGMethod testNGMethod, + String[] groups, + IClass testClass, + Object instance) { boolean result = false; Class cls = testClass.getRealClass(); if (m_suiteState.isFailed()) { - result = true; - } else { - boolean hasConfigurationFailures = classConfigurationFailed(cls, instance); - if (hasConfigurationFailures) { - if (!m_continueOnFailedConfiguration) { - result = true; - } else { - Set set = getInvocationResults(testClass); - result = set.contains(instance); - } - return result; + // if there were suite level failures, then short circuit here itself and report them. + return true; + } + + boolean annotationFound = canDisregardConfigFailure(testClass); + boolean hasConfigurationFailures = classConfigurationFailed(cls, instance); + if (hasConfigurationFailures) { + if (annotationFound) { + // We were told to ignore failures via the annotation. + return false; } - // if method is BeforeClass, currentTestMethod will be null - if (m_continueOnFailedConfiguration && hasConfigFailure(testNGMethod)) { - Object key = TestNgMethodUtils.getMethodInvocationToken(testNGMethod, instance); - result = m_methodInvocationResults.get(testNGMethod).contains(key); - } else if (!m_continueOnFailedConfiguration) { - for (Class clazz : m_classInvocationResults.keySet()) { - if (clazz.isAssignableFrom(cls) - && m_classInvocationResults.get(clazz).contains(instance)) { - result = true; - break; - } + if (m_continueOnFailedConfiguration) { + Set set = getInvocationResults(testClass); + result = set.contains(instance); + } else { + result = true; + } + return result; + } + // if method is BeforeClass, currentTestMethod will be null + if ((m_continueOnFailedConfiguration || annotationFound) && hasConfigFailure(testNGMethod)) { + Object key = TestNgMethodUtils.getMethodInvocationToken(testNGMethod, instance); + result = m_methodInvocationResults.get(testNGMethod).contains(key); + } else if (!(m_continueOnFailedConfiguration || annotationFound)) { + for (Class clazz : m_classInvocationResults.keySet()) { + if (clazz.isAssignableFrom(cls) && m_classInvocationResults.get(clazz).contains(instance)) { + result = true; + break; } } } @@ -245,6 +263,7 @@ public void invokeConfigurations(ConfigMethodArguments arguments) { continue; } if (hasConfigurationFailureFor( + tm, arguments.getTestMethod(), tm.getGroups(), arguments.getTestClass(), @@ -534,18 +553,18 @@ private void recordConfigurationInvocationFailed( if (annotation.getBeforeTestClass() || annotation.getAfterTestClass()) { // tm is the configuration method, and currentTestMethod is null for BeforeClass // methods, so we need testClass - if (m_continueOnFailedConfiguration) { - setClassInvocationFailure(testClass.getRealClass(), instance); - } else { - setClassInvocationFailure(tm.getRealClass(), instance); + Class clazzToUse = tm.getRealClass(); + if (m_continueOnFailedConfiguration || canDisregardConfigFailure(testClass.getRealClass())) { + clazzToUse = testClass.getRealClass(); } + setClassInvocationFailure(clazzToUse, instance); } // If before/afterTestMethod failed, mark either the config method's entire // class as failed, or just the current test method as failed, depending on // the configuration failure policy else if (annotation.getBeforeTestMethod() || annotation.getAfterTestMethod()) { - if (m_continueOnFailedConfiguration) { + if (m_continueOnFailedConfiguration || canDisregardConfigFailure(tm)) { setMethodInvocationFailure(currentTestMethod, instance); } else { setClassInvocationFailure(tm.getRealClass(), instance); @@ -606,4 +625,39 @@ private Set getInvocationResults(IClass testClass) { } return set; } + + private static boolean canDisregardConfigFailure(Class cls) { + while (!Object.class.equals(cls)) { + if (cls.isAnnotationPresent(DisregardConfigFailure.class)) { + return true; + } + cls = cls.getSuperclass(); + } + return false; + } + + private static boolean canDisregardConfigFailure(ITestNGMethod method) { + if (method == null) { + return false; + } + return method + .getConstructorOrMethod() + .getMethod() + .isAnnotationPresent(DisregardConfigFailure.class) + || canDisregardConfigFailure(method.getRealClass()); + } + + private static boolean canDisregardConfigFailure(IClass testClass) { + boolean instanceMatch = testClass instanceof ITestClass; + if (!instanceMatch) { + return false; + } + ITestClass tc = ((ITestClass) testClass); + return Stream.of( + tc.getBeforeTestConfigurationMethods() /*Considering @BeforeTest*/, + tc.getBeforeTestMethods() /*Considering @BeforeMethod*/, + tc.getBeforeClassMethods() /*Considering @BeforeClass*/) + .flatMap(Arrays::stream) + .anyMatch(ConfigInvoker::canDisregardConfigFailure); + } } diff --git a/testng-core/src/main/java/org/testng/internal/invokers/IConfigInvoker.java b/testng-core/src/main/java/org/testng/internal/invokers/IConfigInvoker.java index a9faa9c61c..5bfe64ff22 100644 --- a/testng-core/src/main/java/org/testng/internal/invokers/IConfigInvoker.java +++ b/testng-core/src/main/java/org/testng/internal/invokers/IConfigInvoker.java @@ -8,6 +8,13 @@ public interface IConfigInvoker { boolean hasConfigurationFailureFor( ITestNGMethod testNGMethod, String[] groups, IClass testClass, Object instance); + boolean hasConfigurationFailureFor( + ITestNGMethod configMethod, + ITestNGMethod testNGMethod, + String[] groups, + IClass testClass, + Object instance); + void invokeBeforeGroupsConfigurations(GroupConfigMethodArguments arguments); void invokeAfterGroupsConfigurations(GroupConfigMethodArguments arguments); diff --git a/testng-core/src/test/java/test/configurationfailurepolicy/FailurePolicyTest.java b/testng-core/src/test/java/test/configurationfailurepolicy/FailurePolicyTest.java index 62bf417a96..71bb4c0ed7 100644 --- a/testng-core/src/test/java/test/configurationfailurepolicy/FailurePolicyTest.java +++ b/testng-core/src/test/java/test/configurationfailurepolicy/FailurePolicyTest.java @@ -1,18 +1,31 @@ package test.configurationfailurepolicy; +import static org.assertj.core.api.Assertions.assertThat; import static org.testng.Assert.assertEquals; -import static test.SimpleBaseTest.getPathToResource; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; import org.testng.ITestContext; +import org.testng.ITestResult; import org.testng.TestListenerAdapter; import org.testng.TestNG; import org.testng.annotations.BeforeClass; import org.testng.annotations.DataProvider; +import org.testng.annotations.DisregardConfigFailure; import org.testng.annotations.Test; import org.testng.testhelper.OutputDirectoryPatch; import org.testng.xml.XmlSuite; +import test.SimpleBaseTest; +import test.configurationfailurepolicy.issue2862.AnnotationAtClassLevelForMethodConfigSample; +import test.configurationfailurepolicy.issue2862.AnnotationAtMethodLevelForMethodConfigSample; +import test.configurationfailurepolicy.issue2862.AnnotationAtParentClassLevelForMethodConfigSample; +import test.configurationfailurepolicy.issue2862.AnnotationAtParentClassLevelForMethodConfigSample2; +import test.configurationfailurepolicy.issue2862.AnnotationAtParentClassLevelForMethodConfigSample3; +import test.configurationfailurepolicy.issue2862.ConfigFailurePolicyAwareReporter; -public class FailurePolicyTest { +public class FailurePolicyTest extends SimpleBaseTest { // only if this is run from an xml file that sets this on the suite @BeforeClass(enabled = false) @@ -57,7 +70,7 @@ public Object[][] getData() { @Test(dataProvider = "dp") public void confFailureTest( - Class[] classesUnderTest, + Class[] classesUnderTest, int configurationFailures, int configurationSkips, int skippedTests) { @@ -75,7 +88,7 @@ public void confFailureTest( @Test public void confFailureTestInvolvingGroups() { - Class[] classesUnderTest = + Class[] classesUnderTest = new Class[] {ClassWithFailedBeforeClassMethodAndBeforeGroupsAfterClassAfterGroups.class}; TestListenerAdapter tla = new TestListenerAdapter(); @@ -178,4 +191,131 @@ private void verify( "wrong number of configuration skips"); assertEquals(tla.getSkippedTests().size(), skippedTests, "wrong number of skipped tests"); } + + @Test(description = "GITHUB-2862") + public void ensureFailurePolicyCanBeOverriddenWithAnnotationAtMethodLevel() { + Class clazzOne = AnnotationAtMethodLevelForMethodConfigSample.AnnotatedClassSample.class; + Class clazzTwo = AnnotationAtMethodLevelForMethodConfigSample.RegularClassSample.class; + TestNG tng = create(clazzOne, clazzTwo); + ConfigFailurePolicyAwareReporter reporter = new ConfigFailurePolicyAwareReporter(); + tng.addListener(reporter); + tng.run(); + Map, Map>> grouped = reporter.getGrouped(); + assertThat(grouped.get(clazzOne).get(ITestResult.SKIP)) + .withFailMessage( + clazzOne.getName() + + " should have ONLY 1 skipped test due to config failure since this " + + "class has the custom annotation @" + + DisregardConfigFailure.class.getSimpleName()) + .hasSize(1); + + assertThat(grouped.get(clazzTwo).get(ITestResult.SKIP)) + .withFailMessage( + clazzTwo.getName() + + " should have 2 skipped test due to config failure since this " + + "class DOES NOT has the custom annotation @" + + DisregardConfigFailure.class.getSimpleName() + + " and no config failure policy has been set.") + .hasSize(2); + } + + @Test(description = "GITHUB-2862") + public void ensureFailurePolicyCanBeOverriddenWithAnnotationAtClassLevel() { + Class clazzOne = AnnotationAtClassLevelForMethodConfigSample.AnnotatedClassSample.class; + Class clazzTwo = AnnotationAtClassLevelForMethodConfigSample.RegularClassSample.class; + TestNG tng = create(clazzOne, clazzTwo); + ConfigFailurePolicyAwareReporter reporter = new ConfigFailurePolicyAwareReporter(); + tng.addListener(reporter); + tng.run(); + Map, Map>> grouped = reporter.getGrouped(); + assertThat(grouped.get(clazzOne).get(ITestResult.SKIP)) + .withFailMessage( + clazzOne.getName() + + " should have ONLY 1 skipped test due to config failure since this " + + "class has the custom annotation @" + + DisregardConfigFailure.class.getSimpleName()) + .hasSize(1); + + assertThat(grouped.get(clazzTwo).get(ITestResult.SKIP)) + .withFailMessage( + clazzTwo.getName() + + " should have 2 skipped test due to config failure since this " + + "class DOES NOT has the custom annotation @" + + DisregardConfigFailure.class.getSimpleName() + + " and no config failure policy has been set.") + .hasSize(2); + } + + @Test(description = "GITHUB-2862") + // Scenario: Annotation at base class, but no config methods in base class + public void ensureFailurePolicyCanBeOverriddenWithAnnotationAtBaseClassLevel() { + Class clazzOne = + AnnotationAtParentClassLevelForMethodConfigSample.AnnotatedClassSample.class; + Class clazzTwo = AnnotationAtParentClassLevelForMethodConfigSample.RegularClassSample.class; + TestNG tng = create(clazzOne, clazzTwo); + ConfigFailurePolicyAwareReporter reporter = new ConfigFailurePolicyAwareReporter(); + tng.addListener(reporter); + tng.run(); + Map, Map>> grouped = reporter.getGrouped(); + assertThat(grouped.get(clazzOne).get(ITestResult.SKIP)) + .withFailMessage( + clazzOne.getName() + + " should have ONLY 1 skipped test due to config failure since this " + + "class has the custom annotation @" + + DisregardConfigFailure.class.getName()) + .hasSize(1); + + assertThat(grouped.get(clazzTwo).get(ITestResult.SKIP)) + .withFailMessage( + clazzTwo.getSimpleName() + + " should have 2 skipped test due to config failure since this " + + "class DOES NOT has the custom annotation @" + + DisregardConfigFailure.class.getSimpleName() + + " and no config failure policy has been set.") + .hasSize(2); + } + + @Test(description = "GITHUB-2862") + // Scenario: Annotation at base class, with a failing "@BeforeTest" + public void ensureFailurePolicyCanBeOverriddenWithAnnotationAndFailuresAtBaseClassLevel() { + Class clazzOne = + AnnotationAtParentClassLevelForMethodConfigSample2.AnnotatedClassSample.class; + TestNG tng = create(clazzOne); + ConfigFailurePolicyAwareReporter reporter = new ConfigFailurePolicyAwareReporter(); + tng.addListener(reporter); + tng.run(); + Map, Map>> grouped = reporter.getGrouped(); + assertThat( + grouped + .getOrDefault(clazzOne, new HashMap<>()) + .getOrDefault(ITestResult.SKIP, new ArrayList<>())) + .withFailMessage( + clazzOne.getName() + + " should NOT HAVE ANY skipped test due to config failure since this " + + "class has the custom annotation @" + + DisregardConfigFailure.class.getName()) + .isEmpty(); + } + + @Test(description = "GITHUB-2862") + // Scenario: Annotation at base class, with a failing "@BeforeClass" + public void ensureFailurePolicyCanBeOverriddenWithAnnotationAndFailuresAtBaseClassLevel2() { + Class clazzOne = + AnnotationAtParentClassLevelForMethodConfigSample3.AnnotatedClassSample.class; + TestNG tng = create(clazzOne); + ConfigFailurePolicyAwareReporter reporter = new ConfigFailurePolicyAwareReporter(); + tng.addListener(reporter); + tng.run(); + Map, Map>> grouped = reporter.getGrouped(); + assertThat( + grouped + .getOrDefault(clazzOne, new HashMap<>()) + .getOrDefault(ITestResult.SKIP, new ArrayList<>())) + .withFailMessage( + clazzOne.getName() + + " should NOT HAVE ANY skipped test due to config failure since this " + + "class has the custom annotation @" + + DisregardConfigFailure.class.getName()) + .isEmpty(); + } } diff --git a/testng-core/src/test/java/test/configurationfailurepolicy/issue2862/AnnotationAtClassLevelForMethodConfigSample.java b/testng-core/src/test/java/test/configurationfailurepolicy/issue2862/AnnotationAtClassLevelForMethodConfigSample.java new file mode 100644 index 0000000000..a3ac6b2f13 --- /dev/null +++ b/testng-core/src/test/java/test/configurationfailurepolicy/issue2862/AnnotationAtClassLevelForMethodConfigSample.java @@ -0,0 +1,46 @@ +package test.configurationfailurepolicy.issue2862; + +import org.testng.Assert; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.DisregardConfigFailure; +import org.testng.annotations.Test; + +public class AnnotationAtClassLevelForMethodConfigSample { + + @DisregardConfigFailure + public static class AnnotatedClassSample { + private boolean fail = true; + + @BeforeMethod + public void beforeMethod() { + if (fail) { + fail = false; + Assert.fail(); + } + } + + @Test + public void testA() {} + + @Test + public void testB() {} + } + + public static class RegularClassSample { + private boolean fail = true; + + @BeforeMethod + public void beforeMethod() { + if (fail) { + fail = false; + Assert.fail(); + } + } + + @Test + public void testA() {} + + @Test + public void testB() {} + } +} diff --git a/testng-core/src/test/java/test/configurationfailurepolicy/issue2862/AnnotationAtMethodLevelForMethodConfigSample.java b/testng-core/src/test/java/test/configurationfailurepolicy/issue2862/AnnotationAtMethodLevelForMethodConfigSample.java new file mode 100644 index 0000000000..0317b749ee --- /dev/null +++ b/testng-core/src/test/java/test/configurationfailurepolicy/issue2862/AnnotationAtMethodLevelForMethodConfigSample.java @@ -0,0 +1,46 @@ +package test.configurationfailurepolicy.issue2862; + +import org.testng.Assert; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.DisregardConfigFailure; +import org.testng.annotations.Test; + +public class AnnotationAtMethodLevelForMethodConfigSample { + + public static class AnnotatedClassSample { + private boolean fail = true; + + @DisregardConfigFailure + @BeforeMethod + public void beforeMethod1() { + if (fail) { + fail = false; + Assert.fail(); + } + } + + @Test + public void testA() {} + + @Test + public void testB() {} + } + + public static class RegularClassSample { + private boolean fail = true; + + @BeforeMethod + public void beforeMethod2() { + if (fail) { + fail = false; + Assert.fail(); + } + } + + @Test + public void testA() {} + + @Test + public void testB() {} + } +} diff --git a/testng-core/src/test/java/test/configurationfailurepolicy/issue2862/AnnotationAtParentClassLevelForMethodConfigSample.java b/testng-core/src/test/java/test/configurationfailurepolicy/issue2862/AnnotationAtParentClassLevelForMethodConfigSample.java new file mode 100644 index 0000000000..f0df60ebce --- /dev/null +++ b/testng-core/src/test/java/test/configurationfailurepolicy/issue2862/AnnotationAtParentClassLevelForMethodConfigSample.java @@ -0,0 +1,50 @@ +package test.configurationfailurepolicy.issue2862; + +import org.testng.Assert; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.DisregardConfigFailure; +import org.testng.annotations.Test; + +public class AnnotationAtParentClassLevelForMethodConfigSample { + + @DisregardConfigFailure + public static class MyBaseClassSample {} + + public static class AnnotatedClassSample extends MyBaseClassSample { + + private boolean fail = true; + + @BeforeMethod + public void beforeMethod() { + if (fail) { + fail = false; + Assert.fail(); + } + } + + @Test + public void testA() {} + + @Test + public void testB() {} + } + + public static class RegularClassSample { + + private boolean fail = true; + + @BeforeMethod + public void beforeMethod() { + if (fail) { + fail = false; + Assert.fail(); + } + } + + @Test + public void testA() {} + + @Test + public void testB() {} + } +} diff --git a/testng-core/src/test/java/test/configurationfailurepolicy/issue2862/AnnotationAtParentClassLevelForMethodConfigSample2.java b/testng-core/src/test/java/test/configurationfailurepolicy/issue2862/AnnotationAtParentClassLevelForMethodConfigSample2.java new file mode 100644 index 0000000000..cba9835329 --- /dev/null +++ b/testng-core/src/test/java/test/configurationfailurepolicy/issue2862/AnnotationAtParentClassLevelForMethodConfigSample2.java @@ -0,0 +1,34 @@ +package test.configurationfailurepolicy.issue2862; + +import org.testng.Assert; +import org.testng.annotations.BeforeClass; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.BeforeTest; +import org.testng.annotations.DisregardConfigFailure; +import org.testng.annotations.Test; + +public class AnnotationAtParentClassLevelForMethodConfigSample2 { + + @DisregardConfigFailure + public static class MyBaseClassSample { + @BeforeTest + public void beforeTest() { + Assert.fail(); + } + + @BeforeClass + public void beforeClassInBaseClass() {} + } + + public static class AnnotatedClassSample extends MyBaseClassSample { + + @BeforeMethod + public void beforeMethodInChildTestClass() {} + + @Test + public void testA() {} + + @Test + public void testB() {} + } +} diff --git a/testng-core/src/test/java/test/configurationfailurepolicy/issue2862/AnnotationAtParentClassLevelForMethodConfigSample3.java b/testng-core/src/test/java/test/configurationfailurepolicy/issue2862/AnnotationAtParentClassLevelForMethodConfigSample3.java new file mode 100644 index 0000000000..43de69d7e3 --- /dev/null +++ b/testng-core/src/test/java/test/configurationfailurepolicy/issue2862/AnnotationAtParentClassLevelForMethodConfigSample3.java @@ -0,0 +1,34 @@ +package test.configurationfailurepolicy.issue2862; + +import org.testng.Assert; +import org.testng.annotations.BeforeClass; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.BeforeTest; +import org.testng.annotations.DisregardConfigFailure; +import org.testng.annotations.Test; + +public class AnnotationAtParentClassLevelForMethodConfigSample3 { + + @DisregardConfigFailure + public static class MyBaseClassSample { + @BeforeTest + public void beforeTest() {} + + @BeforeClass + public void beforeClassInBaseClass() { + Assert.fail(); + } + } + + public static class AnnotatedClassSample extends MyBaseClassSample { + + @BeforeMethod + public void beforeMethodInChildTestClass() {} + + @Test + public void testA() {} + + @Test + public void testB() {} + } +} diff --git a/testng-core/src/test/java/test/configurationfailurepolicy/issue2862/ConfigFailurePolicyAwareReporter.java b/testng-core/src/test/java/test/configurationfailurepolicy/issue2862/ConfigFailurePolicyAwareReporter.java new file mode 100644 index 0000000000..ecdcb72776 --- /dev/null +++ b/testng-core/src/test/java/test/configurationfailurepolicy/issue2862/ConfigFailurePolicyAwareReporter.java @@ -0,0 +1,50 @@ +package test.configurationfailurepolicy.issue2862; + +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; +import java.util.stream.Collectors; +import org.testng.IReporter; +import org.testng.ISuite; +import org.testng.ISuiteResult; +import org.testng.ITestContext; +import org.testng.ITestResult; +import org.testng.xml.XmlSuite; + +public class ConfigFailurePolicyAwareReporter implements IReporter { + + private Map, List> grouped; + + @Override + public void generateReport( + List xmlSuites, List suites, String outputDirectory) { + grouped = + suites.stream() + .flatMap(each -> each.getResults().values().stream()) + .map(ISuiteResult::getTestContext) + .flatMap(each -> results(each).stream()) + .collect(Collectors.groupingBy(ConfigFailurePolicyAwareReporter::clazz)); + } + + public Map, Map>> getGrouped() { + return grouped.entrySet().stream() + .collect(Collectors.toMap(Entry::getKey, v -> byStatus(v.getValue()))); + } + + private static Class clazz(ITestResult itr) { + return itr.getMethod().getRealClass(); + } + + private static Map> byStatus(List testResults) { + return testResults.stream().collect(Collectors.groupingBy(ITestResult::getStatus)); + } + + private static Set results(ITestContext itr) { + Set results = new HashSet<>(itr.getSkippedTests().getAllResults()); + results.addAll(itr.getPassedTests().getAllResults()); + results.addAll(itr.getFailedTests().getAllResults()); + return results; + } +}