Skip to content

Commit f9a288c

Browse files
committed
Support getting dependencies info for a test
Closes #893
1 parent 39f8fa5 commit f9a288c

File tree

10 files changed

+227
-5
lines changed

10 files changed

+227
-5
lines changed

CHANGES.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
Current
2+
Fixed: GITHUB-893: TestNG should provide an Api which allow to find all dependent of a specific test (Krishnan Mahadevan)
23
New: Added .yml file extension for yaml suite files, previously only .yaml was allowed for yaml (Steven Jubb)
34
Fixed: GITHUB-2770: FileAlreadyExistsException when report is generated (melloware)
45
Fixed: GITHUB-2825: Programically Loading TestNG Suite from JAR File Fails to Delete Temporary Copy of Suite File (Steven Jubb)

testng-core-api/src/main/java/org/testng/IDynamicGraph.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package org.testng;
22

33
import java.util.Collection;
4+
import java.util.Collections;
45
import java.util.List;
56
import java.util.Set;
67

@@ -21,6 +22,10 @@ public interface IDynamicGraph<T> {
2122

2223
List<T> getFreeNodes();
2324

25+
default List<T> getUpstreamDependenciesFor(T node) {
26+
return Collections.emptyList();
27+
}
28+
2429
List<T> getDependenciesFor(T node);
2530

2631
void setStatus(Collection<T> nodes, Status status);

testng-core-api/src/main/java/org/testng/ITestNGMethod.java

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
package org.testng;
22

3+
import java.util.Collections;
34
import java.util.List;
45
import java.util.Map;
6+
import java.util.Set;
57
import java.util.concurrent.Callable;
68
import org.testng.annotations.CustomAttribute;
79
import org.testng.internal.ConstructorOrMethod;
@@ -71,6 +73,24 @@ public interface ITestNGMethod extends Cloneable {
7173
*/
7274
String[] getMethodsDependedUpon();
7375

76+
/**
77+
* @return - The set of methods that are dependent on the current method. This information can
78+
* help in deciding what other TestNG methods will be skipped if the current method fails. If
79+
* the current method is a configuration method, then an empty set is returned.
80+
*/
81+
default Set<ITestNGMethod> downstreamDependencies() {
82+
return Collections.emptySet();
83+
}
84+
85+
/**
86+
* @return - The set of methods upon which the current method has a dependency. This information
87+
* can help in deciding what all TestNG methods need to pass before the current method can be
88+
* executed. If the current method is a configuration method, then an empty set is returned.
89+
*/
90+
default Set<ITestNGMethod> upstreamDependencies() {
91+
return Collections.emptySet();
92+
}
93+
7494
void addMethodDependedUpon(String methodName);
7595

7696
/** @return true if this method was annotated with @Test */

testng-core/src/main/java/org/testng/TestRunner.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import org.testng.collections.Maps;
2525
import org.testng.collections.Sets;
2626
import org.testng.internal.Attributes;
27+
import org.testng.internal.BaseTestMethod;
2728
import org.testng.internal.ClassBasedWrapper;
2829
import org.testng.internal.ClassInfoMap;
2930
import org.testng.internal.ConfigurationGroupMethods;
@@ -746,6 +747,17 @@ private void privateRun(XmlTest xmlTest) {
746747
});
747748
IDynamicGraph<ITestNGMethod> graph = reference.get();
748749

750+
for (ITestNGMethod each : interceptedOrder) {
751+
if (each instanceof BaseTestMethod) {
752+
// We don't want our users to change this vital info. That is why the setter is NOT
753+
// being exposed via the interface, and so we resort to an "instanceof" check.
754+
Set<ITestNGMethod> downstream = Sets.newHashSet(graph.getDependenciesFor(each));
755+
((BaseTestMethod) each).setDownstreamDependencies(downstream);
756+
Set<ITestNGMethod> upstream = Sets.newHashSet(graph.getUpstreamDependenciesFor(each));
757+
((BaseTestMethod) each).setUpstreamDependencies(upstream);
758+
}
759+
}
760+
749761
graph.setVisualisers(this.visualisers);
750762
// In some cases, additional sorting is needed to make sure tests run in the appropriate order.
751763
// If the user specified a method interceptor, or if we have any methods that have a non-default

testng-core/src/main/java/org/testng/internal/BaseTestMethod.java

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,8 @@ public abstract class BaseTestMethod implements ITestNGMethod, IInvocationStatus
7070
private long m_invocationTimeOut = 0L;
7171

7272
private List<Integer> m_invocationNumbers = Lists.newArrayList();
73+
private Set<ITestNGMethod> downstreamDependencies = Sets.newHashSet();
74+
private Set<ITestNGMethod> upstreamDependencies = Sets.newHashSet();
7375
private final Collection<Integer> m_failedInvocationNumbers = new ConcurrentLinkedQueue<>();
7476
private long m_timeOut = 0;
7577

@@ -176,6 +178,34 @@ public String[] getMethodsDependedUpon() {
176178
return m_methodsDependedUpon;
177179
}
178180

181+
@Override
182+
public Set<ITestNGMethod> downstreamDependencies() {
183+
return Collections.unmodifiableSet(downstreamDependencies);
184+
}
185+
186+
@Override
187+
public Set<ITestNGMethod> upstreamDependencies() {
188+
return Collections.unmodifiableSet(upstreamDependencies);
189+
}
190+
191+
public void setDownstreamDependencies(Set<ITestNGMethod> methods) {
192+
if (RuntimeBehavior.isMemoryFriendlyMode()) {
193+
downstreamDependencies =
194+
methods.stream().map(LiteWeightTestNGMethod::new).collect(Collectors.toSet());
195+
} else {
196+
downstreamDependencies.addAll(methods);
197+
}
198+
}
199+
200+
public void setUpstreamDependencies(Set<ITestNGMethod> methods) {
201+
if (RuntimeBehavior.isMemoryFriendlyMode()) {
202+
upstreamDependencies =
203+
methods.stream().map(LiteWeightTestNGMethod::new).collect(Collectors.toSet());
204+
} else {
205+
upstreamDependencies.addAll(methods);
206+
}
207+
}
208+
179209
/** {@inheritDoc} */
180210
@Override
181211
public boolean isTest() {

testng-core/src/main/java/org/testng/internal/DynamicGraph.java

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import java.util.Collections;
55
import java.util.List;
66
import java.util.Map;
7+
import java.util.Optional;
78
import java.util.Set;
89
import java.util.concurrent.ConcurrentHashMap;
910
import org.testng.IDynamicGraph;
@@ -76,12 +77,19 @@ public List<T> getFreeNodes() {
7677
return finalResult;
7778
}
7879

80+
@Override
81+
public List<T> getUpstreamDependenciesFor(T node) {
82+
return dependencies(m_edges.from(node));
83+
}
84+
7985
public List<T> getDependenciesFor(T node) {
80-
Map<T, Integer> data = m_edges.to(node);
81-
if (data == null) {
82-
return Lists.newArrayList();
83-
}
84-
return Lists.newArrayList(data.keySet());
86+
return dependencies(m_edges.to(node));
87+
}
88+
89+
private List<T> dependencies(Map<T, Integer> dependencies) {
90+
return Optional.ofNullable(dependencies)
91+
.map(found -> Lists.newArrayList(found.keySet()))
92+
.orElse(Lists.newArrayList());
8593
}
8694

8795
/** Set the status for a set of nodes. */

testng-core/src/test/java/test/dependent/DependentTest.java

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import java.util.ArrayList;
66
import java.util.Arrays;
77
import java.util.List;
8+
import java.util.Set;
89
import java.util.function.Function;
910
import org.testng.Assert;
1011
import org.testng.ITestListener;
@@ -23,6 +24,9 @@
2324
import test.dependent.github1380.GitHub1380Sample4;
2425
import test.dependent.issue2658.FailingClassSample;
2526
import test.dependent.issue2658.PassingClassSample;
27+
import test.dependent.issue893.DependencyTrackingListener;
28+
import test.dependent.issue893.MultiLevelDependenciesTestClassSample;
29+
import test.dependent.issue893.TestClassSample;
2630

2731
public class DependentTest extends SimpleBaseTest {
2832

@@ -218,6 +222,72 @@ public void testMethodDependencyAmidstInheritance() {
218222
assertThat(listener.getSkippedMethodNames()).containsExactly("failingMethod");
219223
}
220224

225+
@Test(description = "GITHUB-893", dataProvider = "getTestData")
226+
public void testDownstreamDependencyRetrieval(
227+
Class<?> clazz, String independentMethod, String[] dependentMethods) {
228+
TestNG testng = create(clazz);
229+
DependencyTrackingListener listener = new DependencyTrackingListener();
230+
testng.addListener(listener);
231+
testng.run();
232+
String cls = clazz.getCanonicalName();
233+
String key = cls + "." + independentMethod;
234+
Set<String> downstream = listener.getDownstreamDependencies().get(key);
235+
if (dependentMethods == null) {
236+
assertThat(downstream).isEmpty();
237+
} else {
238+
dependentMethods =
239+
Arrays.stream(dependentMethods).map(each -> cls + "." + each).toArray(String[]::new);
240+
assertThat(downstream).containsExactly(dependentMethods);
241+
}
242+
}
243+
244+
@DataProvider(name = "getTestData")
245+
public Object[][] getTestData() {
246+
return new Object[][] {
247+
{
248+
TestClassSample.class,
249+
"independentTest",
250+
new String[] {"anotherDependentTest", "dependentTest"}
251+
},
252+
{MultiLevelDependenciesTestClassSample.class, "father", new String[] {"child"}},
253+
{
254+
MultiLevelDependenciesTestClassSample.class,
255+
"grandFather",
256+
new String[] {"father", "mother"}
257+
},
258+
{MultiLevelDependenciesTestClassSample.class, "child", null}
259+
};
260+
}
261+
262+
@Test(description = "GITHUB-893", dataProvider = "getUpstreamTestData")
263+
public void testUpstreamDependencyRetrieval(
264+
Class<?> clazz, String independentMethod, String[] dependentMethods) {
265+
TestNG testng = create(clazz);
266+
DependencyTrackingListener listener = new DependencyTrackingListener();
267+
testng.addListener(listener);
268+
testng.run();
269+
String cls = clazz.getCanonicalName();
270+
String key = cls + "." + independentMethod;
271+
Set<String> upstream = listener.getUpstreamDependencies().get(key);
272+
if (dependentMethods == null) {
273+
assertThat(upstream).isEmpty();
274+
} else {
275+
dependentMethods =
276+
Arrays.stream(dependentMethods).map(each -> cls + "." + each).toArray(String[]::new);
277+
assertThat(upstream).containsExactly(dependentMethods);
278+
}
279+
}
280+
281+
@DataProvider(name = "getUpstreamTestData")
282+
public Object[][] getUpstreamTestData() {
283+
return new Object[][] {
284+
{TestClassSample.class, "dependentTest", new String[] {"independentTest"}},
285+
{MultiLevelDependenciesTestClassSample.class, "father", new String[] {"grandFather"}},
286+
{MultiLevelDependenciesTestClassSample.class, "child", new String[] {"father", "mother"}},
287+
{MultiLevelDependenciesTestClassSample.class, "grandFather", null}
288+
};
289+
}
290+
221291
public static class MethodNameCollector implements ITestListener {
222292

223293
private static final Function<ITestResult, String> asString =
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
package test.dependent.issue893;
2+
3+
import java.util.HashMap;
4+
import java.util.Map;
5+
import java.util.Set;
6+
import java.util.stream.Collectors;
7+
import org.testng.ITestContext;
8+
import org.testng.ITestListener;
9+
import org.testng.ITestNGMethod;
10+
import org.testng.ITestResult;
11+
import org.testng.collections.Sets;
12+
13+
public class DependencyTrackingListener implements ITestListener {
14+
private final Map<String, Set<String>> downstreamDependencies = new HashMap<>();
15+
private final Map<String, Set<String>> upstreamDependencies = new HashMap<>();
16+
17+
@Override
18+
public void onTestStart(ITestResult result) {
19+
ITestContext context = result.getTestContext();
20+
for (ITestNGMethod method : context.getAllTestMethods()) {
21+
String key = method.getQualifiedName();
22+
downstreamDependencies
23+
.computeIfAbsent(key, k -> Sets.newHashSet())
24+
.addAll(
25+
method.downstreamDependencies().stream()
26+
.map(ITestNGMethod::getQualifiedName)
27+
.collect(Collectors.toList()));
28+
upstreamDependencies
29+
.computeIfAbsent(key, k -> Sets.newHashSet())
30+
.addAll(
31+
method.upstreamDependencies().stream()
32+
.map(ITestNGMethod::getQualifiedName)
33+
.collect(Collectors.toList()));
34+
}
35+
}
36+
37+
public Map<String, Set<String>> getUpstreamDependencies() {
38+
return upstreamDependencies;
39+
}
40+
41+
public Map<String, Set<String>> getDownstreamDependencies() {
42+
return downstreamDependencies;
43+
}
44+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package test.dependent.issue893;
2+
3+
import org.testng.annotations.Test;
4+
5+
public class MultiLevelDependenciesTestClassSample {
6+
@Test
7+
public void grandFather() {}
8+
9+
@Test(dependsOnMethods = "grandFather")
10+
public void father() {}
11+
12+
@Test(dependsOnMethods = "grandFather")
13+
public void mother() {}
14+
15+
@Test(dependsOnMethods = {"father", "mother"})
16+
public void child() {}
17+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package test.dependent.issue893;
2+
3+
import org.testng.annotations.Test;
4+
5+
public class TestClassSample {
6+
7+
@Test
8+
public void independentTest() {}
9+
10+
@Test(dependsOnMethods = "independentTest")
11+
public void dependentTest() {}
12+
13+
@Test(dependsOnMethods = "independentTest")
14+
public void anotherDependentTest() {}
15+
}

0 commit comments

Comments
 (0)