Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Honour regex in dependsOnMethods #2838

Merged
merged 1 commit into from Dec 2, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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() {}
}