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

Streamline dependsOnMethods for configurations #2845

Merged
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
1 change: 1 addition & 0 deletions CHANGES.txt
@@ -1,5 +1,6 @@
Current
Fixed: GITHUB-2844: Deprecate support for running Spock Tests (Krishnan Mahadevan)
Fixed: GITHUB-550: Weird @BeforeMethod and @AfterMethod behaviour with dependsOnMethods (Krishnan Mahadevan)
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)
Expand Down
Expand Up @@ -10,6 +10,7 @@
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Predicate;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.Stream;
Expand Down Expand Up @@ -90,6 +91,40 @@ protected static ITestNGMethod[] findDependedUponMethods(
return findDependedUponMethods(m, methodsArray);
}

private static Pair<String, Predicate<ITestNGMethod>> filterToUse(ITestNGMethod m) {
if (m.isBeforeMethodConfiguration()) {
return new Pair<>("BeforeMethod", ITestNGMethod::isBeforeMethodConfiguration);
}
if (m.isAfterMethodConfiguration()) {
return new Pair<>("AfterMethod", ITestNGMethod::isAfterMethodConfiguration);
}
if (m.isBeforeClassConfiguration()) {
return new Pair<>("BeforeClass", ITestNGMethod::isBeforeClassConfiguration);
}
if (m.isAfterClassConfiguration()) {
return new Pair<>("AfterClass", ITestNGMethod::isAfterClassConfiguration);
}
if (m.isBeforeTestConfiguration()) {
return new Pair<>("BeforeTest", ITestNGMethod::isBeforeTestConfiguration);
}
if (m.isAfterTestConfiguration()) {
return new Pair<>("AfterTest", ITestNGMethod::isAfterTestConfiguration);
}
if (m.isBeforeSuiteConfiguration()) {
return new Pair<>("BeforeSuite", ITestNGMethod::isBeforeSuiteConfiguration);
}
if (m.isAfterSuiteConfiguration()) {
return new Pair<>("AfterSuite", ITestNGMethod::isAfterSuiteConfiguration);
}
if (m.isBeforeGroupsConfiguration()) {
return new Pair<>("BeforeGroups", ITestNGMethod::isBeforeGroupsConfiguration);
}
if (m.isAfterGroupsConfiguration()) {
return new Pair<>("AfterGroups", ITestNGMethod::isAfterGroupsConfiguration);
}
return new Pair<>("Test", ITestNGMethod::isTest);
}

