| /* |
| * 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; |
| } |
| |
| } |