From a5b850815ad69db74e2f50defca3543aa69562f0 Mon Sep 17 00:00:00 2001 From: Krishnan Mahadevan Date: Wed, 23 Nov 2022 13:28:44 +0530 Subject: [PATCH] Honour regex in dependsOnMethods Closes: #141 --- CHANGES.txt | 3 +- .../main/java/org/testng/DependencyMap.java | 16 ++- .../org/testng/internal/MethodHelper.java | 13 ++- .../java/test/dependent/DependentTest.java | 108 +++++++++++++++++- .../java/test/dependent/issue141/ASample.java | 8 ++ .../java/test/dependent/issue141/BSample.java | 11 ++ .../issue141/ErrorScenarioNestedSample.java | 15 +++ .../MultipleMatchesTestClassSample.java | 18 +++ .../issue141/NestedTestClassSample.java | 25 ++++ .../issue141/NestedTestClassSample2.java | 15 +++ .../test/dependent/issue141/SimpleSample.java | 9 ++ .../test/dependent/issue141/SkipReasoner.java | 24 ++++ .../dependent/issue141/TestClassSample.java | 12 ++ .../java/org/testng/internal/TestResult.java | 16 ++- 14 files changed, 285 insertions(+), 8 deletions(-) create mode 100644 testng-core/src/test/java/test/dependent/issue141/ASample.java create mode 100644 testng-core/src/test/java/test/dependent/issue141/BSample.java create mode 100644 testng-core/src/test/java/test/dependent/issue141/ErrorScenarioNestedSample.java create mode 100644 testng-core/src/test/java/test/dependent/issue141/MultipleMatchesTestClassSample.java create mode 100644 testng-core/src/test/java/test/dependent/issue141/NestedTestClassSample.java create mode 100644 testng-core/src/test/java/test/dependent/issue141/NestedTestClassSample2.java create mode 100644 testng-core/src/test/java/test/dependent/issue141/SimpleSample.java create mode 100644 testng-core/src/test/java/test/dependent/issue141/SkipReasoner.java create mode 100644 testng-core/src/test/java/test/dependent/issue141/TestClassSample.java diff --git a/CHANGES.txt b/CHANGES.txt index 86593d9e75..1a05d13e87 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,8 +1,9 @@ Current Fixed: GITHUB-893: TestNG should provide an Api which allow to find all dependent of a specific test (Krishnan Mahadevan) New: Added .yml file extension for yaml suite files, previously only .yaml was allowed for yaml (Steven Jubb) +Fixed: GITHUB-141: regular expression in "dependsOnMethods" does not work (Krishnan Mahadevan) Fixed: GITHUB-2770: FileAlreadyExistsException when report is generated (melloware) -Fixed: GITHUB-2825: Programically Loading TestNG Suite from JAR File Fails to Delete Temporary Copy of Suite File (Steven Jubb) +Fixed: GITHUB-2825: Programmatically Loading TestNG Suite from JAR File Fails to Delete Temporary Copy of Suite File (Steven Jubb) Fixed: GITHUB-2818: Add configuration key for callback discrepancy behavior (Krishnan Mahadevan) Fixed: GITHUB-2819: Ability to retry a data provider in case of failures (Krishnan Mahadevan) Fixed: GITHUB-2308: StringIndexOutOfBoundsException in findClassesInPackage - Surefire/Maven - JDK 11 fails (Krishnan Mahadevan) diff --git a/testng-core/src/main/java/org/testng/DependencyMap.java b/testng-core/src/main/java/org/testng/DependencyMap.java index bb1039413c..b45669f021 100644 --- a/testng-core/src/main/java/org/testng/DependencyMap.java +++ b/testng-core/src/main/java/org/testng/DependencyMap.java @@ -1,5 +1,7 @@ package org.testng; +import java.util.Arrays; +import java.util.Collection; import java.util.List; import java.util.Optional; import java.util.Set; @@ -7,6 +9,7 @@ import java.util.stream.Collectors; import org.testng.collections.ListMultiMap; import org.testng.collections.Maps; +import org.testng.internal.MethodHelper; import org.testng.internal.RuntimeBehavior; /** Helper class to keep track of dependencies. */ @@ -54,6 +57,13 @@ public List getMethodsThatBelongTo(String group, ITestNGMethod fr public ITestNGMethod getMethodDependingOn(String methodName, ITestNGMethod fromMethod) { List l = m_dependencies.get(methodName); + if (l.isEmpty()) { + ITestNGMethod[] array = + m_dependencies.values().stream() + .flatMap(Collection::stream) + .toArray(ITestNGMethod[]::new); + l = Arrays.asList(MethodHelper.findDependedUponMethods(fromMethod, array)); + } if (l.isEmpty()) { // Try to fetch dependencies by using the test class in the method name. // This is usually needed in scenarios wherein a child class overrides a base class method. @@ -78,7 +88,11 @@ public ITestNGMethod getMethodDependingOn(String methodName, ITestNGMethod fromM } throw new TestNGException( - "Method \"" + fromMethod + "\" depends on nonexistent method \"" + methodName + "\""); + "Method \"" + + fromMethod.getQualifiedName() + + "()\" depends on nonexistent method \"" + + methodName + + "\""); } private static boolean belongToDifferentClassHierarchy( diff --git a/testng-core/src/main/java/org/testng/internal/MethodHelper.java b/testng-core/src/main/java/org/testng/internal/MethodHelper.java index 4986d314c8..d8812aa563 100644 --- a/testng-core/src/main/java/org/testng/internal/MethodHelper.java +++ b/testng-core/src/main/java/org/testng/internal/MethodHelper.java @@ -94,10 +94,18 @@ protected static ITestNGMethod[] findDependedUponMethods( * Finds TestNG methods that the specified TestNG method depends upon * * @param m TestNG method - * @param methods list of methods to search for depended upon methods + * @param incoming list of methods to search for depended upon methods * @return list of methods that match the criteria */ - public static ITestNGMethod[] findDependedUponMethods(ITestNGMethod m, ITestNGMethod[] methods) { + public static ITestNGMethod[] findDependedUponMethods(ITestNGMethod m, ITestNGMethod[] incoming) { + ITestNGMethod[] methods = + Arrays.stream(incoming) + .filter(each -> !each.equals(m)) + .filter(each -> Objects.isNull(each.getRealClass().getEnclosingClass())) + .toArray(ITestNGMethod[]::new); + if (methods.length == 0) { + return new ITestNGMethod[] {}; + } String canonicalMethodName = calculateMethodCanonicalName(m); @@ -163,6 +171,7 @@ private static Method findMethodByName(ITestNGMethod testngMethod, String regExp if (regExp == null) { return null; } + regExp = regExp.replace("\\$", "$"); int lastDot = regExp.lastIndexOf('.'); String className, methodName; if (lastDot == -1) { diff --git a/testng-core/src/test/java/test/dependent/DependentTest.java b/testng-core/src/test/java/test/dependent/DependentTest.java index cc0cc51e6c..4ee5fbf8c9 100644 --- a/testng-core/src/test/java/test/dependent/DependentTest.java +++ b/testng-core/src/test/java/test/dependent/DependentTest.java @@ -11,6 +11,7 @@ import org.testng.ITestListener; import org.testng.ITestResult; import org.testng.TestNG; +import org.testng.TestNGException; import org.testng.annotations.DataProvider; import org.testng.annotations.Test; import org.testng.xml.XmlSuite.ParallelMode; @@ -22,14 +23,111 @@ import test.dependent.github1380.GitHub1380Sample2; import test.dependent.github1380.GitHub1380Sample3; import test.dependent.github1380.GitHub1380Sample4; +import test.dependent.issue141.ErrorScenarioNestedSample; +import test.dependent.issue141.MultipleMatchesTestClassSample; +import test.dependent.issue141.NestedTestClassSample; +import test.dependent.issue141.NestedTestClassSample2; +import test.dependent.issue141.SimpleSample; +import test.dependent.issue141.SkipReasoner; +import test.dependent.issue141.TestClassSample; import test.dependent.issue2658.FailingClassSample; import test.dependent.issue2658.PassingClassSample; import test.dependent.issue893.DependencyTrackingListener; import test.dependent.issue893.MultiLevelDependenciesTestClassSample; -import test.dependent.issue893.TestClassSample; public class DependentTest extends SimpleBaseTest { + @Test(description = "GITHUB-141") + public void ensureDependsOnMethodsHonoursRegexPatternsAcrossClasses() { + TestNG testng = + create(test.dependent.issue141.ASample.class, test.dependent.issue141.BSample.class); + MethodNameCollector listener = new MethodNameCollector(); + testng.addListener(listener); + testng.run(); + assertThat(listener.getPassedNames()).containsExactly("b", "bb", "a"); + } + + @Test( + description = "GITHUB-141", + expectedExceptions = TestNGException.class, + expectedExceptionsMessageRegExp = + "\ntest.dependent.issue141.SimpleSample.testMethod\\(\\) " + + "depends on nonexistent method test.dependent.issue141.BSample.*") + public void ensureDependsOnMethodsHonoursRegexPatternsAcrossClassesErrorCondition() { + TestNG testng = create(SimpleSample.class, test.dependent.issue141.BSample.class); + MethodNameCollector listener = new MethodNameCollector(); + testng.addListener(listener); + testng.run(); + } + + @Test( + description = "GITHUB-141", + expectedExceptions = TestNGException.class, + expectedExceptionsMessageRegExp = + "\nMethod \"test.dependent.issue141.ErrorScenarioNestedSample.a\\(\\)\" " + + "depends on nonexistent " + + "method \"test.dependent.issue141.ErrorScenarioNestedSample\\$InnerTestClass" + + ".rambo.*") + public void ensureDependsOnMethodsHonoursRegexPatternsNestedClassesErrorCondition() { + TestNG testng = create(ErrorScenarioNestedSample.class); + MethodNameCollector listener = new MethodNameCollector(); + testng.addListener(listener); + testng.run(); + } + + @Test(description = "GITHUB-141") + public void ensureDependsOnMethodsHonoursRegexPatternsUniqueMatch() { + TestNG testng = create(TestClassSample.class); + MethodNameCollector listener = new MethodNameCollector(); + testng.addListener(listener); + testng.run(); + assertThat(listener.getPassedNames()).containsExactly("test_C6390323", "randomTest"); + } + + @Test(description = "GITHUB-141") + public void ensureDependsOnMethodsHonoursRegexPatternsDuplicateMatches() { + TestNG testng = create(MultipleMatchesTestClassSample.class); + MethodNameCollector listener = new MethodNameCollector(); + SkipReasoner reasoner = new SkipReasoner(); + testng.addListener(listener); + testng.addListener(reasoner); + testng.run(); + assertThat(listener.getPassedNames()).containsExactly("test_C6390324"); + assertThat(listener.getFailedNames()).containsExactly("test_C6390323"); + assertThat(listener.getSkippedNames()).containsExactly("randomTest"); + assertThat(reasoner.getUpstreamFailures()).containsExactly("test_C6390323"); + } + + @Test( + description = "GITHUB-141", + expectedExceptions = TestNGException.class, + expectedExceptionsMessageRegExp = + "\nMethod \"test.dependent.issue141.NestedTestClassSample\\$FirstSample.randomTest\\(\\)\" " + + "depends on nonexistent method .*") + public void ensureDependsOnMethodsHonoursRegexPatternsDuplicateMatchesNestedClasses() { + TestNG testng = create(NestedTestClassSample.class); + MethodNameCollector listener = new MethodNameCollector(); + SkipReasoner reasoner = new SkipReasoner(); + testng.addListener(listener); + testng.addListener(reasoner); + testng.run(); + } + + @Test( + description = "GITHUB-141", + expectedExceptions = TestNGException.class, + expectedExceptionsMessageRegExp = + "\nMethod \"test.dependent.issue141.NestedTestClassSample2.randomTest\\(\\)\" depends on " + + "nonexistent method .*") + public void ensureDependsOnMethodsHonourRegexPatternsNestedClasses() { + TestNG testng = create(NestedTestClassSample2.class); + MethodNameCollector listener = new MethodNameCollector(); + SkipReasoner reasoner = new SkipReasoner(); + testng.addListener(listener); + testng.addListener(reasoner); + testng.run(); + } + @Test public void simpleSkip() { TestNG testng = create(SampleDependent1.class); @@ -241,7 +339,7 @@ public void testDownstreamDependencyRetrieval( public Object[][] getTestData() { return new Object[][] { { - TestClassSample.class, + test.dependent.issue893.TestClassSample.class, "independentTest", new String[] {"anotherDependentTest", "dependentTest"} }, @@ -273,7 +371,11 @@ public void testUpstreamDependencyRetrieval( @DataProvider(name = "getUpstreamTestData") public Object[][] getUpstreamTestData() { return new Object[][] { - {TestClassSample.class, "dependentTest", new String[] {"independentTest"}}, + { + test.dependent.issue893.TestClassSample.class, + "dependentTest", + new String[] {"independentTest"} + }, {MultiLevelDependenciesTestClassSample.class, "father", new String[] {"grandFather"}}, {MultiLevelDependenciesTestClassSample.class, "child", new String[] {"father", "mother"}}, {MultiLevelDependenciesTestClassSample.class, "grandFather", new String[] {}} diff --git a/testng-core/src/test/java/test/dependent/issue141/ASample.java b/testng-core/src/test/java/test/dependent/issue141/ASample.java new file mode 100644 index 0000000000..f08b5fd467 --- /dev/null +++ b/testng-core/src/test/java/test/dependent/issue141/ASample.java @@ -0,0 +1,8 @@ +package test.dependent.issue141; + +import org.testng.annotations.Test; + +public class ASample { + @Test(dependsOnMethods = "test.dependent.issue141.BSample.b*") + public void a() {} +} diff --git a/testng-core/src/test/java/test/dependent/issue141/BSample.java b/testng-core/src/test/java/test/dependent/issue141/BSample.java new file mode 100644 index 0000000000..b040c1b080 --- /dev/null +++ b/testng-core/src/test/java/test/dependent/issue141/BSample.java @@ -0,0 +1,11 @@ +package test.dependent.issue141; + +import org.testng.annotations.Test; + +public class BSample { + @Test + public void b() {} + + @Test + public void bb() {} +} diff --git a/testng-core/src/test/java/test/dependent/issue141/ErrorScenarioNestedSample.java b/testng-core/src/test/java/test/dependent/issue141/ErrorScenarioNestedSample.java new file mode 100644 index 0000000000..35814502f5 --- /dev/null +++ b/testng-core/src/test/java/test/dependent/issue141/ErrorScenarioNestedSample.java @@ -0,0 +1,15 @@ +package test.dependent.issue141; + +import org.testng.annotations.Test; + +public class ErrorScenarioNestedSample { + + @Test( + dependsOnMethods = "test.dependent.issue141.ErrorScenarioNestedSample$InnerTestClass.rambo*") + public void a() {} + + public static class InnerTestClass { + @Test + public void b() {} + } +} diff --git a/testng-core/src/test/java/test/dependent/issue141/MultipleMatchesTestClassSample.java b/testng-core/src/test/java/test/dependent/issue141/MultipleMatchesTestClassSample.java new file mode 100644 index 0000000000..636393fe34 --- /dev/null +++ b/testng-core/src/test/java/test/dependent/issue141/MultipleMatchesTestClassSample.java @@ -0,0 +1,18 @@ +package test.dependent.issue141; + +import org.testng.Assert; +import org.testng.annotations.Test; + +public class MultipleMatchesTestClassSample { + + @Test(dependsOnMethods = "test_C[0-9]{7}") + public void randomTest() {} + + @Test + public void test_C6390323() { + Assert.fail(); + } + + @Test + public void test_C6390324() {} +} diff --git a/testng-core/src/test/java/test/dependent/issue141/NestedTestClassSample.java b/testng-core/src/test/java/test/dependent/issue141/NestedTestClassSample.java new file mode 100644 index 0000000000..d361dce1f4 --- /dev/null +++ b/testng-core/src/test/java/test/dependent/issue141/NestedTestClassSample.java @@ -0,0 +1,25 @@ +package test.dependent.issue141; + +import org.testng.Assert; +import org.testng.annotations.Test; + +public class NestedTestClassSample { + + public static class FirstSample { + @Test(dependsOnMethods = "test_C[0-9]{7}") + public void randomTest() {} + + @Test + public void test_C6390323() {} + } + + public static class SecondSample { + @Test(dependsOnMethods = "test_C[0-9]{7}") + public void randomTest() {} + + @Test + public void test_C6390323() { + Assert.fail(); + } + } +} diff --git a/testng-core/src/test/java/test/dependent/issue141/NestedTestClassSample2.java b/testng-core/src/test/java/test/dependent/issue141/NestedTestClassSample2.java new file mode 100644 index 0000000000..3a68bade0a --- /dev/null +++ b/testng-core/src/test/java/test/dependent/issue141/NestedTestClassSample2.java @@ -0,0 +1,15 @@ +package test.dependent.issue141; + +import org.testng.annotations.Test; + +public class NestedTestClassSample2 { + + @Test(dependsOnMethods = "test_C[0-9]{7}") + public void randomTest() {} + + public static class InnerClass { + + @Test + public void test_C6390323() {} + } +} diff --git a/testng-core/src/test/java/test/dependent/issue141/SimpleSample.java b/testng-core/src/test/java/test/dependent/issue141/SimpleSample.java new file mode 100644 index 0000000000..fa7895904a --- /dev/null +++ b/testng-core/src/test/java/test/dependent/issue141/SimpleSample.java @@ -0,0 +1,9 @@ +package test.dependent.issue141; + +import org.testng.annotations.Test; + +public class SimpleSample { + + @Test(dependsOnMethods = "test.dependent.issue141.BSample.xx*") + public void testMethod() {} +} diff --git a/testng-core/src/test/java/test/dependent/issue141/SkipReasoner.java b/testng-core/src/test/java/test/dependent/issue141/SkipReasoner.java new file mode 100644 index 0000000000..90153fc7d9 --- /dev/null +++ b/testng-core/src/test/java/test/dependent/issue141/SkipReasoner.java @@ -0,0 +1,24 @@ +package test.dependent.issue141; + +import java.util.List; +import java.util.stream.Collectors; +import org.testng.ITestListener; +import org.testng.ITestNGMethod; +import org.testng.ITestResult; + +public class SkipReasoner implements ITestListener { + + private List upstreamFailures; + + @Override + public void onTestSkipped(ITestResult result) { + upstreamFailures = + result.getSkipCausedBy().stream() + .map(ITestNGMethod::getMethodName) + .collect(Collectors.toList()); + } + + public List getUpstreamFailures() { + return upstreamFailures; + } +} diff --git a/testng-core/src/test/java/test/dependent/issue141/TestClassSample.java b/testng-core/src/test/java/test/dependent/issue141/TestClassSample.java new file mode 100644 index 0000000000..02adeb5a4e --- /dev/null +++ b/testng-core/src/test/java/test/dependent/issue141/TestClassSample.java @@ -0,0 +1,12 @@ +package test.dependent.issue141; + +import org.testng.annotations.Test; + +public class TestClassSample { + + @Test(dependsOnMethods = "test_C[0-9]{7}") + public void randomTest() {} + + @Test + public void test_C6390323() {} +} diff --git a/testng-runner-api/src/main/java/org/testng/internal/TestResult.java b/testng-runner-api/src/main/java/org/testng/internal/TestResult.java index 99ed0d4580..e48352d8c0 100644 --- a/testng-runner-api/src/main/java/org/testng/internal/TestResult.java +++ b/testng-runner-api/src/main/java/org/testng/internal/TestResult.java @@ -7,6 +7,7 @@ import java.util.List; import java.util.Set; import java.util.UUID; +import java.util.regex.Pattern; import java.util.stream.Collectors; import javax.annotation.Nonnull; import org.testng.IAttributes; @@ -427,11 +428,24 @@ public List getSkipCausedBy() { skippedDueTo = allfailures.stream() .map(ITestResult::getMethod) - .filter(method -> upstreamMethods.contains(method.getQualifiedName())) + .filter(method -> matches(upstreamMethods, method)) .collect(Collectors.toList()); return Collections.unmodifiableList(skippedDueTo); } + private static boolean matches(List upstreamMethods, ITestNGMethod method) { + if (upstreamMethods.contains(method.getQualifiedName()) + || upstreamMethods.contains(method.getMethodName())) { + return true; + } + return upstreamMethods.stream() + .map(Pattern::compile) + .anyMatch( + each -> + each.matcher(method.getQualifiedName()).matches() + || each.matcher(method.getMethodName()).matches()); + } + public String id() { return id; }