/******************************************************************************* * Copyright (c) 2014 EclipseSource Muenchen GmbH and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Philip Langer - initial API and implementation * Alexandra Buzila - Test case for bug 446252 * Stefan Dirix - Test cases for bugs 453749 and 460675 *******************************************************************************/ package org.eclipse.emf.compare.tests.merge; import static org.junit.Assert.assertEquals; import java.io.IOException; import org.eclipse.emf.common.util.BasicMonitor; import org.eclipse.emf.common.util.EList; import org.eclipse.emf.compare.Comparison; import org.eclipse.emf.compare.Diff; import org.eclipse.emf.compare.EMFCompare; import org.eclipse.emf.compare.Equivalence; import org.eclipse.emf.compare.ReferenceChange; import org.eclipse.emf.compare.internal.utils.DiffUtil; import org.eclipse.emf.compare.merge.AbstractMerger; import org.eclipse.emf.compare.merge.BatchMerger; import org.eclipse.emf.compare.merge.IBatchMerger; import org.eclipse.emf.compare.merge.IMerger; import org.eclipse.emf.compare.merge.ReferenceChangeMerger; import org.eclipse.emf.compare.scope.DefaultComparisonScope; import org.eclipse.emf.compare.scope.IComparisonScope; import org.eclipse.emf.compare.tests.merge.data.TwoWayMergeInputData; import org.eclipse.emf.compare.tests.nodes.Node; import org.eclipse.emf.compare.tests.nodes.NodeMultipleContainment; import org.eclipse.emf.compare.tests.nodes.NodeOppositeRefOneToMany; import org.eclipse.emf.compare.tests.nodes.NodesPackage; import org.eclipse.emf.ecore.EObject; import org.eclipse.emf.ecore.resource.Resource; import org.eclipse.emf.ecore.resource.ResourceSet; import org.eclipse.emf.ecore.resource.impl.ResourceSetImpl; import org.junit.Test; /** * Tests two-way comparison of {@link NodesPackage nodes models} with XMI IDs and subsequent merging using the * {@link BatchMerger}. * * @author Philip Langer <planger@eclipsesource.com> */ public class TwoWayBatchMergingTest { private enum Direction { LEFT_TO_RIGHT, RIGHT_TO_LEFT; } private TwoWayMergeInputData input = new TwoWayMergeInputData(); private IMerger.Registry mergerRegistry = IMerger.RegistryImpl.createStandaloneInstance(); /** * Tests a scenario in which an element is moved from one container to another, whereas the containment * reference in the original container (left) is not available in the target container (right). This lead * to a NPE in {@link DiffUtil#findInsertionIndex(Comparison, org.eclipse.emf.compare.Diff, boolean)} (cf. * Bug #440679). * <p> * In this test case we have two differences: (1) Deletion of {@link NodeMultipleContainment} "A" and (2) * Move of {@link Node} "B" from {@link NodeMultipleContainment} "A" (reference "containmentRef2") to * {@link Node} "Root" into reference "containmentRef1". As a result, we move node "B" originally * contained through "containmentRef2" into a {@link Node}, which does not have the feature * "containmentRef2". * </p> * * @throws IOException * if {@link TwoWayMergeInputData} fails to load the test models. */ @Test public void mergingMoveToDifferentContainmentFeatureR2L() throws IOException { final Resource left = input.getMoveToDifferentContainmentFeatureRTLLeft(); final Resource right = input.getMoveToDifferentContainmentFeatureRTLRight(); batchMergeAndAssertEquality(left, right, Direction.RIGHT_TO_LEFT); } /** * This test is the reverse of the test ({@link #mergingMoveToDifferentContainmentFeatureR2L() above} to * make sure, this issue is not appearing in the other direction as well. * * @throws IOException * if {@link TwoWayMergeInputData} fails to load the test models. */ @Test public void mergingMoveToDifferentContainmentFeatureL2R() throws IOException { final Resource left = input.getMoveToDifferentContainmentFeatureL2RLeft(); final Resource right = input.getMoveToDifferentContainmentFeatureL2RRight(); batchMergeAndAssertEquality(left, right, Direction.LEFT_TO_RIGHT); } /** * Tests a scenario in which an opposite one-to-many reference is changed, whereas the original object on * the single-valued side of the one-to-many reference has no match. This lead to an * {@link IndexOutOfBoundsException} (cf. Bug #413520), because in the * {@link ReferenceChangeMerger#internalCheckOrdering(ReferenceChange, boolean) ordering check of the * equivalent changes}, the source container is <code>null</code> leading to an empty source list. This * leads to an invocation of {@link EList#move(int, EObject)} on an empty list. * <p> * In this test case, we have the following differences: (1) the deletion of {@link Node} "C" (only * available on the right-hand side), as a result, (2) the deletion of the reference to {@link Node} "C" * from {@link NodeOppositeRefOneToMany} "A" at its feature * {@link NodeOppositeRefOneToMany#getDestination() destination}, and (3 & 4) the change of the opposite * references {@link NodeOppositeRefOneToMany#getSource() source} and * {@link NodeOppositeRefOneToMany#getDestination() destination} between {@link NodeOppositeRefOneToMany * nodes} "A" and "B" (both have a match in the left and right model version). * </p> * * @throws IOException * if {@link TwoWayMergeInputData} fails to load the test models. */ @Test public void mergingOppositeReferenceChangeWithoutMatchingOriginalL2R() throws IOException { final Resource left = input.getOppositeReferenceChangeWithoutMatchingOrignalContainerL2RLeft(); final Resource right = input.getOppositeReferenceChangeWithoutMatchingOrignalContainerL2RRight(); batchMergeAndAssertEquality(left, right, Direction.LEFT_TO_RIGHT); } /** * Tests a scenario in which an opposite one-to-many reference is changed, whereas there is one deletion * and one addition on the multi-valued side of the opposite references. This correctly leads to three * differences: (1) a change of the single-valued reference, (2) an addition of a value in the * multi-valued opposite reference, and (3) a deletion of a value in the multi-valued opposite reference. * However, all three of them end up in the same {@link Equivalence object}, which caused the * {@link AbstractMerger} to merge only one of them; that is, in this scenario, the deletion. This * ultimately lead to unmerged differences (cf. Bug #441172). * * @throws IOException * if {@link TwoWayMergeInputData} fails to load the test models. */ @Test public void mergingOppositeReferenceChangeWithAddAndDeleteOnMultivaluedSideR2L() throws IOException { final Resource left = input.getOppositeReferenceChangeWithAddAndDeleteOnMultivaluedSideLeft(); final Resource right = input.getOppositeReferenceChangeWithAddAndDeleteOnMultivaluedSideRight(); batchMergeAndAssertEquality(left, right, Direction.RIGHT_TO_LEFT); } /** * Tests a scenario in which an element is moved from a single-valued containment reference to a * multi-valued containment reference. This lead to an {@link IllegalArgumentException} (cf. Bug #441258). * * @throws IOException * if {@link TwoWayMergeInputData} fails to load the test models. */ @Test public void mergingMoveFromSingleValueReferenceToMultiValueReferenceR2L() throws IOException { final Resource left = input.getMoveFromSingleValueReferenceToMultiValueReferenceR2LLeft(); final Resource right = input.getMoveFromSingleValueReferenceToMultiValueReferenceR2LRight(); batchMergeAndAssertEquality(left, right, Direction.RIGHT_TO_LEFT); } /** * Tests a scenario in which two elements are moved from a container into another container but in a * different order. In case of a right-to-left merge, this resulted in a wrong order of elements in the * container's list, because the resolution of source list, which is used for finding the LCS when * determining the insertion index, was returning the list of the wrong container (cf. Bug #442439). * * @throws IOException * if {@link TwoWayMergeInputData} fails to load the test models. */ @Test public void mergingMoveToNewContainerInADifferentOrderR2L() throws IOException { final Resource left = input.getMoveToNewContainerInADifferentOrderR2LLeft(); final Resource right = input.getMoveToNewContainerInADifferentOrderR2LRight(); batchMergeAndAssertEquality(left, right, Direction.RIGHT_TO_LEFT); } /** * Tests a scenario in which a new node is added as a new value in two many-to-many references, each at * index 0. This resulted in a wrong order of elements in the many-to-many references, because the values * have been filtered out in {@link DiffUtil#findInsertionIndex(Comparison, Diff, boolean)}, in particular * in {@link DiffUtil#computeIgnoredElements(Iterable<E>, Diff, boolean)}, since there is for each of the * diff elements another diff element in the comparison that concerns the same value and the same feature. * However, the target container of the other diff element is different and, therefore, the value should * not be ignored when computing the insertion index (cf. Bug #443504). * * @throws IOException * if {@link TwoWayMergeInputData} fails to load the test models. */ @Test public void mergingManyToManyReferenceChangesR2L() throws IOException { final Resource left = input.getManyToManyReferenceChangesR2LLeft(); final Resource right = input.getManyToManyReferenceChangesR2LRight(); batchMergeAndAssertEquality(left, right, Direction.RIGHT_TO_LEFT); } /** * Tests a scenario in which multiple nodes are moved inside a feature map. It tests whether the order of * the nodes inside the feature map is correct after the merge. * * @throws IOException * if {@link TwoWayMergeInputData} fails to load the test models. */ @Test public void mergingMoveToFeatureMapL2R() throws IOException { final Resource left = input.getMoveToFeatureMapL2RLeft(); final Resource right = input.getMoveToFeatureMapL2RRight(); batchMergeAndAssertEquality(left, right, Direction.LEFT_TO_RIGHT); } /** * Tests a scenario in which a non-containment feature map key is removed and the node to which it refers * is moved. * * @throws IOException * if {@link TwoWayMergeInputData} fails to load the test models. */ @Test public void mergingFeatureMapKeyRemoveAndRefMoveL2R() throws IOException { ResourceSet resourceSet = new ResourceSetImpl(); final Resource left = input.getFeatureMapKeyRemoveAndRefMoveL2RLeft(resourceSet); final Resource right = input.getFeatureMapKeyRemoveAndRefMoveL2RRight(resourceSet); batchMergeAndAssertEquality(left, right, Direction.LEFT_TO_RIGHT); } /** * Tests a scenario in which a non-containment feature map key is added and the node to which it refers is * moved. * * @throws IOException * if {@link TwoWayMergeInputData} fails to load the test models. */ @Test public void mergingFeatureMapKeyAddAndRefMoveR2L() throws IOException { ResourceSet resourceSet = new ResourceSetImpl(); final Resource left = input.getFeatureMapKeyAddAndRefMoveR2LLeft(resourceSet); final Resource right = input.getFeatureMapKeyAddAndRefMoveR2LRight(resourceSet); batchMergeAndAssertEquality(left, right, Direction.RIGHT_TO_LEFT); } /** * Tests a scenario in which a feature map contains multiple references to the same node without * containing it. It is tested if the merger can correctly delete some of these references. * * @throws IOException * if {@link TwoWayMergeInputData} fails to load the test models. */ @Test public void mergingDeleteFeatureMapNonContainmentsL2R() throws IOException { final ResourceSet resourceSet = new ResourceSetImpl(); final Resource left = input.getDeleteFeatureMapNonContainmentsL2RLeft(resourceSet); final Resource right = input.getDeleteFeatureMapNonContainmentsL2RRight(resourceSet); batchMergeAndAssertEquality(left, right, Direction.LEFT_TO_RIGHT); } /** * Merges the given resources {@code left} and {@code right} using the {@link BatchMerger} in the * specified {@code direction}, re-compares left and right, and asserts their equality in the end. * * @param left * left resource. * @param right * right resource. */ private void batchMergeAndAssertEquality(Resource left, Resource right, Direction direction) { // perform comparison final IComparisonScope scope = new DefaultComparisonScope(left, right, null); Comparison comparison = EMFCompare.builder().build().compare(scope); final EList<Diff> differences = comparison.getDifferences(); // batch merging of all detected differences: final IBatchMerger merger = new BatchMerger(mergerRegistry); switch (direction) { case LEFT_TO_RIGHT: merger.copyAllLeftToRight(differences, new BasicMonitor()); case RIGHT_TO_LEFT: merger.copyAllRightToLeft(differences, new BasicMonitor()); } // check that models are equal after batch merging Comparison assertionComparison = EMFCompare.builder().build().compare(scope); EList<Diff> assertionDifferences = assertionComparison.getDifferences(); assertEquals(0, assertionDifferences.size()); } }