package org.infinispan.util;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertFalse;
import static org.testng.Assert.assertTrue;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import org.infinispan.test.AbstractInfinispanTest;
import org.testng.annotations.Test;
/**
* Tests functionality in {@link org.infinispan.util.DependencyGraph}.
*
* @author gustavonalle
* @since 7.0
*/
@Test(testName = "util.DependencyGraphTest", groups = "unit")
public class DependencyGraphTest extends AbstractInfinispanTest {
@Test
public void testEmpty() throws CyclicDependencyException {
assertTrue(new DependencyGraph().topologicalSort().isEmpty());
}
@Test
public void testLinear() throws CyclicDependencyException {
DependencyGraph<Integer> graph = new DependencyGraph<>();
int size = 100;
for (int i = 1; i <= size; i++) {
graph.addDependency(i, i - 1);
}
List<Integer> sort = graph.topologicalSort();
assertEquals(sort.size(), size + 1);
assertEquals(sort.get(0), Integer.valueOf(100));
assertEquals(sort.get(100), Integer.valueOf(0));
}
@Test
public void testNonLinear() throws CyclicDependencyException {
DependencyGraph<String> graph = new DependencyGraph<>();
String A = "a";
String B = "b";
String C = "c";
String D = "d";
graph.addDependency(C, B);
graph.addDependency(C, D);
graph.addDependency(B, A);
graph.addDependency(A, D);
List<String> sort = graph.topologicalSort();
assertEquals(sort, Arrays.asList(C, B, A, D));
}
@Test
public void testIdempotency() throws CyclicDependencyException {
DependencyGraph<String> g = new DependencyGraph<>();
g.addDependency("N1", "N2");
g.addDependency("N2", "N3");
g.addDependency("N1", "N2");
g.addDependency("N2", "N3");
assertEquals(g.topologicalSort().size(), 3);
assertEquals(g.topologicalSort(), Arrays.asList("N1", "N2", "N3"));
}
@Test
public void testDependent() throws CyclicDependencyException {
DependencyGraph<String> graph = new DependencyGraph<>();
graph.addDependency("A", "B");
graph.addDependency("A", "C");
graph.addDependency("A", "D");
graph.addDependency("D", "F");
assertTrue(graph.hasDependent("B"));
assertTrue(graph.hasDependent("C"));
assertTrue(graph.hasDependent("D"));
assertTrue(graph.hasDependent("F"));
assertFalse(graph.hasDependent("A"));
assertTrue(graph.getDependents("A").isEmpty());
assertEquals(graph.getDependents("B").iterator().next(), "A");
assertEquals(graph.getDependents("C").iterator().next(), "A");
assertEquals(graph.getDependents("D").iterator().next(), "A");
assertEquals(graph.getDependents("F").iterator().next(), "D");
}
@Test
public void testConcurrentAccess() throws Exception {
DependencyGraph<String> graph = new DependencyGraph<>();
ExecutorService service = Executors.newCachedThreadPool(getTestThreadFactory("Worker"));
CountDownLatch startLatch = new CountDownLatch(1);
int threads = 20;
ArrayList<Future<?>> futures = new ArrayList<>();
for (int i = 0; i < threads; i++) {
futures.add(submitTask("A", "B", startLatch, service, graph));
futures.add(submitTask("A", "C", startLatch, service, graph));
futures.add(submitTask("A", "D", startLatch, service, graph));
futures.add(submitTask("A", "B", startLatch, service, graph));
futures.add(submitTask("D", "B", startLatch, service, graph));
futures.add(submitTask("D", "C", startLatch, service, graph));
futures.add(submitTask("C", "B", startLatch, service, graph));
}
startLatch.countDown();
awaitAll(futures);
assertEquals(graph.topologicalSort(), Arrays.asList("A", "D", "C", "B"));
}
@Test
public void testRemoveDependency() throws CyclicDependencyException {
DependencyGraph<String> g = new DependencyGraph<>();
g.addDependency("E", "B");
g.addDependency("E", "C");
g.addDependency("E", "D");
g.addDependency("B", "D");
g.addDependency("B", "C");
g.addDependency("C", "D");
assertEquals(g.topologicalSort(), Arrays.asList("E", "B", "C", "D"));
g.removeDependency("E", "B");
g.addDependency("B", "E");
assertEquals(g.topologicalSort(), Arrays.asList("B", "E", "C", "D"));
g.clearAll();
assertTrue(g.topologicalSort().isEmpty());
}
@Test
public void testRemoveElement() throws CyclicDependencyException {
DependencyGraph<String> g = new DependencyGraph<>();
g.addDependency("E", "B");
g.addDependency("E", "C");
g.addDependency("E", "D");
g.addDependency("B", "D");
g.addDependency("B", "C");
g.addDependency("C", "D");
assertEquals(g.topologicalSort(), Arrays.asList("E", "B", "C", "D"));
g.remove("C");
assertEquals(g.topologicalSort(), Arrays.asList("E", "B", "D"));
g.remove("B");
assertEquals(g.topologicalSort(), Arrays.asList("E", "D"));
g.remove("E");
assertEquals(g.topologicalSort(), Arrays.asList("D"));
g.remove("D");
assertTrue(g.topologicalSort().isEmpty());
}
@Test(expectedExceptions = IllegalArgumentException.class)
public void testAddSelf() {
new DependencyGraph<>().addDependency("N", "N");
}
@Test(expectedExceptions = IllegalArgumentException.class)
public void testAdNull() {
new DependencyGraph<>().addDependency("N", null);
}
@Test(expectedExceptions = CyclicDependencyException.class)
public void testCycle() throws CyclicDependencyException {
DependencyGraph<Object> graph = new DependencyGraph<>();
Object o1 = new Object();
Object o2 = new Object();
Object o3 = new Object();
graph.addDependency(o1, o2);
graph.addDependency(o2, o3);
graph.addDependency(o3, o1);
graph.topologicalSort();
}
private Future<?> submitTask(final String from, final String to, final CountDownLatch waitingFor, ExecutorService onExecutor, final DependencyGraph<String> graph) {
return onExecutor.submit(new Runnable() {
@Override
public void run() {
try {
waitingFor.await();
graph.addDependency(from, to);
} catch (InterruptedException ignored) {
}
}
});
}
private void awaitAll(List<Future<?>> futures) throws Exception {
for (Future f : futures) {
f.get(10, TimeUnit.SECONDS);
}
}
}