/**
* Finds TestNG methods that the specified TestNG method depends upon
*
Expand All @@ -104,6 +139,25 @@ public static ITestNGMethod[] findDependedUponMethods(ITestNGMethod m, ITestNGMe
.filter(each -> Objects.isNull(each.getRealClass().getEnclosingClass()))
.toArray(ITestNGMethod[]::new);
String canonicalMethodName = calculateMethodCanonicalName(m);
Pair<String, Predicate<ITestNGMethod>> filterPair = filterToUse(m);
String annotationType = filterPair.first();
Predicate<ITestNGMethod> predicate = filterPair.second();

if (isConfigurationMethod(m)) {
methods =
Arrays.stream(incoming)
.filter(tm -> !tm.equals(m)) // exclude the current config method from the list
.filter(predicate) // include only similar config methods
.toArray(ITestNGMethod[]::new);

if (methods.length == 0) {
String msg =
String.format(
"None of the dependencies of the method %s are annotated with [@%s].",
canonicalMethodName, annotationType);
throw new TestNGException(msg);
}
}

List<ITestNGMethod> vResult = Lists.newArrayList();
String regexp = null;
Expand Down Expand Up @@ -142,11 +196,17 @@ public static ITestNGMethod[] findDependedUponMethods(ITestNGMethod m, ITestNGMe
}
Method maybeReferringTo = findMethodByName(m, regexp);
if (maybeReferringTo != null) {
String suffix = " or not included.";
if (isConfigurationMethod(m)) {
suffix = ".";
}
throw new TestNGException(
canonicalMethodName
+ "() is depending on method "
+ maybeReferringTo
+ ", which is not annotated with @Test or not included.");
+ ", which is not annotated with @"
+ annotationType
+ suffix);
}
throw new TestNGException(
canonicalMethodName + "() depends on nonexistent method " + regexp);
Expand Down
61 changes: 61 additions & 0 deletions testng-core/src/test/java/test/dependent/DependentTest.java
Expand Up @@ -32,6 +32,11 @@
import test.dependent.issue141.TestClassSample;
import test.dependent.issue2658.FailingClassSample;
import test.dependent.issue2658.PassingClassSample;
import test.dependent.issue550.ConfigDependencySample;
import test.dependent.issue550.ConfigDependencyWithMismatchedLevelSample;
import test.dependent.issue550.ConfigDependsOnTestAndConfigMethodSample;
import test.dependent.issue550.ConfigDependsOnTestMethodSample;
import test.dependent.issue550.OrderedResultsGatherer;
import test.dependent.issue893.DependencyTrackingListener;
import test.dependent.issue893.MultiLevelDependenciesTestClassSample;

Expand Down Expand Up @@ -378,6 +383,62 @@ public Object[][] getUpstreamTestData() {
};
}

@Test(description = "GITHUB-550", dataProvider = "configDependencyTestData")
public void testConfigDependencies(String expectedErrorMsg, Class<?> testClassToUse) {
TestNG testng = create(testClassToUse);
String actualErrorMsg = null;
try {
testng.run();
} catch (TestNGException e) {
actualErrorMsg = e.getMessage().replace("\n", "");
}
assertThat(actualErrorMsg).isEqualTo(expectedErrorMsg);
}

@Test(description = "GITHUB-550")
public void testConfigDependenciesHappyCase() {
TestNG testng = create(ConfigDependencySample.class);
OrderedResultsGatherer gatherer = new OrderedResultsGatherer();
testng.addListener(gatherer);
testng.run();
assertThat(gatherer.getStartTimes()).isSorted();
}

@DataProvider(name = "configDependencyTestData")
public Object[][] configDependencyTestData() {
String template1 = "None of the dependencies of the method %s.%s are annotated with [@%s].";
String template2 =
"%s.%s() is depending on method public void %s.%s(), " + "which is not annotated with @%s.";
return new Object[][] {
{
String.format(
template1,
ConfigDependencyWithMismatchedLevelSample.class.getCanonicalName(),
"beforeMethod",
"BeforeMethod"),
ConfigDependencyWithMismatchedLevelSample.class
},
{
String.format(
template2,
ConfigDependsOnTestAndConfigMethodSample.class.getCanonicalName(),
"beforeMethod",
ConfigDependsOnTestAndConfigMethodSample.class.getCanonicalName(),
"testMethod",
"BeforeMethod"),
ConfigDependsOnTestAndConfigMethodSample.class
},
{
String.format(
template1,
ConfigDependsOnTestMethodSample.class.getCanonicalName(),
"beforeMethod",
"BeforeMethod"),
ConfigDependsOnTestMethodSample.class
}
};
}

public static class MethodNameCollector implements ITestListener {

private static final Function<ITestResult, String> asString =
Expand Down
@@ -0,0 +1,19 @@
package test.dependent.issue550;

import java.util.concurrent.TimeUnit;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;

public class ConfigDependencySample {

@BeforeMethod(dependsOnMethods = "anotherBeforeMethod")
public void beforeMethod() {}

@BeforeMethod
public void anotherBeforeMethod() throws InterruptedException {
TimeUnit.MILLISECONDS.sleep(100);
}

@Test
public void testMethod() {}
}
@@ -0,0 +1,17 @@
package test.dependent.issue550;

import org.testng.annotations.BeforeClass;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;

public class ConfigDependencyWithMismatchedLevelSample {

@BeforeClass
public void beforeClass() {}

@BeforeMethod(dependsOnMethods = "beforeClass")
public void beforeMethod() {}

@Test
public void testMethod() {}
}
@@ -0,0 +1,23 @@
package test.dependent.issue550;

import org.testng.annotations.BeforeClass;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;

public class ConfigDependsOnTestAndConfigMethodSample {

@BeforeClass
public void beforeClass() {}

@BeforeClass
public void anotherBeforeClass() {}

@BeforeMethod(dependsOnMethods = {"testMethod", "anotherBeforeMethod"})
public void beforeMethod() {}

@BeforeMethod
public void anotherBeforeMethod() {}

@Test
public void testMethod() {}
}
@@ -0,0 +1,13 @@
package test.dependent.issue550;

import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;

public class ConfigDependsOnTestMethodSample {

@BeforeMethod(dependsOnMethods = "testMethod")
public void beforeMethod() {}

@Test
public void testMethod() {}
}
@@ -0,0 +1,21 @@
package test.dependent.issue550;

import java.util.ArrayList;
import java.util.List;
import org.testng.IInvokedMethod;
import org.testng.IInvokedMethodListener;
import org.testng.ITestResult;

public class OrderedResultsGatherer implements IInvokedMethodListener {

List<Long> startTimes = new ArrayList<>();

public List<Long> getStartTimes() {
return startTimes;
}

@Override
public void afterInvocation(IInvokedMethod method, ITestResult testResult) {
startTimes.add(testResult.getStartMillis());
}
}
@@ -0,0 +1,13 @@
package test.dependent.issue550;

import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;

public class TestClassSample {

@Test(dependsOnMethods = "a")
public void b() {}

@BeforeMethod
public void a() {}
}