org.testng.internal.Graph Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of testng Show documentation
Show all versions of testng Show documentation
A testing framework for the JVM
package org.testng.internal;
import org.testng.TestNGException;
import org.testng.collections.Lists;
import org.testng.collections.Maps;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* Simple graph class to implement topological sort (used to sort methods based on what groups
* they depend on).
*
* @author Cedric Beust, Aug 19, 2004
*/
public class Graph {
private static boolean m_verbose = false;
private Map> m_nodes = Maps.newLinkedHashMap();
private List m_strictlySortedNodes = null;
private final Comparator> comparator;
// A map of nodes that are not the predecessors of any node
// (not needed for the algorithm but convenient to calculate
// the parallel/sequential lists in TestNG).
private Map> m_independentNodes = null;
public Graph(Comparator> comparator) {
this.comparator = comparator;
}
public void addNode(T tm) {
ppp("ADDING NODE " + tm + " " + tm.hashCode());
m_nodes.put(tm, new Node<>(tm));
// Initially, all the nodes are put in the independent list as well
}
public Set getPredecessors(T node) {
return findNode(node).getPredecessors().keySet();
}
public boolean isIndependent(T object) {
return m_independentNodes.containsKey(object);
}
private Node findNode(T object) {
return m_nodes.get(object);
}
public void addPredecessor(T tm, T predecessor) {
Node node = findNode(tm);
if (null == node) {
throw new TestNGException("Non-existing node: " + tm);
}
else {
node.addPredecessor(predecessor);
addNeighbor(tm, predecessor);
// Remove these two nodes from the independent list
initializeIndependentNodes();
m_independentNodes.remove(predecessor);
m_independentNodes.remove(tm);
ppp(" REMOVED " + predecessor + " FROM INDEPENDENT OBJECTS");
}
}
private void addNeighbor(T tm, T predecessor) {
findNode(tm).addNeighbor(findNode(predecessor));
}
public Set getNeighbors(T t) {
Set result = new HashSet<>();
for (Node n : findNode(t).getNeighbors()) {
result.add(n.getObject());
}
return result;
}
private Collection> getNodes() {
return m_nodes.values();
}
public Collection getNodeValues() {
return m_nodes.keySet();
}
/**
* @return All the nodes that don't have any order with each other.
*/
public Set getIndependentNodes() {
return m_independentNodes.keySet();
}
/**
* @return All the nodes that have an order with each other, sorted
* in one of the valid sorts.
*/
public List getStrictlySortedNodes() {
return m_strictlySortedNodes;
}
public void topologicalSort() {
ppp("================ SORTING");
m_strictlySortedNodes = Lists.newArrayList();
initializeIndependentNodes();
//
// Clone the list of nodes but only keep those that are
// not independent.
//
List> nodes2 = Lists.newArrayList();
for (Node n : getNodes()) {
if (! isIndependent(n.getObject())) {
ppp("ADDING FOR SORT: " + n.getObject());
nodes2.add(n.clone());
}
else {
ppp("SKIPPING INDEPENDENT NODE " + n);
}
}
//
// 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);
//
// Sort
//
while (! nodes2.isEmpty()) {
//
// Find all the nodes that don't have any predecessors, add
// them to the result and mark them for removal
//
Node node = findNodeWithNoPredecessors(nodes2);
if (null == node) {
List cycle = new Tarjan<>(this, nodes2.get(0).getObject()).getCycle();
StringBuilder sb = new StringBuilder();
sb.append("The following methods have cyclic dependencies:\n");
for (T m : cycle) {
sb.append(m).append("\n");
}
throw new TestNGException(sb.toString());
}
else {
m_strictlySortedNodes.add(node.getObject());
removeFromNodes(nodes2, node);
}
}
ppp("=============== DONE SORTING");
if (m_verbose) {
dumpSortedNodes();
}
}
private void initializeIndependentNodes() {
if (null == m_independentNodes) {
List> list = Lists.newArrayList(m_nodes.values());
// Ideally, we should not have to sort this. However, due to a bug where it treats all the methods as
// dependent nodes.
Collections.sort(list, comparator);
m_independentNodes = Maps.newLinkedHashMap();
for (Node node : list) {
m_independentNodes.put(node.getObject(), node);
}
}
}
private void dumpSortedNodes() {
System.out.println("====== SORTED NODES");
for (T n : m_strictlySortedNodes) {
System.out.println(" " + n);
}
System.out.println("====== END SORTED NODES");
}
/**
* Remove a node from a list of nodes and update the list of predecessors
* for all the remaining nodes.
*/
private void removeFromNodes(List> nodes, Node node) {
nodes.remove(node);
for (Node n : nodes) {
n.removePredecessor(node.getObject());
}
}
private static void ppp(String s) {
if (m_verbose) {
System.out.println("[Graph] " + s);
}
}
private Node findNodeWithNoPredecessors(List> nodes) {
for (Node n : nodes) {
if (! n.hasPredecessors()) {
return n;
}
}
return null;
}
/**
* @param o
* @return A list of all the predecessors for o
*/
public List findPredecessors(T o) {
// Locate the node
Node node = findNode(o);
if (null == node) {
// This can happen if an interceptor returned new methods
return Lists.newArrayList();
}
// If we found the node, use breadth first search to find all
// all of the predecessors of o. "result" is the growing list
// of all predecessors. "visited" is the set of items we've
// already encountered. "queue" is the queue of items whose
// predecessors we haven't yet explored.
LinkedList result = new LinkedList<>();
Set visited = new HashSet<>();
LinkedList queue = new LinkedList<>();
visited.add(o);
queue.addLast(o);
while (! queue.isEmpty()) {
for (T obj : getPredecessors(queue.removeFirst())) {
if (! visited.contains(obj)) {
visited.add(obj);
queue.addLast(obj);
result.addFirst(obj);
}
}
}
return result;
}
@Override
public String toString() {
StringBuilder result = new StringBuilder("[Graph ");
for (T node : m_nodes.keySet()) {
result.append(findNode(node)).append(" ");
}
result.append("]");
return result.toString();
}
/////
// class Node
//
public static class Node {
private T m_object = null;
private Map m_predecessors = Maps.newHashMap();
public Node(T tm) {
m_object = tm;
}
private Set> m_neighbors = new HashSet<>();
public void addNeighbor(Node neighbor) {
m_neighbors.add(neighbor);
}
public Set> getNeighbors() {
return m_neighbors;
}
@Override
public Node clone() {
Node result = new Node<>(m_object);
for (T pred : m_predecessors.values()) {
result.addPredecessor(pred);
}
return result;
}
public T getObject() {
return m_object;
}
public Map getPredecessors() {
return m_predecessors;
}
/**
*
* @return true if this predecessor was found and removed
*/
public boolean removePredecessor(T o) {
boolean result = false;
T pred = m_predecessors.get(o);
if (null != pred) {
result = null != m_predecessors.remove(o);
if (result) {
ppp(" REMOVED PRED " + o + " FROM NODE " + m_object);
}
else {
ppp(" FAILED TO REMOVE PRED " + o + " FROM NODE " + m_object);
}
}
return result;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder("[Node:" + m_object);
sb.append(" pred:");
for (T o : m_predecessors.values()) {
sb.append(" ").append(o);
}
sb.append("]");
String result = sb.toString();
return result;
}
public void addPredecessor(T tm) {
ppp(" ADDING PREDECESSOR FOR " + m_object + " ==> " + tm);
m_predecessors.put(tm, tm);
}
public boolean hasPredecessors() {
return m_predecessors.size() > 0;
}
public boolean hasPredecessor(T m) {
return m_predecessors.containsKey(m);
}
}
}