/****************************************************************************** * Copyright (c) 2006, 2010 VMware Inc. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * and Apache License v2.0 which accompanies this distribution. * The Eclipse Public License is available at * http://www.eclipse.org/legal/epl-v10.html and the Apache License v2.0 * is available at http://www.opensource.org/licenses/apache2.0.php. * You may elect to redistribute this code under either of these licenses. * * Contributors: * VMware Inc. *****************************************************************************/ package org.eclipse.gemini.blueprint.extender.internal.dependencies; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; import junit.framework.TestCase; import org.eclipse.gemini.blueprint.extender.internal.DependencyMockBundle; import org.eclipse.gemini.blueprint.extender.internal.dependencies.shutdown.ServiceDependencySorter; import org.osgi.framework.Bundle; import org.springframework.util.ObjectUtils; /** * Base testing suite for ordering bundles based on service dependencies. To * visualize the graph, see the .dot files in the same folder which can read * through Graphviz tool. * * @author Costin Leau */ public abstract class AbstractServiceDependencySorterTest extends TestCase { protected ServiceDependencySorter sorter; private int count = 1; protected void setUp() throws Exception { sorter = createSorter(); } protected abstract ServiceDependencySorter createSorter(); protected void tearDown() throws Exception { sorter = null; } // A -> B -> C public void testSimpleTree() { DependencyMockBundle a = new DependencyMockBundle("A"); DependencyMockBundle b = new DependencyMockBundle("B"); DependencyMockBundle c = new DependencyMockBundle("C"); // A -> B -> C a.setDependentOn(b); b.setDependentOn(c); testDependencyTreeWithShuffle(new Bundle[]{c, b, a}, new Bundle[]{a, b, c}); } // A -> B, C, D // B -> C, E // C -> E // D -> B public void testMediumTree() { DependencyMockBundle a = new DependencyMockBundle("A"); DependencyMockBundle b = new DependencyMockBundle("B"); DependencyMockBundle c = new DependencyMockBundle("C"); DependencyMockBundle d = new DependencyMockBundle("D"); DependencyMockBundle e = new DependencyMockBundle("E"); a.setDependentOn(new Bundle[]{d, c, b}); b.setDependentOn(new Bundle[]{e, c}); c.setDependentOn(e); d.setDependentOn(b); testDependencyTreeWithShuffle(new Bundle[]{e, c, b, d, a}, new Bundle[]{a, b, c, d, e}); } // A -> B, C, D // B -> C // D -> B, E // E -> F, G // F -> G // H -> G // I -> H, J // depending on the order there are multiple shutdown orders public void testLargeTree() { DependencyMockBundle a = new DependencyMockBundle("a"); DependencyMockBundle b = new DependencyMockBundle("b"); DependencyMockBundle c = new DependencyMockBundle("c"); DependencyMockBundle d = new DependencyMockBundle("d"); DependencyMockBundle e = new DependencyMockBundle("e"); DependencyMockBundle f = new DependencyMockBundle("f"); DependencyMockBundle g = new DependencyMockBundle("g"); DependencyMockBundle h = new DependencyMockBundle("h"); DependencyMockBundle i = new DependencyMockBundle("i"); DependencyMockBundle j = new DependencyMockBundle("j"); a.setDependentOn(new Bundle[]{b, c, d}); b.setDependentOn(c); d.setDependentOn(new Bundle[]{b, e}); e.setDependentOn(new Bundle[]{f, g}); f.setDependentOn(g); h.setDependentOn(g); i.setDependentOn(new Bundle[]{h, j}); testDependencyTree(new Bundle[]{g, f, e, c, b, d, a, h, j, i}, new Bundle[]{a, b, c, d, e, f, g, h, i, j}); } // A -> B,D // B -> C, E // C // D -> B, C // E -> C public void testComplexTree() { DependencyMockBundle a = new DependencyMockBundle("A"); DependencyMockBundle b = new DependencyMockBundle("B"); DependencyMockBundle c = new DependencyMockBundle("C"); DependencyMockBundle d = new DependencyMockBundle("D"); DependencyMockBundle e = new DependencyMockBundle("E"); a.setDependentOn(new Bundle[]{b, d}); b.setDependentOn(new Bundle[]{c, e}); d.setDependentOn(new Bundle[]{b, c}); e.setDependentOn(new Bundle[]{c}); testDependencyTreeWithShuffle(new Bundle[]{c, e, b, d, a}, new Bundle[]{a, b, c, d, e}); } // Although this is an interesting test, the shutdown logic does not require that // it pass and the current algorithm does not handle this case. public void XtestMissingMiddle() throws Exception { DependencyMockBundle A = new DependencyMockBundle("A"); DependencyMockBundle B = new DependencyMockBundle("B"); DependencyMockBundle C = new DependencyMockBundle("C"); DependencyMockBundle D = new DependencyMockBundle("D"); DependencyMockBundle E = new DependencyMockBundle("E"); // Sets dependency A -> B -> C -> D -> E B.setDependentOn(A); C.setDependentOn(new Bundle[]{B}); D.setDependentOn(new Bundle[]{C}); E.setDependentOn(new Bundle[]{D}); testDependencyTree(new Bundle[]{A, C, E}, new Bundle[]{C, E, A}); } // ////////// // CIRCULAR TREES // ///////// // A -> B (id = 1) // B -> A (id = 2) // B -> A has to be stopped first since it was created last (highest id) public void testSimpleCircularTreeTieOnServiceRankingUsingServiceId() { DependencyMockBundle a = new DependencyMockBundle("A"); DependencyMockBundle b = new DependencyMockBundle("B"); b.setDependentOn(a, 0, 2); a.setDependentOn(b, 0, 1); Bundle[] expectedVer1 = new Bundle[]{a, b}; Bundle[] expectedVer2 = new Bundle[]{b, a}; // we should get the same order always (B should be stopped first) assertTrue(Arrays.equals(expectedVer2, sorter.computeServiceDependencyGraph(expectedVer1))); assertTrue(Arrays.equals(expectedVer2, sorter.computeServiceDependencyGraph(expectedVer2))); } public void testSimpleCircularTreeUsingServiceRanking() { DependencyMockBundle a = new DependencyMockBundle("A"); DependencyMockBundle b = new DependencyMockBundle("B"); b.setDependentOn(a); a.setDependentOn(b); testDependencyTreeWithShuffle(new Bundle[]{a, b}, new Bundle[]{a, b}); } // A -> B, C // B -> C, D // C -> D // D -> A public void testMediumCircularCycle() { DependencyMockBundle a = new DependencyMockBundle("A"); DependencyMockBundle b = new DependencyMockBundle("B"); DependencyMockBundle c = new DependencyMockBundle("C"); DependencyMockBundle d = new DependencyMockBundle("D"); a.setDependentOn(new Bundle[]{b, c}); b.setDependentOn(new Bundle[]{c, d}); c.setDependentOn(d); d.setDependentOn(a); testDependencyTreeWithShuffle(new Bundle[]{d, c, b, a}, new Bundle[]{a, b, c, d}); } // A -> B, D // B -> C, E // C -> D // D -> B, C // E -> C // E should be stopped first, since it is the last service started public void testComplexCyclicTree() { DependencyMockBundle a = new DependencyMockBundle("A"); DependencyMockBundle b = new DependencyMockBundle("B"); DependencyMockBundle c = new DependencyMockBundle("C"); DependencyMockBundle d = new DependencyMockBundle("D"); DependencyMockBundle e = new DependencyMockBundle("E"); a.setDependentOn(new Bundle[]{b, d}); b.setDependentOn(new Bundle[]{c, e}); c.setDependentOn(d); d.setDependentOn(new Bundle[]{b, c}); e.setDependentOn(c); testDependencyTreeWithShuffle(new Bundle[]{e, d, c, b, a}, new Bundle[]{a, b, c, d, e}); } public void testCircularReferenceId() throws Exception { DependencyMockBundle A = new DependencyMockBundle("A"); DependencyMockBundle B = new DependencyMockBundle("B"); DependencyMockBundle C = new DependencyMockBundle("C"); DependencyMockBundle D = new DependencyMockBundle("D"); DependencyMockBundle E = new DependencyMockBundle("E"); // Sets dependency A -> B -> C -> D -> E -> A // A has lowest id so gets shutdown last (started first). A.setDependentOn(new Bundle[]{E}, 0, 0); B.setDependentOn(new Bundle[]{A}, 0, 1); C.setDependentOn(new Bundle[]{B}, 0, 2); D.setDependentOn(new Bundle[]{C}, 0, 3); E.setDependentOn(new Bundle[]{D}, 0, 4); testDependencyTreeWithShuffle(new Bundle[]{E, D, C, B, A}, new Bundle[]{E, D, C, B, A}); } public void tstCircularReferenceIdMulti() throws Exception { DependencyMockBundle A = new DependencyMockBundle("A"); DependencyMockBundle B = new DependencyMockBundle("B"); DependencyMockBundle C = new DependencyMockBundle("C"); // Sets dependency A -> C -> B -> A // A has lowest id so gets shutdown last (started first). // B should go first since its service was started last (id = 4) A.setDependentOn(new Bundle[]{C}, 0, 0); B.setDependentOn(new Bundle[]{A, A}, new int[]{0, 0}, new long[]{4, 1}); C.setDependentOn(new Bundle[]{B}, 0, 2); testDependencyTreeWithShuffle(new Bundle[]{B, C, A}, new Bundle[]{C, B, A}); } public void testCircularReferenceRankMulti() throws Exception { DependencyMockBundle A = new DependencyMockBundle("A"); DependencyMockBundle B = new DependencyMockBundle("B"); DependencyMockBundle C = new DependencyMockBundle("C"); // Sets dependency A -> B -> C -> A // B has highest rank so gets shutdown last (its started first). // C should go second since its service has the second ranking (2) // which means A should go first A.setDependentOn(new Bundle[]{B}, 0, 0); B.setDependentOn(new Bundle[]{C, C}, new int[]{0, 3}, new long[]{0, 0}); C.setDependentOn(new Bundle[]{A}, 2, 0); testDependencyTreeWithShuffle(new Bundle[]{A, C, B}, new Bundle[]{C, B, A}); } public void testCircularReferenceReference() throws Exception { DependencyMockBundle A = new DependencyMockBundle("A"); DependencyMockBundle B = new DependencyMockBundle("B"); DependencyMockBundle C = new DependencyMockBundle("C"); DependencyMockBundle D = new DependencyMockBundle("D"); DependencyMockBundle E = new DependencyMockBundle("E"); // Sets dependency A -> B -> C -> D -> E -> A // A has higher ranking so gets shutdown last // A -> E // B -> A // C -> B // D -> C // E -> D // E -> D -> C -> B -> A -> E A.setDependentOn(new Bundle[]{E}, 4, 0); B.setDependentOn(new Bundle[]{A}, 3, 1); C.setDependentOn(new Bundle[]{B}, 2, 2); D.setDependentOn(new Bundle[]{C}, 1, 3); E.setDependentOn(new Bundle[]{D}, 0, 4); testDependencyTreeWithShuffle(new Bundle[]{E, D, C, B, A}, new Bundle[]{E, D, C, B, A}); } public void testForest() throws Exception { DependencyMockBundle A = new DependencyMockBundle("A"); DependencyMockBundle B = new DependencyMockBundle("B"); DependencyMockBundle C = new DependencyMockBundle("C"); DependencyMockBundle D = new DependencyMockBundle("D"); DependencyMockBundle E = new DependencyMockBundle("E"); DependencyMockBundle F = new DependencyMockBundle("F"); DependencyMockBundle G = new DependencyMockBundle("G"); DependencyMockBundle H = new DependencyMockBundle("H"); DependencyMockBundle I = new DependencyMockBundle("I"); DependencyMockBundle J = new DependencyMockBundle("J"); // Sets dependency A -> B -> C, B -> D -> E A.setDependentOn(new Bundle[]{B}); B.setDependentOn(new Bundle[]{C, D}); D.setDependentOn(new Bundle[]{E}); // Sets dependency F -> G, F -> H F.setDependentOn(new Bundle[]{G, H}); // Sets dependency I -> J I.setDependentOn(new Bundle[]{J}); testDependencyTree(new Bundle[]{G, H, F, E, D, J, C, B, A, I}, new Bundle[]{F, D, J, B, E, A, H, I, G, C}); } public void testInversedForest() throws Exception { DependencyMockBundle A = new DependencyMockBundle("A"); DependencyMockBundle B = new DependencyMockBundle("B"); DependencyMockBundle C = new DependencyMockBundle("C"); DependencyMockBundle D = new DependencyMockBundle("D"); DependencyMockBundle E = new DependencyMockBundle("E"); DependencyMockBundle F = new DependencyMockBundle("F"); DependencyMockBundle G = new DependencyMockBundle("G"); DependencyMockBundle H = new DependencyMockBundle("H"); DependencyMockBundle I = new DependencyMockBundle("I"); DependencyMockBundle J = new DependencyMockBundle("J"); // C -> B -> A, E -> D -> B B.setDependentOn(new Bundle[]{A}); C.setDependentOn(new Bundle[]{B}); D.setDependentOn(new Bundle[]{B}); E.setDependentOn(new Bundle[]{D}); // Sets dependency G -> F, H -> F G.setDependentOn(new Bundle[]{F}); H.setDependentOn(new Bundle[]{F}); // Sets dependency J -> I J.setDependentOn(new Bundle[]{I}); testDependencyTree(new Bundle[]{F, A, B, D, I, J, E, H, G, C}, new Bundle[]{F, D, J, B, E, A, H, I, G, C}); } /** * Test the resulting tree after shuffling the input bundles several times. * * @param expected * @param bundles * @return */ protected void testDependencyTree(Bundle[] expected, Bundle[] bundles) { Bundle[] tree = sorter.computeServiceDependencyGraph(bundles); assertTrue("array [" + ObjectUtils.nullSafeToString(tree) + "] does not match [" + ObjectUtils.nullSafeToString(expected) + "] for input [" + ObjectUtils.nullSafeToString(bundles) + "]", Arrays.equals(expected, tree)); } protected void testDependencyTreeWithShuffle(Bundle[] expected, Bundle[] bundles) { List input = new ArrayList(bundles.length); for (int i = 0; i < bundles.length; i++) { input.add(bundles[i]); } // shuffle based on the number of elements for (int i = 0; i < bundles.length; i++) { testDependencyTree(expected, (Bundle[]) input.toArray(new Bundle[bundles.length])); Collections.shuffle(input); } count += bundles.length; } public int countTestCases() { return count; } }