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