blob: acf2b677be292e58d2b8c039f087775dd6a7fa97 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2015 EclipseSource Munich 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
*******************************************************************************/
package org.eclipse.emf.compare.ide.ui.tests.unit;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import java.io.File;
import java.io.IOException;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.Path;
import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.compare.ide.ui.dependency.DependencyProviderDescriptor;
import org.eclipse.emf.compare.ide.ui.dependency.IDependencyProvider;
import org.eclipse.emf.compare.ide.ui.dependency.ModelDependencyProviderRegistry;
import org.eclipse.emf.compare.ide.ui.internal.EMFCompareIDEUIPlugin;
import org.eclipse.emf.ecore.EClass;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EPackage;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.emf.ecore.resource.URIConverter;
import org.junit.Test;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
@SuppressWarnings({"nls", "restriction" })
public class GitLogicalMergeWithCustomDependenciesTest extends AbstractGitLogicalModelTest {
private static final String DEPENDENT_FILE_NAME = "dependentFile.ecore";
/**
* We have a file1 and file2. The file1 contains package P1, which in turn contains the classes C1 and C2.
* The file2 contains the package P2 containing the classes C3 and C4. The class C1 (in file1) is a
* super-class of C3 (in file2). Thus, there is a cross-reference from a model element in file1 to file2.
*/
@Override
public void setUp() throws Exception {
super.setUp();
// set up the initial models in file1 and file2
final EPackage packageP1 = createPackage(null, "P1");
final EClass classC1 = createClass(packageP1, "C1");
createClass(packageP1, "C2");
resource1.getContents().add(packageP1);
final EPackage packageP2 = createPackage(null, "P2");
final EClass classC3 = createClass(packageP2, "C3");
createClass(packageP2, "C4");
resource2.getContents().add(packageP2);
classC3.getESuperTypes().add(classC1);
save(resource1, resource2);
}
@Override
public void tearDown() throws Exception {
// clear all dependency providers
getModelDependencyProviderRegistry().clear();
super.tearDown();
}
private ModelDependencyProviderRegistry getModelDependencyProviderRegistry() {
return EMFCompareIDEUIPlugin.getDefault().getModelDependencyProviderRegistry();
}
/**
* In this test, a commit in a branch <em>adds</em> a <em>non-empty</em> file that is specified as a
* custom dependency but is not linked using cross-references; also it adds a class to file1. A commit in
* the master branch also adds a new class to file1 and file2. Adding the new classes on both sides, is
* only done to make sure the logical merge kicked in. If the logical merge wouldn't kick in, we'd get a
* textual conflict.
* <p>
* Since the logical merge has to deal with two existing and one added file, the EMFResourceMappingMerger
* is also responsible for merging the addition of the file in the branch although it is not directly
* linked to any other model.
* </p>
*/
@Test
public void testRemoteBranchAddsUnlinkedNonEmptyDependentFile() throws Exception {
final Collection<EObject> contentsOfNewResource = new HashSet<EObject>();
contentsOfNewResource.add(createPackage(null, "P3"));
assertCorrectMergingIfRemoteBranchAddsUnlinkedButDependentFile(contentsOfNewResource);
}
/**
* In this test, a commit in a branch <em>adds</em> a <em>empty</em> file that is specified as a custom
* dependency but is not linked using cross-references; also it adds a class to file1. A commit in the
* master branch also adds a new class to file1 and file2. Adding the new classes on both sides, is only
* done to make sure the logical merge kicked in. If the logical merge wouldn't kick in, we'd get a
* textual conflict.
* <p>
* Since the logical merge has to deal with two existing and one added file, the EMFResourceMappingMerger
* is also responsible for merging the addition of the file in the branch although it is not directly
* linked to any other model.
* </p>
*/
@Test
public void testRemoteBranchAddsUnlinkedEmptyDependentFile() throws Exception {
assertCorrectMergingIfRemoteBranchAddsUnlinkedButDependentFile(new HashSet<EObject>());
}
private void assertCorrectMergingIfRemoteBranchAddsUnlinkedButDependentFile(
final Collection<EObject> contentsOfAddedFile) throws Exception, IOException, CoreException {
// commit initial state as common ancestor commit
repository.addAllAndCommit("initial-commit");
// create branch but stay on master
repository.createBranch(MASTER, BRANCH);
// add class C4 to file1, add class C5 to file2, and commit to master
createClass((EPackage)findObject(resource1, "P1"), "C4");
createClass((EPackage)findObject(resource2, "P2"), "C5");
save(resource1, resource2);
repository.addAndCommit(project, "master-commit", file1, file2);
// checkout branch and add a file that will be declared as dependency
repository.checkoutBranch(BRANCH);
save(createResourceWithContents(DEPENDENT_FILE_NAME, contentsOfAddedFile));
final File dependentFile = getFile(DEPENDENT_FILE_NAME);
assertTrue(dependentFile.exists());
// also add a new class to produce a textual conflict
createClass((EPackage)findObject(resource1, "P1"), "C6");
save(resource1);
repository.addAndCommit(project, "branch-commit", dependentFile, file1);
// install mock dependency provider
installMockModelDependencyProvider(
ImmutableMap.of(file1.getName(), ImmutableSet.of(DEPENDENT_FILE_NAME)));
// checkout master and merge branch
repository.checkoutBranch(MASTER);
// dependent file does not exit before the merge
assertFalse(iProject.exists(new Path(DEPENDENT_FILE_NAME)));
repository.mergeLogical(BRANCH);
// there shouldn't be a conflict, because the model-based merge handled this merge
assertTrue(repository.status().getConflicting().isEmpty());
// dependent file does exit after the merge
assertTrue(iProject.exists(new Path(DEPENDENT_FILE_NAME)));
}
/**
* In this test, a commit in a branch <em>deletes</em> a <em>non-empty</em> file that is specified as a
* custom dependency but is not linked using cross-references; also it adds a new class. A commit in the
* master branch adds a new class to file1 and to file2. Adding the new classes on both sides, is only
* done to make sure the logical merge kicked in. If the logical merge wouldn't kick in, we'd get a
* textual conflict.
* <p>
* Since the logical merge has to deal with two existing and one deleted file, the
* EMFResourceMappingMerger is also responsible for merging the deletion of the file in the branch
* although it is not directly linked to any other model.
* </p>
*/
@Test
public void testRemoteBranchDeletesUnlinkedNonEmptyDependentFile() throws Exception {
final Collection<EObject> contentsOfDeletedResource = new HashSet<EObject>();
contentsOfDeletedResource.add(createPackage(null, "P3"));
assertCorrectMergingIfRemoteBranchDeletesUnlinkedButDependentFile(contentsOfDeletedResource);
}
/**
* In this test, a commit in a branch <em>deletes</em> a <em>empty</em> file that is specified as a custom
* dependency but is not linked using cross-references; also it adds a new class. A commit in the master
* branch adds a new class to file1 and to file2. Adding the new classes on both sides, is only done to
* make sure the logical merge kicked in. If the logical merge wouldn't kick in, we'd get a textual
* conflict.
* <p>
* Since the logical merge has to deal with two existing and one deleted file, the
* EMFResourceMappingMerger is also responsible for merging the deletion of the file in the branch
* although it is not directly linked to any other model.
* </p>
*/
@Test
public void testRemoteBranchDeletesUnlinkedEmptyDependentFile() throws Exception {
assertCorrectMergingIfRemoteBranchDeletesUnlinkedButDependentFile(new HashSet<EObject>());
}
private void assertCorrectMergingIfRemoteBranchDeletesUnlinkedButDependentFile(
final Collection<EObject> contentsOfDeletedResource)
throws IOException, CoreException, Exception {
// add another dependent file and commit state as common ancestor commit
save(createResourceWithContents(DEPENDENT_FILE_NAME, contentsOfDeletedResource));
repository.addAllAndCommit("initial-commit");
// create branch but stay on master
repository.createBranch(MASTER, BRANCH);
// add class C4 to file1, add class C5 to file2, and commit to master
createClass((EPackage)findObject(resource1, "P1"), "C4");
createClass((EPackage)findObject(resource2, "P2"), "C5");
save(resource1, resource2);
repository.addAndCommit(project, "master-commit", file1, file2);
// checkout branch, delete the dependent file, and commit
repository.checkoutBranch(BRANCH);
final File dependentFile = getFile(DEPENDENT_FILE_NAME);
iProject.getFile(DEPENDENT_FILE_NAME).delete(true, new NullProgressMonitor());
assertFalse(iProject.exists(new Path(DEPENDENT_FILE_NAME)));
// also add a new class to produce a textual conflict
createClass((EPackage)findObject(resource1, "P1"), "C6");
save(resource1);
repository.addAndCommit(project, "branch-commit", file1, dependentFile);
// install mock dependency provider
installMockModelDependencyProvider(
ImmutableMap.of(file1.getName(), ImmutableSet.of(DEPENDENT_FILE_NAME)));
// checkout master and merge branch
repository.checkoutBranch(MASTER);
// dependent file exits before the merge
assertTrue(iProject.exists(new Path(DEPENDENT_FILE_NAME)));
repository.mergeLogical(BRANCH);
// there shouldn't be a conflict, because the model-based merge handled this merge
assertTrue(repository.status().getConflicting().isEmpty());
// dependent file does not exit after the merge
assertFalse(iProject.exists(new Path(DEPENDENT_FILE_NAME)));
}
private Resource createResourceWithContents(String fileName, Collection<EObject> contents)
throws Exception {
final Resource newResource = createAndConnectResource(fileName);
newResource.getContents().addAll(contents);
return newResource;
}
private void installMockModelDependencyProvider(
final ImmutableMap<String, ImmutableSet<String>> dependencies) {
getModelDependencyProviderRegistry().addProvider("mock",
new DependencyProviderDescriptor(null, null) {
private final IDependencyProvider mock = new MockDependencyProvider(dependencies);
@Override
public IDependencyProvider getDependencyProvider() {
return mock;
}
});
}
private class MockDependencyProvider implements IDependencyProvider {
private ImmutableMap<String, ImmutableSet<String>> dependencies;
public MockDependencyProvider(ImmutableMap<String, ImmutableSet<String>> dependencies) {
this.dependencies = dependencies;
}
public boolean apply(URI uri) {
return dependencies.containsKey(uri.lastSegment());
}
public Set<URI> getDependencies(URI uri, URIConverter uriConverter) {
if (dependencies.containsKey(uri.lastSegment())) {
final Set<URI> uris = new HashSet<URI>();
for (String fileName : dependencies.get(uri.lastSegment())) {
final URI dependentUri = uri.trimSegments(1).appendSegment(fileName);
if (uriConverter.exists(dependentUri, Collections.emptyMap())) {
uris.add(dependentUri);
}
}
return uris;
} else {
return Collections.emptySet();
}
}
}
}