package org.testng.internal; import org.testng.ITestResult; import org.testng.TestNGException; import org.testng.collections.Lists; import org.testng.collections.Maps; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; 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<T extends Object> { private static boolean m_verbose = false; private Map<T, Node<T>> m_nodes = Maps.newHashMap(); private List<T> m_strictlySortedNodes = null; // 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<T, Node<T>> m_independentNodes = null; public void addNode(T tm) { ppp("ADDING NODE " + tm + " " + tm.hashCode()); m_nodes.put(tm, new Node<T>(tm)); // Initially, all the nodes are put in the independent list as well } public Set<T> getPredecessors(T node) { return findNode(node).getPredecessors().keySet(); } /*private boolean hasBeenSorted() { return null != m_strictlySortedNodes; }*/ public boolean isIndependent(T object) { return m_independentNodes.containsKey(object); } private Node<T> findNode(T object) { return m_nodes.get(object); } public void addPredecessor(T tm, T predecessor) { Node<T> 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 if (null == m_independentNodes) { m_independentNodes = Maps.newHashMap(); for (T k : m_nodes.keySet()) { m_independentNodes.put(k, m_nodes.get(k)); } } 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<T> getNeighbors(T t) { Set<T> result = new HashSet<T>(); for (Node<T> n : findNode(t).getNeighbors()) { result.add(n.getObject()); } return result; } private Collection<Node<T>> getNodes() { return m_nodes.values(); } public Collection<T> getNodeValues() { return m_nodes.keySet(); } /** * @return All the nodes that don't have any order with each other. */ public Set<T> 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<T> getStrictlySortedNodes() { return m_strictlySortedNodes; } public void topologicalSort() { ppp("================ SORTING"); m_strictlySortedNodes = Lists.newArrayList(); if (null == m_independentNodes) { m_independentNodes = Maps.newHashMap(); } // // Clone the list of nodes but only keep those that are // not independent. // List<Node<T>> nodes2 = Lists.newArrayList(); for (Node<T> n : getNodes()) { if (! isIndependent((T) 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); // // 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<T> node = findNodeWithNoPredecessors(nodes2); if (null == node) { List<T> cycle = new Tarjan<T>(this, nodes2.get(0).getObject()).getCycle(); StringBuffer sb = new StringBuffer(); 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((T) node.getObject()); removeFromNodes(nodes2, node); } } ppp("=============== DONE SORTING"); if (m_verbose) { dumpSortedNodes(); } } private void dumpSortedNodes() { System.out.println("====== SORTED NODES"); for (T n : m_strictlySortedNodes) { System.out.println(" " + n); } System.out.println("====== END SORTED NODES"); } private void dumpGraph() { System.out.println("====== GRAPH"); for (Node<T> n : m_nodes.values()) { System.out.println(" " + n); } } /** * Remove a node from a list of nodes and update the list of predecessors * for all the remaining nodes. */ private void removeFromNodes(List<Node<T>> nodes, Node<T> node) { nodes.remove(node); for (Node<T> n : nodes) { n.removePredecessor(node.getObject()); } } private static void ppp(String s) { if (m_verbose) { System.out.println("[Graph] " + s); } } private Node<T> findNodeWithNoPredecessors(List<Node<T>> nodes) { for (Node<T> n : nodes) { if (! n.hasPredecessors()) { return n; } } return null; } /** * @param o * @return A list of all the predecessors for o */ public List<T> findPredecessors(T o) { // Locate the node Node<T> 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<T> result = new LinkedList<T>(); Set<T> visited = new HashSet<T>(); LinkedList<T> queue = new LinkedList<T>(); 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() { StringBuffer result = new StringBuffer("[Graph "); for (T node : m_nodes.keySet()) { result.append(findNode(node)).append(" "); } result.append("]"); return result.toString(); } ///// // class Node // public static class Node<T> implements Comparable<Node<T>> { private T m_object = null; private Map<T, T> m_predecessors = Maps.newHashMap(); public Node(T tm) { m_object = tm; } private Set<Node<T>> m_neighbors = new HashSet<Node<T>>(); public void addNeighbor(Node<T> neighbor) { m_neighbors.add(neighbor); } public Set<Node<T>> getNeighbors() { return m_neighbors; } @Override public Node<T> clone() { Node<T> result = new Node<T>(m_object); for (T pred : m_predecessors.values()) { result.addPredecessor(pred); } return result; } public T getObject() { return m_object; } public Map<T, T> getPredecessors() { return m_predecessors; } /** * * @return true if this predecessor was found and removed */ public boolean removePredecessor(T o) { boolean result = false; // dump(); 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; } private void dump() { ppp(toString()); } @Override public String toString() { StringBuffer sb = new StringBuffer("[Node:" + m_object); sb.append(" pred:"); for (T o : m_predecessors.values()) { sb.append(" " + o.toString()); } 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); } public int compareTo(Node<T> o) { return getObject().toString().compareTo(o.getObject().toString()); } } // // class Node ///// public static void main(String[] argv) { Graph<String> g = new Graph<String>(); g.addNode("3"); g.addNode("1"); g.addNode("2.2"); g.addNode("independent"); g.addNode("2.1"); g.addNode("2.3"); // 1 -> 2.1, 2.2, 2.3 --> 3 g.addPredecessor("3", "2.1"); g.addPredecessor("3", "2.1"); g.addPredecessor("3", "2.3"); g.addPredecessor("2.1", "1"); g.addPredecessor("2.2", "1"); g.addPredecessor("2.3", "1"); g.topologicalSort(); List<String> l = g.getStrictlySortedNodes(); for (String s : l) { System.out.println(" " + s); } int i = 0; assert "1".equals(l.get(i)); i++; assert "2.1".equals(l.get(i)) || "2.1".equals(l.get(i)) || "2.2".equals(l.get(i)); i++; assert "2.1".equals(l.get(i)) || "2.1".equals(l.get(i)) || "2.2".equals(l.get(i)); i++; assert "2.1".equals(l.get(i)) || "2.1".equals(l.get(i)) || "2.2".equals(l.get(i)); i++; assert "3".equals(l.get(i)); assert 1 == g.getIndependentNodes().size(); // // Test findPredecessors // ppp("GRAPH:" + g); { List<String> predecessors = g.findPredecessors("2.1"); assert predecessors.size() == 1; assert predecessors.get(0).equals("1"); } { List<String> predecessors = g.findPredecessors("3"); assert predecessors.size() == 4; assert predecessors.get(0).equals("1"); assert predecessors.get(1).equals("2.1") || predecessors.get(2).equals("2.2") || predecessors.get(2).equals("2"); assert predecessors.get(2).equals("2.1") || predecessors.get(2).equals("2.2") || predecessors.get(2).equals("2"); assert predecessors.get(3).equals("2.1") || predecessors.get(2).equals("2.2") || predecessors.get(2).equals("2"); } ppp("TESTS PASSED"); } }