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