/* Copyright (c) 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.api.plumbing.diff;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.locationtech.geogig.api.plumbing.diff.TreeTestSupport.createFeaturesTree;
import static org.locationtech.geogig.api.plumbing.diff.TreeTestSupport.createTreesTree;
import static org.locationtech.geogig.api.plumbing.diff.TreeTestSupport.featureNode;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyInt;
import static org.mockito.Matchers.eq;
import static org.mockito.Matchers.isNull;
import static org.mockito.Matchers.notNull;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
import java.util.concurrent.atomic.AtomicInteger;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import org.locationtech.geogig.api.Bucket;
import org.locationtech.geogig.api.Node;
import org.locationtech.geogig.api.NodeRef;
import org.locationtech.geogig.api.ObjectId;
import org.locationtech.geogig.api.RevObject.TYPE;
import org.locationtech.geogig.api.RevTree;
import org.locationtech.geogig.api.RevTreeBuilder;
import org.locationtech.geogig.api.plumbing.diff.PreOrderDiffWalk.Consumer;
import org.locationtech.geogig.repository.SpatialOps;
import org.locationtech.geogig.storage.ObjectDatabase;
import org.locationtech.geogig.storage.memory.HeapObjectDatabse;
import org.mockito.ArgumentCaptor;
import com.vividsolutions.jts.geom.Envelope;
public class PreOrderDiffWalkTest {
private ObjectDatabase leftSource;
private ObjectDatabase rightSource;
private PreOrderDiffWalk.Consumer consumer;
@Before
public void beforeTest() {
leftSource = new HeapObjectDatabse();
rightSource = new HeapObjectDatabse();
leftSource.open();
rightSource.open();
consumer = mock(Consumer.class);
}
/**
* Creates a root node for the given tree as the one {@link PreOrderDiffWalk} should use to start
* the traversal
*/
private Node nodeFor(RevTree root) {
Envelope bounds = SpatialOps.boundsOf(root);
return Node.create(NodeRef.ROOT, root.getId(), ObjectId.NULL, TYPE.TREE, bounds);
}
@Test
public void testSameRootTree() {
RevTree left = createFeaturesTree(leftSource, "f", 10).build();
RevTree right = left;
PreOrderDiffWalk visitor = new PreOrderDiffWalk(left, right, leftSource, rightSource);
visitor.walk(consumer);
verifyNoMoreInteractions(consumer);
}
@Test
public void testSameChildTree() {
RevTree left = createFeaturesTree(leftSource, "f", 10).build();
RevTree right = left;
PreOrderDiffWalk visitor = new PreOrderDiffWalk(left, right, leftSource, rightSource);
visitor.walk(consumer);
verifyNoMoreInteractions(consumer);
}
@Test
public void testCallsRootNode() {
RevTree left = createFeaturesTree(leftSource, "f", 1).build();
RevTree right = createFeaturesTree(rightSource, "f", 2).build();
PreOrderDiffWalk visitor = new PreOrderDiffWalk(left, right, leftSource, rightSource);
visitor.walk(consumer);
when(consumer.tree(any(Node.class), any(Node.class))).thenReturn(false);
final Node lNode = nodeFor(left);
final Node rNode = nodeFor(right);
ArgumentCaptor<Node> leftNode = ArgumentCaptor.forClass(Node.class);
ArgumentCaptor<Node> rightNode = ArgumentCaptor.forClass(Node.class);
verify(consumer, times(1)).tree(leftNode.capture(), rightNode.capture());
assertEquals(lNode, leftNode.getValue());
assertEquals(rNode, rightNode.getValue());
verify(consumer, times(1)).endTree(leftNode.capture(), rightNode.capture());
verifyNoMoreInteractions(consumer);
}
@Test
public void testLeafLeafTwoAdds() {
// two leaf trees
RevTree left = createFeaturesTree(leftSource, "f", 3).build();
RevTree right = createFeaturesTree(rightSource, "f", 5).build();
PreOrderDiffWalk visitor = new PreOrderDiffWalk(left, right, leftSource, rightSource);
final Node lroot = nodeFor(left);
final Node rroot = nodeFor(right);
when(consumer.tree(eq(lroot), eq(rroot))).thenReturn(true);
visitor.walk(consumer);
verify(consumer, times(1)).tree(eq(lroot), eq(rroot));
ArgumentCaptor<Node> larg = ArgumentCaptor.forClass(Node.class);
ArgumentCaptor<Node> rarg = ArgumentCaptor.forClass(Node.class);
verify(consumer, times(2)).feature(larg.capture(), rarg.capture());
assertEquals(2, larg.getAllValues().size());
assertNull(larg.getAllValues().get(0));
assertNull(larg.getAllValues().get(1));
Node n1 = featureNode("f", 3);// the two added nodes
Node n2 = featureNode("f", 4);
assertTrue(rarg.getAllValues().contains(n1));
assertTrue(rarg.getAllValues().contains(n2));
verify(consumer, times(1)).endTree(eq(lroot), eq(rroot));
verifyNoMoreInteractions(consumer);
}
@Test
public void testLeafLeafTwoRemoves() {
// two leaf trees
RevTree left = createFeaturesTree(leftSource, "f", 5).build();
RevTree right = createFeaturesTree(rightSource, "f", 3).build();
PreOrderDiffWalk visitor = new PreOrderDiffWalk(left, right, leftSource, rightSource);
final Node lroot = nodeFor(left);
final Node rroot = nodeFor(right);
when(consumer.tree(eq(lroot), eq(rroot))).thenReturn(true);
visitor.walk(consumer);
verify(consumer, times(1)).tree(eq(lroot), eq(rroot));
ArgumentCaptor<Node> larg = ArgumentCaptor.forClass(Node.class);
ArgumentCaptor<Node> rarg = ArgumentCaptor.forClass(Node.class);
verify(consumer, times(2)).feature(larg.capture(), rarg.capture());
assertEquals(2, larg.getAllValues().size());
assertNull(rarg.getAllValues().get(0));
assertNull(rarg.getAllValues().get(1));
Node n1 = featureNode("f", 3);// the two added nodes
Node n2 = featureNode("f", 4);
assertTrue(larg.getAllValues().contains(n1));
assertTrue(larg.getAllValues().contains(n2));
verify(consumer, times(1)).endTree(eq(lroot), eq(rroot));
verifyNoMoreInteractions(consumer);
}
@Test
public void testLeafLeafWithSubStrees() {
// two leaf trees
ObjectId metadataId = ObjectId.forString("fake");
RevTree left = createTreesTree(leftSource, 2, 100, metadataId).build();
RevTree right = createTreesTree(rightSource, 3, 100, metadataId).build();
PreOrderDiffWalk visitor = new PreOrderDiffWalk(left, right, leftSource, rightSource);
final Node lroot = nodeFor(left);
final Node rroot = nodeFor(right);
// consume any tree diff
when(consumer.tree(any(Node.class), any(Node.class))).thenReturn(true);
visitor.walk(consumer);
// capture calls to consumer.tree(node, node)
ArgumentCaptor<Node> larg = ArgumentCaptor.forClass(Node.class);
ArgumentCaptor<Node> rarg = ArgumentCaptor.forClass(Node.class);
verify(consumer, times(2)).tree(larg.capture(), rarg.capture());
assertEquals(2, larg.getAllValues().size());
assertEquals("left side arg for the first tree() call is not the left root", lroot, larg
.getAllValues().get(0));
assertNull("left side arg for the second tree() call should be null", larg.getAllValues()
.get(1));
assertEquals(2, rarg.getAllValues().size());
assertEquals(rroot, rarg.getAllValues().get(0));
assertNotNull(rarg.getAllValues().get(1));
verify(consumer, times(100)).feature((Node) isNull(), any(Node.class));
verify(consumer, times(2)).endTree(any(Node.class), any(Node.class));
verifyNoMoreInteractions(consumer);
}
@Test
public void testSkipAddedTree() {
// two leaf trees
ObjectId metadataId = ObjectId.forString("fake");
RevTree left = createTreesTree(leftSource, 2, 10, metadataId).build();
RevTree right = createTreesTree(rightSource, 3, 10, metadataId).build();
PreOrderDiffWalk visitor = new PreOrderDiffWalk(left, right, leftSource, rightSource);
final Node lroot = nodeFor(left);
final Node rroot = nodeFor(right);
// consume the root tree
when(consumer.tree(eq(lroot), eq(rroot))).thenReturn(true);
// but skip the added tree
when(consumer.tree((Node) isNull(), any(Node.class))).thenReturn(false);
visitor.walk(consumer);
// one call to tree() for the root tree, and another for the new subtree
verify(consumer, times(2)).tree(any(Node.class), any(Node.class));
// but no calls to feature() as we returned false on the second call to tree()
verify(consumer, times(0)).feature(any(Node.class), any(Node.class));
verify(consumer, times(2)).endTree(any(Node.class), any(Node.class));
verifyNoMoreInteractions(consumer);
}
@Test
public void testSkipBucket() {
// two bucket trees of depth 2
final int size = RevTree.MAX_BUCKETS * RevTree.NORMALIZED_SIZE_LIMIT;
RevTree left = createFeaturesTree(leftSource, "f", size).build();
RevTree right = createFeaturesTree(rightSource, "f", size, 0, true).build();// all features
// changed
assertDepth(left, leftSource, 2);
assertDepth(right, rightSource, 2);
PreOrderDiffWalk visitor = new PreOrderDiffWalk(left, right, leftSource, rightSource);
final Node lroot = nodeFor(left);
final Node rroot = nodeFor(right);
// consume the root tree
when(consumer.tree(eq(lroot), eq(rroot))).thenReturn(true);
// skip all buckets of depth 0
when(consumer.bucket(anyInt(), eq(0), any(Bucket.class), any(Bucket.class))).thenReturn(
false);
visitor.walk(consumer);
verify(consumer, times(1)).tree(eq(lroot), eq(rroot));
verify(consumer, times(32)).bucket(anyInt(), eq(0), any(Bucket.class), any(Bucket.class));
// should not be any call to consumer.features as we skipped all buckets of depth 0 (which
// point to leaf trees)
verify(consumer, times(0)).feature(any(Node.class), any(Node.class));
verify(consumer, times(32))
.endBucket(anyInt(), eq(0), any(Bucket.class), any(Bucket.class));
verify(consumer, times(1)).endTree(eq(lroot), eq(rroot));
verifyNoMoreInteractions(consumer);
}
@Test
public void testSkipRemovedTree() {
// two leaf trees
ObjectId metadataId = ObjectId.forString("fake");
RevTree left = createTreesTree(leftSource, 3, 10, metadataId).build();
RevTree right = createTreesTree(rightSource, 2, 10, metadataId).build();
PreOrderDiffWalk visitor = new PreOrderDiffWalk(left, right, leftSource, rightSource);
final Node lroot = nodeFor(left);
final Node rroot = nodeFor(right);
// consume the root tree
when(consumer.tree(eq(lroot), eq(rroot))).thenReturn(true);
// but skip the removed tree
when(consumer.tree(any(Node.class), (Node) isNull())).thenReturn(false);
visitor.walk(consumer);
// one call to tree() for the root tree, and another for the removed subtree
verify(consumer, times(2)).tree(any(Node.class), any(Node.class));
// but no calls to feature() as we returned false on the second call to tree()
verify(consumer, times(0)).feature(any(Node.class), any(Node.class));
verify(consumer, times(2)).endTree(any(Node.class), any(Node.class));
verifyNoMoreInteractions(consumer);
}
@Test
public void testLeafLeafChanged() {
// two leaf trees
final RevTree left;
final RevTree right;
final Node nodeChange1 = Node.create("f2", ObjectId.forString("forcechange"),
ObjectId.NULL, TYPE.FEATURE, null);
final Node nodeChange2 = Node.create("f3", ObjectId.forString("fakefake"), ObjectId.NULL,
TYPE.FEATURE, null);
{
left = createFeaturesTree(leftSource, "f", 5).build();
// change two nodes
RevTreeBuilder builder = createFeaturesTree(rightSource, "f", 5);
builder.put(nodeChange1);
builder.put(nodeChange2);
right = builder.build();
}
PreOrderDiffWalk visitor = new PreOrderDiffWalk(left, right, leftSource, rightSource);
when(consumer.tree(any(Node.class), any(Node.class))).thenReturn(true);
visitor.walk(consumer);
// call of the root tree nodes
verify(consumer, times(1)).tree(any(Node.class), any(Node.class));
ArgumentCaptor<Node> larg = ArgumentCaptor.forClass(Node.class);
ArgumentCaptor<Node> rarg = ArgumentCaptor.forClass(Node.class);
verify(consumer, times(2)).feature(larg.capture(), rarg.capture());
assertEquals(2, larg.getAllValues().size());
assertEquals(2, rarg.getAllValues().size());
Node n1 = featureNode("f", 2);// the two added nodes
Node n2 = featureNode("f", 3);
assertTrue(larg.getAllValues().contains(n1));
assertTrue(larg.getAllValues().contains(n2));
assertTrue(rarg.getAllValues().contains(nodeChange1));
assertTrue(rarg.getAllValues().contains(nodeChange2));
verify(consumer, times(1)).endTree(any(Node.class), any(Node.class));
verifyNoMoreInteractions(consumer);
}
@Test
public void testBucketBucketFlat() {
RevTree left = createFeaturesTree(leftSource, "f", RevTree.NORMALIZED_SIZE_LIMIT + 1)
.build();
RevTree right = createFeaturesTree(rightSource, "f", RevTree.NORMALIZED_SIZE_LIMIT + 2)
.build();
PreOrderDiffWalk visitor = new PreOrderDiffWalk(left, right, leftSource, rightSource);
when(consumer.tree(any(Node.class), any(Node.class))).thenReturn(true);
when(consumer.bucket(anyInt(), anyInt(), any(Bucket.class), any(Bucket.class))).thenReturn(
true);
visitor.walk(consumer);
verify(consumer, times(1)).tree(any(Node.class), any(Node.class));
verify(consumer, times(1)).bucket(anyInt(), eq(0), any(Bucket.class), any(Bucket.class));
verify(consumer, times(1)).feature((Node) isNull(), any(Node.class));
verify(consumer, times(1)).endTree(any(Node.class), any(Node.class));
verify(consumer, times(1)).endBucket(anyInt(), eq(0), any(Bucket.class), any(Bucket.class));
verifyNoMoreInteractions(consumer);
}
@Test
public void testBucketBucketFlatMoreDepth() {
RevTree left = createFeaturesTree(leftSource, "f",
RevTree.MAX_BUCKETS * RevTree.NORMALIZED_SIZE_LIMIT).build();
RevTree right = createFeaturesTree(rightSource, "f",
RevTree.MAX_BUCKETS * RevTree.NORMALIZED_SIZE_LIMIT + 1).build();
PreOrderDiffWalk visitor = new PreOrderDiffWalk(left, right, leftSource, rightSource);
when(consumer.tree(any(Node.class), any(Node.class))).thenReturn(true);
when(consumer.bucket(anyInt(), anyInt(), any(Bucket.class), any(Bucket.class))).thenReturn(
true);
visitor.walk(consumer);
verify(consumer, times(1)).tree(any(Node.class), any(Node.class));
// consumer.bucket should be called for depth 0 and then 1
verify(consumer, times(1)).bucket(anyInt(), eq(0), any(Bucket.class), any(Bucket.class));
verify(consumer, times(1)).bucket(anyInt(), eq(1), any(Bucket.class), any(Bucket.class));
verify(consumer, times(1)).feature((Node) isNull(), any(Node.class));
verify(consumer, times(1)).endBucket(anyInt(), eq(0), any(Bucket.class), any(Bucket.class));
verify(consumer, times(1)).endBucket(anyInt(), eq(1), any(Bucket.class), any(Bucket.class));
verify(consumer, times(1)).endTree(any(Node.class), any(Node.class));
verifyNoMoreInteractions(consumer);
}
@Test
public void testBucketLeafSimple() {
final int leftsize = 1 + RevTree.NORMALIZED_SIZE_LIMIT;
RevTree left = createFeaturesTree(leftSource, "f", leftsize).build();
RevTree right = createFeaturesTree(rightSource, "f", 1).build();
PreOrderDiffWalk visitor = new PreOrderDiffWalk(left, right, leftSource, rightSource);
// consume all
when(consumer.tree(any(Node.class), any(Node.class))).thenReturn(true);
when(consumer.bucket(anyInt(), anyInt(), any(Bucket.class), any(Bucket.class))).thenReturn(
true);
visitor.walk(consumer);
// there's only the root tree
verify(consumer, times(1)).tree(any(Node.class), any(Node.class));
// there's only one feature on the right tree, so all right trees features fall on a single
// bucket
final int leftBucketCount = left.buckets().get().size();
final int expectedBucketCalls = leftBucketCount - 1;
verify(consumer, times(expectedBucketCalls)).bucket(anyInt(), eq(0), any(Bucket.class),
any(Bucket.class));
verify(consumer, times(leftsize - 1)).feature(any(Node.class), (Node) isNull());
verify(consumer, times(expectedBucketCalls)).endBucket(anyInt(), eq(0), any(Bucket.class),
any(Bucket.class));
verify(consumer, times(1)).endTree(any(Node.class), any(Node.class));
verifyNoMoreInteractions(consumer);
}
@Test
public void testLeafBucketSimple() {
final int rightsize = 1 + RevTree.NORMALIZED_SIZE_LIMIT;
RevTree left = createFeaturesTree(leftSource, "f", 1).build();
RevTree right = createFeaturesTree(rightSource, "f", rightsize).build();
PreOrderDiffWalk visitor = new PreOrderDiffWalk(left, right, leftSource, rightSource);
// consume all
when(consumer.tree(any(Node.class), any(Node.class))).thenReturn(true);
when(consumer.bucket(anyInt(), anyInt(), any(Bucket.class), any(Bucket.class))).thenReturn(
true);
visitor.walk(consumer);
// there's only the root tree
verify(consumer, times(1)).tree(any(Node.class), any(Node.class));
// there's only one feature on the right tree, so all right trees features fall on a single
// bucket
final int leftBucketCount = right.buckets().get().size();
final int expectedBucketCalls = leftBucketCount - 1;
verify(consumer, times(expectedBucketCalls)).bucket(anyInt(), eq(0), any(Bucket.class),
any(Bucket.class));
verify(consumer, times(rightsize - 1)).feature((Node) isNull(), any(Node.class));
verify(consumer, times(expectedBucketCalls)).endBucket(anyInt(), eq(0), any(Bucket.class),
any(Bucket.class));
verify(consumer, times(1)).endTree(any(Node.class), any(Node.class));
verifyNoMoreInteractions(consumer);
}
@Test
public void testBucketLeafOneLevelDepth() {
final int leftsize = 2 * RevTree.NORMALIZED_SIZE_LIMIT;
final int rightsize = RevTree.NORMALIZED_SIZE_LIMIT;
final int overlapCount = 100;
RevTree left = createFeaturesTree(leftSource, "f", leftsize).build();
assertDepth(left, leftSource, 1);
testBucketLeafDeeper(left, rightsize, overlapCount);
}
@Test
public void testBucketLeafTwoLevelsDepth() {
final int leftsize = RevTree.MAX_BUCKETS * RevTree.NORMALIZED_SIZE_LIMIT;
RevTree left = createFeaturesTree(leftSource, "f", leftsize).build();
assertDepth(left, leftSource, 2);
final int rightsize = RevTree.NORMALIZED_SIZE_LIMIT;
final int overlapCount = 100;
testBucketLeafDeeper(left, rightsize, overlapCount);
}
// goes OOM with the deafult test heap size, but can be manually run with a bigger one
@Ignore
@Test
public void testBucketLeafThreeLevelsDepth() {
final int leftsize = RevTree.MAX_BUCKETS * RevTree.MAX_BUCKETS
* RevTree.NORMALIZED_SIZE_LIMIT;
RevTree left = createFeaturesTree(leftSource, "f", leftsize).build();
assertDepth(left, leftSource, 3);
final int rightsize = RevTree.NORMALIZED_SIZE_LIMIT;
final int overlapCount = 100;
testBucketLeafDeeper(left, rightsize, overlapCount);
}
private void testBucketLeafDeeper(final RevTree left, final int rightsize,
final int overlapCount) {
consumer = mock(Consumer.class);
// left tree has feature nodes "f0" to "f<leftsize-1>"
final int leftsize = (int) left.size();
// the right tree feature node names start at "f<leftsize - 100>", so there's a 100 node
// overlap
RevTree right = createFeaturesTree(rightSource, "f", rightsize, leftsize - overlapCount,
true).build();
PreOrderDiffWalk visitor = new PreOrderDiffWalk(left, right, leftSource, rightSource);
// consume all
when(consumer.tree(any(Node.class), any(Node.class))).thenReturn(true);
when(consumer.bucket(anyInt(), anyInt(), any(Bucket.class), any(Bucket.class))).thenReturn(
true);
visitor.walk(consumer);
// there's only the root tree
verify(consumer, times(1)).tree(any(Node.class), any(Node.class));
// there shall be <overlapCount> calls to feature with both non null args
verify(consumer, times(overlapCount)).feature((Node) notNull(), (Node) notNull());
final int expectedDeletes = leftsize - overlapCount;
final int expectedAdds = rightsize - overlapCount;
verify(consumer, times(expectedDeletes)).feature((Node) notNull(), (Node) isNull());
verify(consumer, times(expectedAdds)).feature((Node) isNull(), (Node) notNull());
}
@Test
public void testLeafBucketOneLevelDepth() {
final int leftsize = RevTree.NORMALIZED_SIZE_LIMIT;
final int rightsize = 2 * RevTree.NORMALIZED_SIZE_LIMIT;
final int overlapCount = 100;
RevTree right = createFeaturesTree(rightSource, "f", rightsize).build();
assertDepth(right, rightSource, 1);
testLeafBucketDeeper(leftsize, right, overlapCount);
}
@Test
public void testLeafBucketTwoLevelsDepth() {
final int leftsize = RevTree.NORMALIZED_SIZE_LIMIT;
final int rightsize = RevTree.MAX_BUCKETS * RevTree.NORMALIZED_SIZE_LIMIT;
final int overlapCount = 100;
RevTree right = createFeaturesTree(rightSource, "f", rightsize).build();
assertDepth(right, rightSource, 2);
testLeafBucketDeeper(leftsize, right, overlapCount);
}
private void testLeafBucketDeeper(final int leftsize, final RevTree rightRoot,
final int overlapCount) {
consumer = mock(Consumer.class);
// right tree has feature nodes "f0" to "f<rightsize-1>"
final int rightsize = (int) rightRoot.size();
// the left tree feature node names start at "f<rightsize - 100>", so there's a 100 node
// overlap
RevTree leftRoot = createFeaturesTree(leftSource, "f", leftsize, rightsize - overlapCount,
true).build();
PreOrderDiffWalk visitor = new PreOrderDiffWalk(leftRoot, rightRoot, leftSource, rightSource);
// consume all
when(consumer.tree(any(Node.class), any(Node.class))).thenReturn(true);
when(consumer.bucket(anyInt(), anyInt(), any(Bucket.class), any(Bucket.class))).thenReturn(
true);
visitor.walk(consumer);
// there's only the root tree
verify(consumer, times(1)).tree(any(Node.class), any(Node.class));
// there shall be <overlapCount> calls to feature with both non null args
verify(consumer, times(overlapCount)).feature((Node) notNull(), (Node) notNull());
final int expectedDeletes = leftsize - overlapCount;
final int expectedAdds = rightsize - overlapCount;
verify(consumer, times(expectedDeletes)).feature((Node) notNull(), (Node) isNull());
verify(consumer, times(expectedAdds)).feature((Node) isNull(), (Node) notNull());
}
private void assertDepth(RevTree tree, ObjectDatabase source, int expectedDepth) {
int depth = getTreeDepth(tree, source, 0);
assertEquals(expectedDepth, depth);
}
/**
*
*/
private int getTreeDepth(RevTree tree, ObjectDatabase source, final int depth) {
PreOrderDiffWalk visitor = new PreOrderDiffWalk(tree, RevTree.EMPTY, source, source);
final AtomicInteger maxDepth = new AtomicInteger();
visitor.walk(new Consumer() {
@Override
public boolean tree(Node left, Node right) {
return true;
}
@Override
public void feature(Node left, Node right) {
//
}
@Override
public boolean bucket(int bucketIndex, int bucketDepth, Bucket left, Bucket right) {
maxDepth.set(Math.max(maxDepth.get(), bucketDepth + 1));// use +1 cause we want the
// number of levels, not the
// zero-based level index
return true;
}
@Override
public void endTree(Node left, Node right) {
// TODO Auto-generated method stub
}
@Override
public void endBucket(int bucketIndex, int bucketDepth, Bucket left, Bucket right) {
// TODO Auto-generated method stub
}
});
return maxDepth.get();
}
@Test
public void testBucketLeafSeveral() {
final int leftsize = 1 + RevTree.NORMALIZED_SIZE_LIMIT;
RevTree left = createFeaturesTree(leftSource, "f", leftsize).build();
RevTree right = createFeaturesTree(rightSource, "f", 1).build();
PreOrderDiffWalk visitor = new PreOrderDiffWalk(left, right, leftSource, rightSource);
// consume all
when(consumer.tree(any(Node.class), any(Node.class))).thenReturn(true);
when(consumer.bucket(anyInt(), anyInt(), any(Bucket.class), any(Bucket.class))).thenReturn(
true);
visitor.walk(consumer);
// there's only the root tree
verify(consumer, times(1)).tree(any(Node.class), any(Node.class));
// there's only one feature on the right tree, so all right trees features fall on a single
// bucket
final int leftBucketCount = left.buckets().get().size();
final int expectedBucketCalls = leftBucketCount - 1;
verify(consumer, times(expectedBucketCalls)).bucket(anyInt(), eq(0), any(Bucket.class),
any(Bucket.class));
verify(consumer, times(leftsize - 1)).feature(any(Node.class), (Node) isNull());
verify(consumer, times(expectedBucketCalls)).endBucket(anyInt(), eq(0), any(Bucket.class),
any(Bucket.class));
verify(consumer, times(1)).endTree(any(Node.class), any(Node.class));
verifyNoMoreInteractions(consumer);
}
}