/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ package org.apache.ivy.core.sort; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Date; import java.util.Iterator; import java.util.List; import org.apache.ivy.core.module.descriptor.DefaultDependencyDescriptor; import org.apache.ivy.core.module.descriptor.DefaultModuleDescriptor; import org.apache.ivy.core.module.descriptor.DependencyDescriptor; import org.apache.ivy.core.module.descriptor.ModuleDescriptor; import org.apache.ivy.core.module.id.ModuleRevisionId; import org.apache.ivy.plugins.circular.CircularDependencyHelper; import org.apache.ivy.plugins.circular.CircularDependencyStrategy; import org.apache.ivy.plugins.circular.WarnCircularDependencyStrategy; import org.apache.ivy.plugins.version.ExactVersionMatcher; import org.apache.ivy.plugins.version.LatestVersionMatcher; import junit.framework.Assert; import junit.framework.TestCase; public class SortTest extends TestCase { private DefaultModuleDescriptor md1; private DefaultModuleDescriptor md2; private DefaultModuleDescriptor md3; private DefaultModuleDescriptor md4; private SortEngine sortEngine; private SimpleSortEngineSettings settings; private SilentNonMatchingVersionReporter nonMatchReporter; /* * (non-Javadoc) * * @see junit.framework.TestCase#setUp() */ protected void setUp() throws Exception { super.setUp(); md1 = createModuleDescriptorToSort("md1", null); // The revison is often not set in the // ivy.xml file that are ordered md2 = createModuleDescriptorToSort("md2", "rev2"); // But somtimes they are set md3 = createModuleDescriptorToSort("md3", "rev3"); md4 = createModuleDescriptorToSort("md4", "rev4"); settings = new SimpleSortEngineSettings(); settings.setCircularDependencyStrategy(WarnCircularDependencyStrategy.getInstance()); settings.setVersionMatcher(new ExactVersionMatcher()); sortEngine = new SortEngine(settings); nonMatchReporter = new SilentNonMatchingVersionReporter(); } public void testSort() throws Exception { addDependency(md2, "md1", "rev1"); addDependency(md3, "md2", "rev2"); addDependency(md4, "md3", "rev3"); DefaultModuleDescriptor[][] expectedOrder = new DefaultModuleDescriptor[][] {{md1, md2, md3, md4}}; Collection permutations = getAllLists(md1, md3, md2, md4); for (Iterator it = permutations.iterator(); it.hasNext();) { List toSort = (List) it.next(); assertSorted(expectedOrder, sortModuleDescriptors(toSort, nonMatchReporter)); } } /** * Sorter does not throw circular dependency, circular dependencies are handled at resolve time * only. However the sort respect the transitive order when it is unambiguous. (if A depends * transitively of B, but B doesn't depends transitively on A then B always comes before A). */ public void testCircularDependency() throws Exception { addDependency(md1, "md4", "rev4"); addDependency(md2, "md1", "rev1"); addDependency(md3, "md2", "rev2"); addDependency(md4, "md3", "rev3"); DefaultModuleDescriptor[][] possibleOrder = new DefaultModuleDescriptor[][] { {md2, md3, md4, md1}, {md3, md4, md1, md2}, {md4, md1, md2, md3}, {md1, md2, md3, md4}}; Collection permutations = getAllLists(md1, md3, md2, md4); for (Iterator it = permutations.iterator(); it.hasNext();) { List toSort = (List) it.next(); assertSorted(possibleOrder, sortModuleDescriptors(toSort, nonMatchReporter)); } } public void testCircularDependency2() throws Exception { addDependency(md2, "md3", "rev3"); addDependency(md2, "md1", "rev1"); addDependency(md3, "md2", "rev2"); addDependency(md4, "md3", "rev3"); DefaultModuleDescriptor[][] possibleOrder = new DefaultModuleDescriptor[][] { {md1, md3, md2, md4}, {md1, md2, md3, md4} // , // {md3, md1, md2, md4} //we don't have this solution. The loops apear has one contigous // element. }; Collection permutations = getAllLists(md1, md3, md2, md4); for (Iterator it = permutations.iterator(); it.hasNext();) { List toSort = (List) it.next(); assertSorted(possibleOrder, sortModuleDescriptors(toSort, nonMatchReporter)); } } // Test IVY-624 public void testCircularDependencyInfiniteLoop() throws Exception { addDependency(md1, "md2", "rev2"); addDependency(md1, "md3", "rev3"); addDependency(md2, "md3", "rev3"); addDependency(md3, "md4", "rev4"); addDependency(md4, "md1", "rev1"); addDependency(md4, "md2", "rev2"); List toSort = Arrays.asList(new Object[] {md1, md2, md3, md4}); sortModuleDescriptors(toSort, nonMatchReporter); // If it ends, it's ok. } /** * In case of Circular dependency a warning is generated. */ public void testCircularDependencyReport() { addDependency(md2, "md3", "rev3"); addDependency(md2, "md1", "rev1"); addDependency(md3, "md2", "rev2"); addDependency(md4, "md3", "rev3"); // Would be much easier with a tool like jmock class CircularDependencyReporterMock implements CircularDependencyStrategy { private int nbOfCall = 0; public String getName() { return "CircularDependencyReporterMock"; } public void handleCircularDependency(ModuleRevisionId[] mrids) { assertEquals("handleCircularDependency is expected to be called only once", 0, nbOfCall); String assertMsg = "incorrect cicular dependency invocation" + CircularDependencyHelper.formatMessage(mrids); final int expectedLength = 3; assertEquals(assertMsg, expectedLength, mrids.length); if (mrids[0].equals(md2.getModuleRevisionId())) { assertEquals(assertMsg, md3.getModuleRevisionId(), mrids[1]); assertEquals(assertMsg, md2.getModuleRevisionId(), mrids[2]); } else { assertEquals(assertMsg, md3.getModuleRevisionId(), mrids[0]); assertEquals(assertMsg, md2.getModuleRevisionId(), mrids[1]); assertEquals(assertMsg, md3.getModuleRevisionId(), mrids[2]); } nbOfCall++; } public void validate() { Assert.assertEquals("handleCircularDependency has nor been called", 1, nbOfCall); } } CircularDependencyReporterMock circularDepReportMock = new CircularDependencyReporterMock(); settings.setCircularDependencyStrategy(circularDepReportMock); List toSort = Arrays.asList(new ModuleDescriptor[] {md4, md3, md2, md1}); sortModuleDescriptors(toSort, nonMatchReporter); circularDepReportMock.validate(); } /** * The dependency can ask for the latest integration. It should match whatever the version * declared in the modules to order. */ public void testLatestIntegration() { addDependency(md2, "md1", "latest.integration"); addDependency(md3, "md2", "latest.integration"); addDependency(md4, "md3", "latest.integration"); settings.setVersionMatcher(new LatestVersionMatcher()); DefaultModuleDescriptor[][] expectedOrder = new DefaultModuleDescriptor[][] {{md1, md2, md3, md4}}; Collection permutations = getAllLists(md1, md3, md2, md4); for (Iterator it = permutations.iterator(); it.hasNext();) { List toSort = (List) it.next(); assertSorted(expectedOrder, sortModuleDescriptors(toSort, nonMatchReporter)); } } /** * When the version asked by a dependency is not compatible with the version declared in the * module to order, the two modules should be considered as independant NB: I'm sure of what * 'compatible' means ! */ public void testDifferentVersionNotConsidered() { // To test it, I use a 'broken' loop (in one step, I change the revision) in such a way that // I get only one solution. If the loop was // complete more solutions where possible. addDependency(md1, "md4", "rev4-other"); addDependency(md2, "md1", "rev1"); addDependency(md3, "md2", "rev2"); addDependency(md4, "md3", "rev3"); DefaultModuleDescriptor[][] possibleOrder = new DefaultModuleDescriptor[][] {{md1, md2, md3, md4}}; Collection permutations = getAllLists(md1, md3, md2, md4); for (Iterator it = permutations.iterator(); it.hasNext();) { List toSort = (List) it.next(); assertSorted(possibleOrder, sortModuleDescriptors(toSort, nonMatchReporter)); } } /** * In case of Different version a warning is generated. */ public void testDifferentVersionWarning() { final DependencyDescriptor md4OtherDep = addDependency(md1, "md4", "rev4-other"); addDependency(md2, "md1", "rev1"); addDependency(md3, "md2", "rev2"); addDependency(md4, "md3", "rev3"); // Would be much easier with a tool like jmock class NonMatchingVersionReporterMock implements NonMatchingVersionReporter { private int nbOfCall = 0; public void reportNonMatchingVersion(DependencyDescriptor descriptor, ModuleDescriptor md) { Assert.assertEquals("reportNonMatchingVersion should be invokded only once", 0, nbOfCall); Assert.assertEquals(md4OtherDep, descriptor); Assert.assertEquals(md4, md); nbOfCall++; } public void validate() { Assert.assertEquals("reportNonMatchingVersion has not be called", 1, nbOfCall); } } NonMatchingVersionReporterMock nonMatchingVersionReporterMock = new NonMatchingVersionReporterMock(); List toSort = Arrays.asList(new ModuleDescriptor[] {md4, md3, md2, md1}); sortModuleDescriptors(toSort, nonMatchingVersionReporterMock); nonMatchingVersionReporterMock.validate(); } private List sortModuleDescriptors(List toSort, NonMatchingVersionReporter nonMatchingVersionReporter) { return sortEngine.sortModuleDescriptors(toSort, new SortOptions().setNonMatchingVersionReporter(nonMatchingVersionReporter)); } private DefaultModuleDescriptor createModuleDescriptorToSort(String moduleName, String revision) { ModuleRevisionId mrid = ModuleRevisionId.newInstance("org", moduleName, revision); return new DefaultModuleDescriptor(mrid, "integration", new Date()); } private DependencyDescriptor addDependency(DefaultModuleDescriptor parent, String moduleName, String revision) { ModuleRevisionId mrid = ModuleRevisionId.newInstance("org", moduleName, revision); DependencyDescriptor depDescr = new DefaultDependencyDescriptor(parent, mrid, false, false, true); parent.addDependency(depDescr); return depDescr; } /** * Verifies that sorted in one of the list of listOfPossibleSort. * * @param listOfPossibleSort * array of possible sort result * @param sorted * actual sortedList to compare */ private void assertSorted(DefaultModuleDescriptor[][] listOfPossibleSort, List sorted) { for (int i = 0; i < listOfPossibleSort.length; i++) { DefaultModuleDescriptor[] expectedList = listOfPossibleSort[i]; assertEquals(expectedList.length, sorted.size()); boolean isExpected = true; for (int j = 0; j < expectedList.length; j++) { if (!expectedList[j].equals(sorted.get(j))) { isExpected = false; break; } } if (isExpected) { return; } } // failed, build a nice message StringBuffer errorMessage = new StringBuffer(); errorMessage.append("Unexpected order : \n{ "); for (int i = 0; i < sorted.size(); i++) { if (i > 0) { errorMessage.append(" , "); } errorMessage.append(((DefaultModuleDescriptor) sorted.get(i)).getModuleRevisionId()); } errorMessage.append("}\nEpected : \n"); for (int i = 0; i < listOfPossibleSort.length; i++) { DefaultModuleDescriptor[] expectedList = listOfPossibleSort[i]; if (i > 0) { errorMessage.append(" or\n"); } errorMessage.append("{ "); for (int j = 0; j < expectedList.length; j++) { if (j > 0) { errorMessage.append(" , "); } errorMessage.append(expectedList[j].getModuleRevisionId()); } errorMessage.append(" } "); } fail(errorMessage.toString()); } /** Returns a collection of lists that contains the elements a,b,c and d */ private Collection getAllLists(Object a, Object b, Object c, Object d) { final int nbOfList = 24; ArrayList r = new ArrayList(nbOfList); r.add(Arrays.asList(new Object[] {a, b, c, d})); r.add(Arrays.asList(new Object[] {a, b, d, c})); r.add(Arrays.asList(new Object[] {a, c, b, d})); r.add(Arrays.asList(new Object[] {a, c, d, b})); r.add(Arrays.asList(new Object[] {a, d, b, c})); r.add(Arrays.asList(new Object[] {a, d, c, b})); r.add(Arrays.asList(new Object[] {b, a, c, d})); r.add(Arrays.asList(new Object[] {b, a, d, c})); r.add(Arrays.asList(new Object[] {b, c, a, d})); r.add(Arrays.asList(new Object[] {b, c, d, a})); r.add(Arrays.asList(new Object[] {b, d, a, c})); r.add(Arrays.asList(new Object[] {b, d, c, a})); r.add(Arrays.asList(new Object[] {c, b, a, d})); r.add(Arrays.asList(new Object[] {c, b, d, a})); r.add(Arrays.asList(new Object[] {c, a, b, d})); r.add(Arrays.asList(new Object[] {c, a, d, b})); r.add(Arrays.asList(new Object[] {c, d, b, a})); r.add(Arrays.asList(new Object[] {c, d, a, b})); r.add(Arrays.asList(new Object[] {d, b, c, a})); r.add(Arrays.asList(new Object[] {d, b, a, c})); r.add(Arrays.asList(new Object[] {d, c, b, a})); r.add(Arrays.asList(new Object[] {d, c, a, b})); r.add(Arrays.asList(new Object[] {d, a, b, c})); r.add(Arrays.asList(new Object[] {d, a, c, b})); return r; } }