Skip to content

Commit 563bd6d

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

File tree

10 files changed

+221
-5
lines changed

10 files changed

+221
-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: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,10 @@ public interface IDynamicGraph<T> {
2121

2222
List<T> getFreeNodes();
2323

24+
default List<T> getUpstreamDependenciesFor(T node) {
25+
throw new UnsupportedOperationException("Pending implementation");
26+
}
27+
2428
List<T> getDependenciesFor(T node);
2529

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

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

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import java.util.List;
44
import java.util.Map;
5+
import java.util.Set;
56
import java.util.concurrent.Callable;
67
import org.testng.annotations.CustomAttribute;
78
import org.testng.internal.ConstructorOrMethod;
@@ -71,6 +72,24 @@ public interface ITestNGMethod extends Cloneable {
7172
*/
7273
String[] getMethodsDependedUpon();
7374

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

7695
/** @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: 34 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 final Set<ITestNGMethod> downstreamDependencies = Sets.newHashSet();
74+
private final Set<ITestNGMethod> upstreamDependencies = Sets.newHashSet();
7375
private final Collection<Integer> m_failedInvocationNumbers = new ConcurrentLinkedQueue<>();
7476
private long m_timeOut = 0;
7577

@@ -176,6 +178,38 @@ 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 (!downstreamDependencies.isEmpty()) {
193+
downstreamDependencies.clear();
194+
}
195+
Set<ITestNGMethod> toAdd = methods;
196+
if (RuntimeBehavior.isMemoryFriendlyMode()) {
197+
toAdd = methods.stream().map(LiteWeightTestNGMethod::new).collect(Collectors.toSet());
198+
}
199+
downstreamDependencies.addAll(toAdd);
200+
}
201+
202+
public void setUpstreamDependencies(Set<ITestNGMethod> methods) {
203+
if (!upstreamDependencies.isEmpty()) {
204+
upstreamDependencies.clear();
205+
}
206+
Set<ITestNGMethod> toAdd = methods;
207+
if (RuntimeBehavior.isMemoryFriendlyMode()) {
208+
toAdd = methods.stream().map(LiteWeightTestNGMethod::new).collect(Collectors.toSet());
209+
}
210+
upstreamDependencies.addAll(toAdd);
211+
}
212+
179213
/** {@inheritDoc} */
180214
@Override
181215
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: 62 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,64 @@ 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+
dependentMethods =
236+
Arrays.stream(dependentMethods).map(each -> cls + "." + each).toArray(String[]::new);
237+
assertThat(downstream).containsExactly(dependentMethods);
238+
}
239+
240+
@DataProvider(name = "getTestData")
241+
public Object[][] getTestData() {
242+
return new Object[][] {
243+
{
244+
TestClassSample.class,
245+
"independentTest",
246+
new String[] {"anotherDependentTest", "dependentTest"}
247+
},
248+
{MultiLevelDependenciesTestClassSample.class, "father", new String[] {"child"}},
249+
{
250+
MultiLevelDependenciesTestClassSample.class,
251+
"grandFather",
252+
new String[] {"father", "mother"}
253+
},
254+
{MultiLevelDependenciesTestClassSample.class, "child", new String[] {}}
255+
};
256+
}
257+
258+
@Test(description = "GITHUB-893", dataProvider = "getUpstreamTestData")
259+
public void testUpstreamDependencyRetrieval(
260+
Class<?> clazz, String independentMethod, String[] dependentMethods) {
261+
TestNG testng = create(clazz);
262+
DependencyTrackingListener listener = new DependencyTrackingListener();
263+
testng.addListener(listener);
264+
testng.run();
265+
String cls = clazz.getCanonicalName();
266+
String key = cls + "." + independentMethod;
267+
Set<String> upstream = listener.getUpstreamDependencies().get(key);
268+
dependentMethods =
269+
Arrays.stream(dependentMethods).map(each -> cls + "." + each).toArray(String[]::new);
270+
assertThat(upstream).containsExactly(dependentMethods);
271+
}
272+
273+
@DataProvider(name = "getUpstreamTestData")
274+
public Object[][] getUpstreamTestData() {
275+
return new Object[][] {
276+
{TestClassSample.class, "dependentTest", new String[] {"independentTest"}},
277+
{MultiLevelDependenciesTestClassSample.class, "father", new String[] {"grandFather"}},
278+
{MultiLevelDependenciesTestClassSample.class, "child", new String[] {"father", "mother"}},
279+
{MultiLevelDependenciesTestClassSample.class, "grandFather", new String[] {}}
280+
};
281+
}
282+
221283
public static class MethodNameCollector implements ITestListener {
222284

223285
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)