blob: a9cf383805be36c3792b794c8a66922b30c1d1ef [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.cassandra.utils.btree;
import static org.apache.cassandra.utils.btree.BTreeRemoval.remove;
import static org.junit.Assert.assertArrayEquals;
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 java.util.Comparator;
import java.util.Random;
import java.util.SortedSet;
import java.util.TreeSet;
import org.junit.Test;
import com.google.common.collect.Iterables;
public class BTreeRemovalTest
{
static
{
System.setProperty("cassandra.btree.fanfactor", "8");
}
private static final Comparator<Integer> CMP = new Comparator<Integer>()
{
public int compare(Integer o1, Integer o2)
{
return Integer.compare(o1, o2);
}
};
private static Object[] copy(final Object[] btree)
{
final Object[] result = new Object[btree.length];
System.arraycopy(btree, 0, result, 0, btree.length);
if (!BTree.isLeaf(btree))
{
for (int i = BTree.getChildStart(btree); i < BTree.getChildEnd(btree); ++i)
result[i] = copy((Object[]) btree[i]);
final int[] sizeMap = BTree.getSizeMap(btree);
final int[] resultSizeMap = new int[sizeMap.length];
System.arraycopy(sizeMap, 0, resultSizeMap, 0, sizeMap.length);
result[result.length - 1] = resultSizeMap;
}
return result;
}
private static Object[] assertRemove(final Object[] btree, final int key)
{
final Object[] btreeBeforeRemoval = copy(btree);
final Object[] result = remove(btree, CMP, key);
assertBTree(btreeBeforeRemoval, btree);
assertTrue(BTree.isWellFormed(result, CMP));
assertEquals(BTree.size(btree) - 1, BTree.size(result));
assertNull(BTree.find(result, CMP, key));
for (Integer k : BTree.<Integer>iterable(btree))
if (k != key)
assertNotNull(BTree.find(result, CMP, k));
return result;
}
private static void assertBTree(final Object[] expected, final Object[] result)
{
assertEquals(BTree.isEmpty(expected), BTree.isEmpty(result));
assertEquals(BTree.isLeaf(expected), BTree.isLeaf(result));
assertEquals(expected.length, result.length);
if (BTree.isLeaf(expected))
{
assertArrayEquals(expected, result);
}
else
{
for (int i = 0; i < BTree.getBranchKeyEnd(expected); ++i)
assertEquals(expected[i], result[i]);
for (int i = BTree.getChildStart(expected); i < BTree.getChildEnd(expected); ++i)
assertBTree((Object[]) expected[i], (Object[]) result[i]);
assertArrayEquals(BTree.getSizeMap(expected), BTree.getSizeMap(result));
}
}
private static Object[] generateLeaf(int from, int size)
{
final Object[] result = new Object[(size & 1) == 1 ? size : size + 1];
for (int i = 0; i < size; ++i)
result[i] = from + i;
return result;
}
private static Object[] generateBranch(int[] keys, Object[][] children)
{
assert keys.length > 0;
assert children.length > 1;
assert children.length == keys.length + 1;
final Object[] result = new Object[keys.length + children.length + 1];
for (int i = 0; i < keys.length; ++i)
result[i] = keys[i];
for (int i = 0; i < children.length; ++i)
result[keys.length + i] = children[i];
final int[] sizeMap = new int[children.length];
sizeMap[0] = BTree.size(children[0]);
for (int i = 1; i < children.length; ++i)
sizeMap[i] = sizeMap[i - 1] + BTree.size(children[i]) + 1;
result[result.length - 1] = sizeMap;
return result;
}
private static Object[] generateSampleTwoLevelsTree(final int[] leafSizes)
{
assert leafSizes.length > 1;
final Object[][] leaves = new Object[leafSizes.length][];
for (int i = 0; i < leaves.length; ++i)
leaves[i] = generateLeaf(10 * i + 1, leafSizes[i]);
final int[] keys = new int[leafSizes.length - 1];
for (int i = 0; i < keys.length; ++i)
keys[i] = 10 * (i + 1);
final Object[] btree = generateBranch(keys, leaves);
assertTrue(BTree.isWellFormed(btree, CMP));
return btree;
}
private static Object[] generateSampleThreeLevelsTree(final int[] middleNodeSizes)
{
assert middleNodeSizes.length > 1;
final Object[][] middleNodes = new Object[middleNodeSizes.length][];
for (int i = 0; i < middleNodes.length; ++i)
{
final Object[][] leaves = new Object[middleNodeSizes[i]][];
for (int j = 0; j < middleNodeSizes[i]; ++j)
leaves[j] = generateLeaf(100 * i + 10 * j + 1, 4);
final int[] keys = new int[middleNodeSizes[i] - 1];
for (int j = 0; j < keys.length; ++j)
keys[j] = 100 * i + 10 * (j + 1);
middleNodes[i] = generateBranch(keys, leaves);
}
final int[] keys = new int[middleNodeSizes.length - 1];
for (int i = 0; i < keys.length; ++i)
keys[i] = 100 * (i + 1);
final Object[] btree = generateBranch(keys, middleNodes);
assertTrue(BTree.isWellFormed(btree, CMP));
return btree;
}
@Test
public void testRemoveFromEmpty()
{
assertBTree(BTree.empty(), remove(BTree.empty(), CMP, 1));
}
@Test
public void testRemoveNonexistingElement()
{
final Object[] btree = new Object[] {1, 2, 3, 4, null};
assertBTree(btree, remove(btree, CMP, 5));
}
@Test
public void testRemoveLastElement()
{
final Object[] btree = new Object[] {1};
assertBTree(BTree.empty(), remove(btree, CMP, 1));
}
@Test
public void testRemoveFromRootWhichIsALeaf()
{
for (int size = 1; size < 9; ++size)
{
final Object[] btree = new Object[(size & 1) == 1 ? size : size + 1];
for (int i = 0; i < size; ++i)
btree[i] = i + 1;
for (int i = 0; i < size; ++i)
{
final Object[] result = remove(btree, CMP, i + 1);
assertTrue("size " + size, BTree.isWellFormed(result, CMP));
for (int j = 0; j < i; ++j)
assertEquals("size " + size + "elem " + j, btree[j], result[j]);
for (int j = i; j < size - 1; ++j)
assertEquals("size " + size + "elem " + j, btree[j + 1], result[j]);
for (int j = size - 1; j < result.length; ++j)
assertNull("size " + size + "elem " + j, result[j]);
}
{
final Object[] result = remove(btree, CMP, 0);
assertTrue("size " + size, BTree.isWellFormed(result, CMP));
assertBTree(btree, result);
}
{
final Object[] result = remove(btree, CMP, size + 1);
assertTrue("size " + size, BTree.isWellFormed(result, CMP));
assertBTree(btree, result);
}
}
}
@Test
public void testRemoveFromNonMinimalLeaf()
{
for (int size = 5; size < 9; ++size)
{
final Object[] btree = generateSampleTwoLevelsTree(new int[] {size, 4, 4, 4, 4});
for (int i = 1; i < size + 1; ++i)
assertRemove(btree, i);
}
}
@Test
public void testRemoveFromMinimalLeafRotateLeft()
{
final Object[] btree = generateSampleTwoLevelsTree(new int[] {4, 5, 5, 5, 5});
for (int i = 11; i < 15; ++i)
assertRemove(btree, i);
}
@Test
public void testRemoveFromMinimalLeafRotateRight1()
{
final Object[] btree = generateSampleTwoLevelsTree(new int[] {4, 5, 5, 5, 5});
for (int i = 1; i < 5; ++i)
assertRemove(btree, i);
}
@Test
public void testRemoveFromMinimalLeafRotateRight2()
{
final Object[] btree = generateSampleTwoLevelsTree(new int[] {4, 4, 5, 5, 5});
for (int i = 11; i < 15; ++i)
assertRemove(btree, i);
}
@Test
public void testRemoveFromMinimalLeafMergeWithLeft1()
{
final Object[] btree = generateSampleTwoLevelsTree(new int[] {4, 4, 4, 4, 4});
for (int i = 11; i < 15; ++i)
assertRemove(btree, i);
}
@Test
public void testRemoveFromMinimalLeafMergeWithLeft2()
{
final Object[] btree = generateSampleTwoLevelsTree(new int[] {4, 4, 4, 4, 4});
for (int i = 41; i < 45; ++i)
assertRemove(btree, i);
}
@Test
public void testRemoveFromMinimalLeafMergeWithRight()
{
final Object[] btree = generateSampleTwoLevelsTree(new int[] {4, 4, 4, 4, 4});
for (int i = 1; i < 5; ++i)
assertRemove(btree, i);
}
@Test
public void testRemoveFromMinimalLeafWhenSingleKeyRootMergeWithLeft()
{
final Object[] btree = generateSampleTwoLevelsTree(new int[] {4, 4});
for (int i = 1; i < 5; ++i)
assertRemove(btree, i);
}
@Test
public void testRemoveFromMinimalLeafWhenSingleKeyRootMergeWithRight()
{
final Object[] btree = generateSampleTwoLevelsTree(new int[] {4, 4});
for (int i = 11; i < 15; ++i)
assertRemove(btree, i);
}
@Test
public void testRemoveFromMinimalLeafWithBranchLeftRotation()
{
final Object[] btree = generateSampleThreeLevelsTree(new int[] {6, 5, 5, 5, 5});
for (int i = 101; i < 105; ++i)
assertRemove(btree, i);
}
@Test
public void testRemoveFromMinimalLeafWithBranchRightRotation1()
{
final Object[] btree = generateSampleThreeLevelsTree(new int[] {5, 6, 5, 5, 5});
for (int i = 1; i < 5; ++i)
assertRemove(btree, i);
}
@Test
public void testRemoveFromMinimalLeafWithBranchRightRotation2()
{
final Object[] btree = generateSampleThreeLevelsTree(new int[] {5, 5, 6, 5, 5});
for (int i = 101; i < 105; ++i)
assertRemove(btree, i);
}
@Test
public void testRemoveFromMinimalLeafWithBranchMergeWithLeft1()
{
final Object[] btree = generateSampleThreeLevelsTree(new int[] {5, 5, 5, 5, 5});
for (int i = 101; i < 105; ++i)
assertRemove(btree, i);
}
@Test
public void testRemoveFromMinimalLeafWithBranchMergeWithLeft2()
{
final Object[] btree = generateSampleThreeLevelsTree(new int[] {5, 5, 5, 5, 5});
for (int i = 401; i < 405; ++i)
assertRemove(btree, i);
}
@Test
public void testRemoveFromMinimalLeafWithBranchMergeWithRight()
{
final Object[] btree = generateSampleThreeLevelsTree(new int[] {5, 5, 5, 5, 5});
for (int i = 1; i < 5; ++i)
assertRemove(btree, i);
}
@Test
public void testRemoveFromMiddleBranch()
{
final Object[] btree = generateSampleThreeLevelsTree(new int[] {5, 5, 5, 5, 5});
for (int i = 10; i < 50; i += 10)
assertRemove(btree, i);
}
@Test
public void testRemoveFromRootBranch()
{
final Object[] btree = generateSampleThreeLevelsTree(new int[] {5, 5, 5, 5, 5});
for (int i = 100; i < 500; i += 100)
assertRemove(btree, i);
}
@Test
public void randomizedTest()
{
Random rand = new Random(2);
SortedSet<Integer> data = new TreeSet<>();
for (int i = 0; i < 1000; ++i)
data.add(rand.nextInt());
Object[] btree = BTree.build(data, UpdateFunction.<Integer>noOp());
assertTrue(BTree.isWellFormed(btree, CMP));
assertTrue(Iterables.elementsEqual(data, BTree.iterable(btree)));
while (btree != BTree.empty())
{
int idx = rand.nextInt(BTree.size(btree));
Integer val = BTree.findByIndex(btree, idx);
assertTrue(data.remove(val));
btree = assertRemove(btree, val);
}
}
}