/* Copyright (c) 2012-2014 Boundless and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Distribution License v1.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/org/documents/edl-v10.html
*
* Contributors:
* Gabriel Roldan (Boundless) - initial implementation
*/
package org.locationtech.geogig.test.integration;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import org.junit.Test;
import org.locationtech.geogig.api.Node;
import org.locationtech.geogig.api.NodeRef;
import org.locationtech.geogig.api.ObjectId;
import org.locationtech.geogig.api.Ref;
import org.locationtech.geogig.api.RevCommit;
import org.locationtech.geogig.api.RevObject.TYPE;
import org.locationtech.geogig.api.plumbing.DiffIndex;
import org.locationtech.geogig.api.plumbing.DiffTree;
import org.locationtech.geogig.api.plumbing.DiffWorkTree;
import org.locationtech.geogig.api.plumbing.diff.DiffEntry;
import org.locationtech.geogig.api.plumbing.diff.DiffEntry.ChangeType;
import org.locationtech.geogig.api.porcelain.AddOp;
import org.locationtech.geogig.api.porcelain.CommitOp;
import org.locationtech.geogig.api.porcelain.DiffOp;
import org.locationtech.geogig.repository.WorkingTree;
import org.opengis.feature.Feature;
import org.opengis.feature.simple.SimpleFeature;
import org.opengis.feature.simple.SimpleFeatureType;
import org.opengis.feature.type.Name;
import com.google.common.base.Function;
import com.google.common.collect.Collections2;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;
/**
* Unit test suite for {@link DiffOp}, must cover {@link DiffIndex}, {@link DiffWorkTree}, and
* {@link DiffTree}
*
*/
public class DiffOpTest extends RepositoryTestCase {
private DiffOp diffOp;
@Override
protected void setUpInternal() throws Exception {
this.diffOp = geogig.command(DiffOp.class);
}
@Test
public void testDiffPreconditions() throws Exception {
Iterator<DiffEntry> difflist = geogig.command(DiffOp.class).call();
assertNotNull(difflist);
assertFalse(difflist.hasNext());
final ObjectId oid1 = insertAndAdd(points1);
final RevCommit commit1_1 = geogig.command(CommitOp.class).call();
try {
diffOp.setOldVersion(oid1.toString()).setNewVersion(Ref.HEAD).call();
fail("Expected IAE as oldVersion is not a commit");
} catch (IllegalArgumentException e) {
assertTrue(e.getMessage(), e.getMessage().contains(oid1.toString()));
assertTrue(e.getMessage(),
e.getMessage().contains("doesn't resolve to a tree-ish object"));
}
try {
diffOp.setOldVersion(commit1_1.getId().toString()).setNewVersion(oid1.toString())
.call();
fail("Expected IAE as newVersion is not a commit");
} catch (IllegalArgumentException e) {
assertTrue(e.getMessage(), e.getMessage().contains(oid1.toString()));
assertTrue(e.getMessage(),
e.getMessage().contains("doesn't resolve to a tree-ish object"));
}
}
@Test
public void testEmptyRepo() throws Exception {
Iterator<DiffEntry> difflist = diffOp.setOldVersion(ObjectId.NULL.toString()).call();
assertNotNull(difflist);
assertFalse(difflist.hasNext());
}
@Test
public void testNoChangeSameCommit() throws Exception {
insertAndAdd(points1);
final RevCommit commit = geogig.command(CommitOp.class).setAll(true).call();
assertFalse(diffOp.setOldVersion(commit.getId().toString())
.setNewVersion(commit.getId().toString()).call().hasNext());
}
@Test
public void testSingleAddition() throws Exception {
final ObjectId newOid = insertAndAdd(points1);
geogig.command(CommitOp.class).setAll(true).call();
List<DiffEntry> difflist = toList(diffOp.setOldVersion(ObjectId.NULL)
.setNewVersion(Ref.HEAD).call());
assertNotNull(difflist);
assertEquals(1, difflist.size());
DiffEntry de = difflist.get(0);
assertNull(de.getOldObject());
assertNotNull(de.getNewObject());
String expectedPath = NodeRef.appendChild(pointsName, points1.getIdentifier().getID());
assertEquals(expectedPath, de.newPath());
assertEquals(DiffEntry.ChangeType.ADDED, de.changeType());
assertEquals(ObjectId.NULL, de.oldObjectId());
assertEquals(newOid, de.newObjectId());
assertFalse(de.getNewObject().getMetadataId().isNull());
}
@Test
public void testSingleAdditionReverseOrder() throws Exception {
final ObjectId newOid = insertAndAdd(points1);
final RevCommit commit = geogig.command(CommitOp.class).setAll(true).call();
List<DiffEntry> difflist = toList(diffOp.setOldVersion(commit.getId())
.setNewVersion(ObjectId.NULL).call());
assertNotNull(difflist);
assertEquals(1, difflist.size());
DiffEntry de = difflist.get(0);
assertNull(de.getNewObject());
assertNotNull(de.getOldObject());
assertEquals(DiffEntry.ChangeType.REMOVED, de.changeType());
assertEquals(ObjectId.NULL, de.newObjectId());
assertEquals(newOid, de.oldObjectId());
assertFalse(de.getOldObject().getMetadataId().isNull());
}
@Test
public void testSingleDeletion() throws Exception {
final ObjectId featureContentId = insertAndAdd(points1);
final RevCommit addCommit = geogig.command(CommitOp.class).setAll(true).call();
assertTrue(deleteAndAdd(points1));
final RevCommit deleteCommit = geogig.command(CommitOp.class).setAll(true).call();
List<DiffEntry> difflist = toList(diffOp.setOldVersion(addCommit.getId())
.setNewVersion(deleteCommit.getId()).call());
final String path = NodeRef.appendChild(pointsName, points1.getIdentifier().getID());
assertNotNull(difflist);
assertEquals(1, difflist.size());
DiffEntry de = difflist.get(0);
assertEquals(path, de.oldPath());
assertEquals(DiffEntry.ChangeType.REMOVED, de.changeType());
assertEquals(featureContentId, de.oldObjectId());
assertEquals(ObjectId.NULL, de.newObjectId());
}
@Test
public void testSingleDeletionReverseOrder() throws Exception {
final ObjectId featureContentId = insertAndAdd(points1);
final RevCommit addCommit = geogig.command(CommitOp.class).setAll(true).call();
assertTrue(deleteAndAdd(points1));
final RevCommit deleteCommit = geogig.command(CommitOp.class).setAll(true).call();
// set old/new version in reverse order
List<DiffEntry> difflist = toList(diffOp.setOldVersion(deleteCommit.getId())
.setNewVersion(addCommit.getId()).call());
final String path = NodeRef.appendChild(pointsName, points1.getIdentifier().getID());
// then the diff should report an ADD instead of a DELETE
assertNotNull(difflist);
assertEquals(1, difflist.size());
DiffEntry de = difflist.get(0);
assertNull(de.oldPath());
assertEquals(path, de.newPath());
assertEquals(DiffEntry.ChangeType.ADDED, de.changeType());
assertEquals(ObjectId.NULL, de.oldObjectId());
assertEquals(featureContentId, de.newObjectId());
}
@Test
public void testSingleModification() throws Exception {
final ObjectId oldOid = insertAndAdd(points1);
final RevCommit insertCommit = geogig.command(CommitOp.class).setAll(true).call();
final String featureId = points1.getIdentifier().getID();
final Feature modifiedFeature = feature((SimpleFeatureType) points1.getType(), featureId,
"changedProp", new Integer(1500), null);
final ObjectId newOid = insertAndAdd(modifiedFeature);
final RevCommit changeCommit = geogig.command(CommitOp.class).setAll(true).call();
List<DiffEntry> difflist = toList(diffOp.setOldVersion(insertCommit.getId())
.setNewVersion(changeCommit.getId()).call());
assertNotNull(difflist);
assertEquals(1, difflist.size());
DiffEntry de = difflist.get(0);
String expectedPath = NodeRef.appendChild(pointsName, featureId);
assertEquals(expectedPath, de.newPath());
assertEquals(DiffEntry.ChangeType.MODIFIED, de.changeType());
assertEquals(oldOid, de.oldObjectId());
assertEquals(newOid, de.newObjectId());
}
@Test
public void testFilterNamespaceNoChanges() throws Exception {
// two commits on different trees
insertAndAdd(points1);
final RevCommit commit1 = geogig.command(CommitOp.class).setAll(true).call();
insertAndAdd(lines1);
final RevCommit commit2 = geogig.command(CommitOp.class).setAll(true).call();
diffOp.setOldVersion(commit1.getId()).setNewVersion(commit2.getId());
diffOp.setFilter(pointsName);
Iterator<DiffEntry> diffs = diffOp.call();
assertFalse(diffs.hasNext());
}
@Test
public void testFilterTypeNameNoChanges() throws Exception {
// two commits on different trees
insertAndAdd(points1);
final RevCommit commit1 = geogig.command(CommitOp.class).setAll(true).call();
insertAndAdd(lines1);
final RevCommit commit2 = geogig.command(CommitOp.class).setAll(true).call();
diffOp.setOldVersion(commit1.getId()).setNewVersion(commit2.getId());
diffOp.setFilter(pointsName);
Iterator<DiffEntry> diffs = diffOp.call();
assertFalse(diffs.hasNext());
}
@Test
public void testFilterDidntMatchAnything() throws Exception {
// two commits on different trees
insertAndAdd(points1);
final RevCommit commit1 = geogig.command(CommitOp.class).setAll(true).call();
insertAndAdd(lines1);
final RevCommit commit2 = geogig.command(CommitOp.class).setAll(true).call();
// set a filter that doesn't produce any match
diffOp.setOldVersion(commit1.getId()).setNewVersion(commit2.getId());
diffOp.setFilter(NodeRef.appendChild(pointsName, "nonExistentId"));
Iterator<DiffEntry> diffs = diffOp.call();
assertNotNull(diffs);
assertFalse(diffs.hasNext());
}
@Test
public void testFilterFeatureIdNoChanges() throws Exception {
// two commits on different trees
insertAndAdd(points1);
final RevCommit commit1 = geogig.command(CommitOp.class).setAll(true).call();
insertAndAdd(lines1);
final RevCommit commit2 = geogig.command(CommitOp.class).setAll(true).call();
// filter on feature1_1, it didn't change between commit2 and commit1
diffOp.setOldVersion(commit1.getId()).setNewVersion(commit2.getId());
diffOp.setFilter(NodeRef.appendChild(pointsName, points1.getIdentifier().getID()));
Iterator<DiffEntry> diffs = diffOp.call();
assertFalse(diffs.hasNext());
}
@Test
public void testFilterMatchesSingleBlobChange() throws Exception {
final ObjectId initialOid = insertAndAdd(points1);
final RevCommit commit1 = geogig.command(CommitOp.class).setAll(true).call();
insertAndAdd(lines1);
final RevCommit commit2 = geogig.command(CommitOp.class).setAll(true).call();
((SimpleFeature) points1).setAttribute("sp", "modified");
final ObjectId modifiedOid = insertAndAdd(points1);
final RevCommit commit3 = geogig.command(CommitOp.class).setAll(true).call();
diffOp.setOldVersion(commit1.getId()).setNewVersion(commit3.getId());
diffOp.setFilter(NodeRef.appendChild(pointsName, points1.getIdentifier().getID()));
List<DiffEntry> diffs;
DiffEntry diff;
diffs = toList(diffOp.call());
assertEquals(1, diffs.size());
diff = diffs.get(0);
assertEquals(ChangeType.MODIFIED, diff.changeType());
assertEquals(initialOid, diff.oldObjectId());
assertEquals(modifiedOid, diff.newObjectId());
assertTrue(deleteAndAdd(points1));
final RevCommit commit4 = geogig.command(CommitOp.class).setAll(true).call();
diffOp.setOldVersion(commit2.getId()).setNewVersion(commit4.getId());
diffOp.setFilter(NodeRef.appendChild(pointsName, points1.getIdentifier().getID()));
diffs = toList(diffOp.call());
assertEquals(1, diffs.size());
diff = diffs.get(0);
assertEquals(ChangeType.REMOVED, diff.changeType());
assertEquals(initialOid, diff.oldObjectId());
assertEquals(ObjectId.NULL, diff.newObjectId());
// invert the order of old and new commit
diffOp.setOldVersion(commit4.getId()).setNewVersion(commit1.getId());
diffOp.setFilter(NodeRef.appendChild(pointsName, points1.getIdentifier().getID()));
diffs = toList(diffOp.call());
assertEquals(1, diffs.size());
diff = diffs.get(0);
assertEquals(ChangeType.ADDED, diff.changeType());
assertEquals(ObjectId.NULL, diff.oldObjectId());
assertEquals(initialOid, diff.newObjectId());
// different commit range
diffOp.setOldVersion(commit4.getId()).setNewVersion(commit3.getId());
diffOp.setFilter(NodeRef.appendChild(pointsName, points1.getIdentifier().getID()));
diffs = toList(diffOp.call());
assertEquals(1, diffs.size());
diff = diffs.get(0);
assertEquals(ChangeType.ADDED, diff.changeType());
assertEquals(ObjectId.NULL, diff.oldObjectId());
assertEquals(modifiedOid, diff.newObjectId());
}
// @Test
// public void testFilterAddressesNamespaceTree() throws Exception {
//
// // two commits on different trees
// final ObjectId oid11 = insertAndAdd(points1);
// final ObjectId oid12 = insertAndAdd(points2);
// final RevCommit commit1 = geogig.command(CommitOp.class).setAll(true).call();
//
// final ObjectId oid21 = insertAndAdd(lines1);
// final ObjectId oid22 = insertAndAdd(lines2);
// final RevCommit commit2 = geogig.command(CommitOp.class).setAll(true).call();
//
// List<DiffEntry> diffs;
//
// // filter on namespace1, no changes between commit1 and commit2
// diffOp.setOldVersion(commit1.getId());
// diffOp.setFilter(pointsNs);
//
// diffs = toList(diffOp.call());
// assertEquals(0, diffs.size());
//
// // filter on namespace2, all additions between commit1 and commit2
// diffOp.setOldVersion(commit1.getId());
// diffOp.setFilter(linesNs);
//
// diffs = toList(diffOp.call());
// assertEquals(2, diffs.size());
// assertEquals(ChangeType.ADD, diffs.get(0).getType());
// assertEquals(ChangeType.ADD, diffs.get(1).getType());
//
// assertEquals(ObjectId.NULL, diffs.get(0).getOldObjectId());
// assertEquals(ObjectId.NULL, diffs.get(1).getOldObjectId());
//
// // don't care about order
// Set<ObjectId> expected = new HashSet<ObjectId>();
// expected.add(oid21);
// expected.add(oid22);
// Set<ObjectId> actual = new HashSet<ObjectId>();
// actual.add(diffs.get(0).getNewObjectId());
// actual.add(diffs.get(1).getNewObjectId());
// assertEquals(expected, actual);
// }
@SuppressWarnings("unused")
@Test
public void testMultipleDeletes() throws Exception {
// two commits on different trees
final ObjectId oid11 = insertAndAdd(points1);
final ObjectId oid12 = insertAndAdd(points2);
final ObjectId oid13 = insertAndAdd(points3);
final RevCommit commit1 = geogig.command(CommitOp.class).setAll(true).call();
final ObjectId oid21 = insertAndAdd(lines1);
final RevCommit commit2 = geogig.command(CommitOp.class).setAll(true).call();
deleteAndAdd(points1);
deleteAndAdd(points3);
final RevCommit commit3 = geogig.command(CommitOp.class).setAll(true).call();
List<DiffEntry> diffs;
// filter on namespace1, no changes between commit1 and commit2
diffOp.setOldVersion(commit1.getId()).setNewVersion(commit3.getId());
diffOp.setFilter(pointsName);
diffs = toList(diffOp.call());
assertEquals(2, diffs.size());
assertEquals(ChangeType.REMOVED, diffs.get(0).changeType());
assertEquals(ChangeType.REMOVED, diffs.get(1).changeType());
Set<ObjectId> ids = Sets.newHashSet(diffs.get(0).oldObjectId(), diffs.get(1).oldObjectId());
assertEquals(Sets.newHashSet(oid11, oid13), ids);
}
@SuppressWarnings("unused")
@Test
public void testTreeDeletes() throws Exception {
// two commits on different trees
final ObjectId oid11 = insertAndAdd(points1);
final ObjectId oid12 = insertAndAdd(points2);
final ObjectId oid13 = insertAndAdd(points3);
final RevCommit commit1 = geogig.command(CommitOp.class).setAll(true).call();
final ObjectId oid21 = insertAndAdd(lines1);
final ObjectId oid22 = insertAndAdd(lines2);
final RevCommit commit2 = geogig.command(CommitOp.class).setAll(true).call();
deleteAndAdd(points1);
deleteAndAdd(points2);
deleteAndAdd(points3);
final RevCommit commit3 = geogig.command(CommitOp.class).setAll(true).call();
List<DiffEntry> diffs;
// filter on namespace1, no changes between commit1 and commit2
diffOp.setOldVersion(commit1.getId());
diffOp.setNewVersion(Ref.HEAD);
diffOp.setFilter(pointsName);
diffs = toList(diffOp.call());
assertEquals(3, diffs.size());
}
@Test
public void testReportTreesEmptyTree() throws Exception {
WorkingTree workingTree = geogig.getRepository().workingTree();
workingTree.createTypeTree(linesName, linesType);
List<DiffEntry> difflist = toList(diffOp.setReportTrees(true).setOldVersion(ObjectId.NULL)
.setNewVersion(Ref.WORK_HEAD).call());
assertNotNull(difflist);
assertEquals(1, difflist.size());
DiffEntry de = difflist.get(0);
assertNull(de.getOldObject());
assertNotNull(de.getNewObject());
assertEquals(linesName, de.newPath());
assertEquals(DiffEntry.ChangeType.ADDED, de.changeType());
assertEquals(ObjectId.NULL, de.oldObjectId());
assertFalse(de.getNewObject().getMetadataId().isNull());
}
@Test
public void testReportRename() throws Exception {
insertAndAdd(lines1);
final RevCommit commit1 = geogig.command(CommitOp.class).setAll(true).call();
Feature lines1B = feature(linesType, idL2, "StringProp2_1", new Integer(1000),
"LINESTRING (1 1, 2 2)");
delete(lines1);
// insert(lines2);
WorkingTree workTree = repo.workingTree();
Name name = lines1.getType().getName();
String parentPath = name.getLocalPart();
@SuppressWarnings("unused")
Node ref = workTree.insert(parentPath, lines1B);
geogig.command(AddOp.class).call();
RevCommit commit2 = geogig.command(CommitOp.class).setAll(true).call();
List<DiffEntry> diffs;
diffOp.setOldVersion(commit1.getId());
diffOp.setNewVersion(commit2.getId());
diffs = toList(diffOp.call());
assertEquals(2, diffs.size()); // this is reported as an addition and a removal, with both
// nodes pointing to same ObjectId
assertEquals(diffs.get(0).newObjectId(), diffs.get(1).oldObjectId());
assertEquals(diffs.get(1).newObjectId(), diffs.get(0).oldObjectId());
}
@Test
public void testReportTreesEmptyTreeFromFeatureDeletion() throws Exception {
insert(lines1);
delete(lines1);
List<DiffEntry> difflist = toList(diffOp.setReportTrees(true).setOldVersion(ObjectId.NULL)
.setNewVersion(Ref.WORK_HEAD).call());
assertNotNull(difflist);
assertEquals(1, difflist.size());
assertEquals(linesName, difflist.get(0).newName());
DiffEntry de = difflist.get(0);
assertNull(de.getOldObject());
assertNotNull(de.getNewObject());
assertEquals(linesName, de.newPath());
assertEquals(DiffEntry.ChangeType.ADDED, de.changeType());
assertEquals(ObjectId.NULL, de.oldObjectId());
assertFalse(de.getNewObject().getMetadataId().isNull());
}
@Test
public void testReportTrees() throws Exception {
insert(points1);
insert(lines1);
List<DiffEntry> difflist = toList(diffOp.setReportTrees(true).setOldVersion(ObjectId.NULL)
.setNewVersion(Ref.WORK_HEAD).call());
assertNotNull(difflist);
assertEquals(4, difflist.size());
Set<String> expected = ImmutableSet.of(linesName, pointsName,
NodeRef.appendChild(linesName, idL1), NodeRef.appendChild(pointsName, idP1));
Set<String> actual = Sets.newHashSet(Collections2.transform(difflist,
new Function<DiffEntry, String>() {
@Override
public String apply(DiffEntry input) {
return input.newPath();
}
}));
assertEquals(expected, actual);
}
@Test
public void testChangedFeatureType() throws Exception {
insertAndAdd(points1, points2);
geogig.getRepository().workingTree().updateTypeTree(pointsName, modifiedPointsType);
List<DiffEntry> difflist = toList(diffOp.setReportTrees(true).call());
assertNotNull(difflist);
assertEquals(1, difflist.size());
}
@Test
public void testTreeModifiedByAddingExtraFeature() throws Exception {
insertAndAdd(points1, points2);
insert(points3);
List<DiffEntry> difflist = toList(diffOp.setReportTrees(true).call());
assertNotNull(difflist);
assertEquals(2, difflist.size());
assertEquals(ChangeType.MODIFIED, difflist.get(0).changeType());
assertEquals(TYPE.TREE, difflist.get(0).getOldObject().getType());
assertEquals(ChangeType.ADDED, difflist.get(1).changeType());
assertEquals(TYPE.FEATURE, difflist.get(1).getNewObject().getType());
}
}