package com.tngtech.archunit.library.dependencies;
import java.util.Collections;
import java.util.Random;
import java.util.Set;
import com.google.common.collect.ImmutableSet;
import org.junit.Test;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.collect.Iterables.getOnlyElement;
import static com.tngtech.archunit.library.dependencies.SimpleEdge.singleEdge;
import static com.tngtech.archunit.library.dependencies.SimpleEdge.singleEdgeList;
import static java.util.Collections.singleton;
import static org.assertj.core.api.Assertions.assertThat;
public class GraphTest {
@Test
public void graph_without_cycles() {
Graph<String, String> graph = new Graph<>();
graph.add(randomNode(), Collections.<Edge<String, String>>emptySet());
graph.add(randomNode(), Collections.<Edge<String, String>>emptySet());
graph.add(randomNode(), Collections.<Edge<String, String>>emptySet());
assertThat(graph.getCycles()).isEmpty();
}
@Test
public void three_node_cycle_is_detected() {
Graph<String, String> graph = new Graph<>();
String nodeA = "Node-A";
String nodeB = "Node-B";
String nodeC = "Node-C";
graph.add(nodeA, Collections.<Edge<String, String>>emptySet());
graph.add(nodeB, Collections.<Edge<String, String>>singleton(new SimpleEdge(nodeA, nodeB)));
graph.add(nodeC, ImmutableSet.<Edge<String, String>>of(new SimpleEdge(nodeB, nodeC), new SimpleEdge(nodeC, nodeA)));
Cycle<String, String> cycle = getOnlyElement(graph.getCycles());
assertThat(cycle.getEdges()).hasSize(3);
assertEdgeExists(cycle, nodeA, nodeB);
assertEdgeExists(cycle, nodeB, nodeC);
assertEdgeExists(cycle, nodeC, nodeA);
}
@Test
public void sub_cycle_of_three_node_cycle_is_detected() {
Graph<String, String> graph = new Graph<>();
String nodeA = "Node-A";
String nodeB = "Node-B";
String nodeC = "Node-C";
graph.add(nodeA, Collections.<Edge<String, String>>emptySet());
graph.add(nodeB, ImmutableSet.<Edge<String, String>>of(new SimpleEdge(nodeB, nodeA), new SimpleEdge(nodeA, nodeB)));
graph.add(nodeC, Collections.<Edge<String, String>>emptySet());
Cycle<String, String> cycle = getOnlyElement(graph.getCycles());
assertThat(cycle.getEdges()).hasSize(2);
assertEdgeExists(cycle, nodeA, nodeB);
assertEdgeExists(cycle, nodeB, nodeA);
}
@Test
public void multiple_cycles_are_detected() {
Graph<String, String> graph = new Graph<>();
Cycle<String, String> threeElements = randomCycle(3);
Cycle<String, String> fourElements = randomCycle(4);
Cycle<String, String> fiveElements = randomCycle(5);
addCycles(graph, threeElements, fourElements, fiveElements);
addCrossLink(graph, threeElements, fourElements);
addCrossLink(graph, fourElements, fiveElements);
Set<Cycle<String, String>> cycles = graph.getCycles();
assertThat(cycles).containsOnly(threeElements, fourElements, fiveElements);
}
@Test
public void double_linked_three_node_cycle_results_in_five_cycles() {
Graph<String, String> graph = new Graph<>();
Cycle<String, String> threeElements = randomCycle(3);
addCycles(graph, threeElements);
for (Edge<String, String> edge : threeElements.getEdges()) {
graph.add(edge.getTo(), singleEdge(edge.getTo(), edge.getFrom()));
}
assertThat(graph.getCycles()).hasSize(5);
}
private Cycle<String, String> randomCycle(int numberOfNodes) {
checkArgument(numberOfNodes > 1, "A cycle can't be formed by less than 2 nodes");
Path<String, String> path = new Path<>(singleEdgeList(randomNode(), randomNode()));
for (int i = 0; i < numberOfNodes - 2; i++) {
path.append(new SimpleEdge(path.getEnd(), randomNode()));
}
return new Cycle<>(path.append(new SimpleEdge(path.getEnd(), path.getStart())));
}
private void addCycles(Graph<String, String> graph, Cycle<String, String>... cycles) {
for (Cycle<String, String> cycle : cycles) {
for (Edge<String, String> edge : cycle.getEdges()) {
graph.add(edge.getFrom(), Collections.<Edge<String, String>>emptySet());
graph.add(edge.getTo(), singleton(edge));
}
}
}
private void addCrossLink(Graph<String, String> graph, Cycle<String, String> first, Cycle<String, String> second) {
Random rand = new Random();
String origin = first.getEdges().get(rand.nextInt(first.getEdges().size())).getFrom();
String target = second.getEdges().get(rand.nextInt(second.getEdges().size())).getFrom();
graph.add(target, ImmutableSet.copyOf(singleEdgeList(origin, target)));
}
static void assertEdgeExists(Cycle<?, ?> cycle, Object from, Object to) {
for (Edge<?, ?> edge : cycle.getEdges()) {
if (edge.getFrom().equals(from) && edge.getTo().equals(to)) {
return;
}
}
throw new AssertionError("Expected Cycle to contain an edge from " + from + " to " + to);
}
static String randomNode() {
return "" + System.nanoTime();
}
}