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

@Factory with dataProvider changes order of iterations #1467

Merged
merged 4 commits into from
Jul 15, 2017
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGES.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
Current
Fixed: GITHUB-799: @Factory with dataProvider changes order of iterations (Krishnan Mahadevan)
New: Enhance XML Reporter to be able to customize the file name (Krishnan Mahadevan)
Fixed: GITHUB-1417: Class param injection is not working with @BeforeClass (Krishnan Mahadevan)
Fixed: GITHUB-1440: Improve error message when wrong params on configuration methods (Krishnan Mahadevan)
Expand Down
17 changes: 10 additions & 7 deletions src/main/java/org/testng/internal/Graph.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
Expand Down Expand Up @@ -121,7 +122,10 @@ public void topologicalSort() {
// Sort the nodes alphabetically to make sure that methods of the same class
// get run close to each other as much as possible
//
Collections.sort(nodes2);
Comparator<Node> comparator = Systematiser.getComparator();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you add the Comparator<Node<T>> as constructor param?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didnt quite understand this observation @juherr . Can you please elaborate.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comparator should be an attribute of the graph and the comparator instantiation logic should be outside.
The attribute description will be: private final Comparator<Node<T>> comparator and its usage will be Collections.sort(nodes2, comparator).

Copy link
Member Author

@krmahadevan krmahadevan Jul 12, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, I understood. But why would the comparator instantiation be plugged into a Graph instance from outside ? The comparator instantiation is completely dependent on just a JVM argument and as such I thought we can have it just query the comparator on an On-Demand basis from Systematiser

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

JVM argument is just a quick workaround which should not be used in the core.
I don't like it because it is a global variable which is bad by definition.
That's why I think we should manage the feature from a global point of view but offer JVM arg to the users in order to keep API in the same state.
At the same time, if the integration of the feature modifies all the structure, we may use some temporary tricks and think about the core redesign later.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From what I have looked at, if one were to be resorting to make this available via a configuration instead of a JVM argument, this is going to require quite a few changes. Also if you look at it, a configuration would still be applicable at the entire TestNG instance level, which is kind of what the JVM argument is doing as well. Does that add a bit of clarity to your question ?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will try to have a deeper look asap.

if (comparator != null) {
Collections.sort(nodes2, comparator);
}

//
// Sort
Expand Down Expand Up @@ -160,7 +164,10 @@ private void initializeIndependentNodes() {
// Ideally, we should not have to sort this. However, due to a bug where it treats all the methods as
// dependent nodes. Therefore, all the nodes were mostly sorted alphabetically. So we need to keep
// the behavior for now.
Collections.sort(list);
Comparator<Node> comparator = Systematiser.getComparator();
if (comparator != null) {
Collections.sort(list, comparator);
}
m_independentNodes = Maps.newLinkedHashMap();
for (Node<T> node : list) {
m_independentNodes.put(node.getObject(), node);
Expand Down Expand Up @@ -255,7 +262,7 @@ public String toString() {
/////
// class Node
//
public static class Node<T> implements Comparable<Node<T>> {
public static class Node<T> {
private T m_object = null;
private Map<T, T> m_predecessors = Maps.newHashMap();

Expand Down Expand Up @@ -335,9 +342,5 @@ public boolean hasPredecessor(T m) {
return m_predecessors.containsKey(m);
}

@Override
public int compareTo(Node<T> o) {
return getObject().toString().compareTo(o.getObject().toString());
}
}
}
86 changes: 86 additions & 0 deletions src/main/java/org/testng/internal/Systematiser.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
package org.testng.internal;

import org.testng.ITestNGMethod;

import java.util.Comparator;

public class Systematiser {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not a huge fan of the name but I've nothing better to propose :)


private Systematiser() {
//Utility class. Defeat instantiation.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The class should be final too.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

}

public static Comparator<Graph.Node> getComparator() {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comparator<Graph.Node<ITestNGMethod>>

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We cannot do this because the INSTANCES one doesn't work with a ITestNGMethod but merely works with just an Object. The ITestNGMethod based comparator also if you notice resorts to using the ITestNGMethod object iff the Node contains an ITestNGMethod object.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't catch it because o1.getObject().toString() will work with Node<ITestNGMethod> too.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In fact, this point is important IMO.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@juherr - Ok, but I would like to understand why does Node object have to be tied to ITestNGMethod ? There are tests for the Graph class viz., GraphTest which attempts at testing Graph behavior using Strings. If we basically tie down the Comparator to ITestNGMethod then this would cause changes to be required in the test as well and by behavior one shouldn't have to need an ITestNGMethod object to test the behavior of the Graph. That is why I didn't change it.

Comparator<Graph.Node> comparator = null;
String text = System.getProperty("testng.order", Order.INSTANCES.getValue());

Order order = Order.parse(text);
switch (order) {
case INSTANCES:
comparator = new Comparator<Graph.Node>() {
@Override
public int compare(Graph.Node o1, Graph.Node o2) {
return o1.getObject().toString().compareTo(o2.getObject().toString());
}

@Override
public String toString() {
return "Instance_Names";
}
};
break;

case METHOD_NAMES:
comparator = new Comparator<Graph.Node>() {
@Override
public int compare(Graph.Node o1, Graph.Node o2) {
if (o1.getObject() instanceof ITestNGMethod && o2.getObject() instanceof ITestNGMethod) {
String n1 = ((ITestNGMethod) o1.getObject()).getMethodName();
String n2 = ((ITestNGMethod) o1.getObject()).getMethodName();
return n1.compareTo(n2);
}
return o1.getObject().getClass().getName().compareTo(o2.getObject().getClass().getName());
}

@Override
public String toString() {
return "Method_Names";
}
};
break;

case NONE:
default:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of null, maybe we can use a default comparator (all params are equals)?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed.

}

return comparator;
}

enum Order {
METHOD_NAMES("methods"),
INSTANCES("instances"),
NONE("none");

Order(String value) {
this.value = value;
}

private String value;

public String getValue() {
return value;
}

public static Order parse(String value) {
if (value == null || value.trim().isEmpty()) {
return INSTANCES;
}
for (Order each : values()) {
if (each.getValue().equalsIgnoreCase(value)) {
return each;
}
}
return INSTANCES;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package test.github799;

import org.testng.Assert;
import org.testng.IInvokedMethod;
import org.testng.IInvokedMethodListener;
import org.testng.ITestNGListener;
import org.testng.ITestResult;
import org.testng.Reporter;
import org.testng.TestNG;
import org.testng.annotations.Test;
import org.testng.collections.Lists;
import test.SimpleBaseTest;

import java.util.List;

public class EnsureInstancesAreOrderedViaFactories extends SimpleBaseTest {

@Test
public void testMethod() {
System.setProperty("testng.order", "none");
runTest(TestSample.class, "1", "2", "3", "4");
}

@Test
public void randomOrderTestMethod() {
System.setProperty("testng.order", "none");
runTest(ReverseOrderTestSample.class, "4", "1", "3", "2");
}

@Test
public void methodsOrderTest() {
System.setProperty("testng.order", "methods");
runTest(MethodsTestSample.class, "android", "angry", "birds");
}

@Test
public void testInstancesOrder() {
System.setProperty("testng.order", "instances");
runTest(InstanceTestSample.class, "Master Oogway:90", "Master Shifu:50");
}

private void runTest(Class<?> clazz, String... expected) {
TestNG tng = create(clazz);
OrderEavesdropper listener = new OrderEavesdropper();
tng.addListener((ITestNGListener) listener);
tng.run();

for (int i = 0; i < expected.length; i++) {
String actual = listener.messages.get(i);
Assert.assertEquals(actual, expected[i]);
}
}

public static class OrderEavesdropper implements IInvokedMethodListener {
List<String> messages = Lists.newArrayList();

@Override
public void beforeInvocation(IInvokedMethod method, ITestResult testResult) {
}

@Override
public void afterInvocation(IInvokedMethod method, ITestResult testResult) {
messages.addAll(Reporter.getOutput(testResult));
}
}
}
37 changes: 37 additions & 0 deletions src/test/java/test/github799/InstanceTestSample.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package test.github799;

import org.testng.Assert;
import org.testng.Reporter;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Factory;
import org.testng.annotations.Test;

public class InstanceTestSample {
private String name;
private int age;

@Factory(dataProvider = "dp")
public InstanceTestSample(String name, int age) {
this.name = name;
this.age = age;
}

@DataProvider(name = "dp")
public static Object[][] getData() {
return new Object[][]{
{"Master Shifu", 50},
{"Master Oogway", 90}
};
}

@Test
public void testMethod() {
Reporter.log(toString());
Assert.assertNotNull(this.name);
}

@Override
public String toString() {
return name + ":" + age;
}
}
28 changes: 28 additions & 0 deletions src/test/java/test/github799/MethodsTestSample.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package test.github799;

import org.testng.Assert;
import org.testng.Reporter;
import org.testng.annotations.Test;

public class MethodsTestSample {
@Test
public void angry() {
String methodName = Reporter.getCurrentTestResult().getMethod().getMethodName();
Reporter.log(methodName);
Assert.assertNotNull(methodName);
}

@Test
public void birds() {
String methodName = Reporter.getCurrentTestResult().getMethod().getMethodName();
Reporter.log(methodName);
Assert.assertNotNull(methodName);
}

@Test
public void android() {
String methodName = Reporter.getCurrentTestResult().getMethod().getMethodName();
Reporter.log(methodName);
Assert.assertNotNull(methodName);
}
}
27 changes: 27 additions & 0 deletions src/test/java/test/github799/ReverseOrderTestSample.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package test.github799;

import org.testng.Assert;
import org.testng.Reporter;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Factory;
import org.testng.annotations.Test;

public class ReverseOrderTestSample {
int num;

@Factory(dataProvider = "data")
public ReverseOrderTestSample(int n) {
num = n;
}

@DataProvider
public static Object[][] data() {
return new Object[][]{{4}, {1}, {3}, {2}};
}

@Test
public void test() {
Reporter.log(Integer.toString(num));
Assert.assertTrue(num > 0);
}
}
27 changes: 27 additions & 0 deletions src/test/java/test/github799/TestSample.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package test.github799;

import org.testng.Assert;
import org.testng.Reporter;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Factory;
import org.testng.annotations.Test;

public class TestSample {
int num;

@Factory(dataProvider = "data")
public TestSample(int n) {
num = n;
}

@DataProvider
public static Object[][] data() {
return new Object[][]{{1}, {2}, {3}, {4}};
}

@Test
public void test() {
Reporter.log(Integer.toString(num));
Assert.assertTrue(num > 0);
}
}
7 changes: 7 additions & 0 deletions src/test/resources/testng.xml
Original file line number Diff line number Diff line change
Expand Up @@ -815,12 +815,19 @@
<class name="test.thread.parallelization.ParallelByMethodsTestCase8Scenario1"/>
</classes>
</test>

<!--This test depends on System properties. So its intentionally being run at the end
because the toggling of the system property will affect other tests -->
<test name = "RunAtLast">
<classes>
<class name="test.dataprovider.issue128.DataProviderParametersMismatchTest"/>
</classes>
</test>

<test name="RunOrderingTest">
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now, we may add a programmatically way to configure the test without system properties.

<classes>
<class name="test.github799.EnsureInstancesAreOrderedViaFactories"/>
</classes>
</test>
</suite>