Skip to content

Commit

Permalink
Honour regex in dependsOnMethods
Browse files Browse the repository at this point in the history
Closes: #141
  • Loading branch information
krmahadevan committed Dec 2, 2022
1 parent 563bd6d commit a5b8508
Show file tree
Hide file tree
Showing 14 changed files with 285 additions and 8 deletions.
3 changes: 2 additions & 1 deletion 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)
Expand Down
16 changes: 15 additions & 1 deletion testng-core/src/main/java/org/testng/DependencyMap.java
@@ -1,12 +1,15 @@
package org.testng;

import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.regex.Pattern;
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. */
Expand Down Expand Up @@ -54,6 +57,13 @@ public List<ITestNGMethod> getMethodsThatBelongTo(String group, ITestNGMethod fr

public ITestNGMethod getMethodDependingOn(String methodName, ITestNGMethod fromMethod) {
List<ITestNGMethod> 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.
Expand All @@ -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(
Expand Down
13 changes: 11 additions & 2 deletions testng-core/src/main/java/org/testng/internal/MethodHelper.java
Expand Up @@ -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);

Expand Down Expand Up @@ -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) {
Expand Down
108 changes: 105 additions & 3 deletions testng-core/src/test/java/test/dependent/DependentTest.java
Expand Up @@ -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;
Expand All @@ -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);
Expand Down Expand Up @@ -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"}
},
Expand Down Expand Up @@ -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[] {}}
Expand Down
@@ -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() {}
}
11 changes: 11 additions & 0 deletions 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() {}
}
@@ -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() {}
}
}
@@ -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() {}
}
@@ -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();
}
}
}
@@ -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() {}
}
}
@@ -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() {}
}
@@ -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<String> upstreamFailures;

@Override
public void onTestSkipped(ITestResult result) {
upstreamFailures =
result.getSkipCausedBy().stream()
.map(ITestNGMethod::getMethodName)
.collect(Collectors.toList());
}

public List<String> getUpstreamFailures() {
return upstreamFailures;
}
}
@@ -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() {}
}

0 comments on commit a5b8508

Please sign in to comment.