/* * Copyright (c) 2014, the Dart project authors. * * Licensed under the Eclipse Public License v1.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.eclipse.org/legal/epl-v10.html * * 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 com.google.dart.engine.internal.index.structure.btree; import com.google.common.collect.Sets; import junit.framework.TestCase; import org.apache.commons.lang3.StringUtils; import java.util.Comparator; import java.util.Set; public class BPlusTreeTest extends TestCase { private static final Comparator<Integer> INT_COMPARATOR = new Comparator<Integer>() { @Override public int compare(Integer o1, Integer o2) { return o1.compareTo(o2); } }; private NodeManager<Integer, String, Integer> nodeManager; private BPlusTree<Integer, String, Integer> tree; public void test_find() throws Exception { insertValues(12); assertEquals(null, tree.find(-1)); assertEquals(null, tree.find(1000)); for (int key = 0; key < 12; key++) { assertEquals("V" + key, tree.find(key)); } } public void test_insert_01() throws Exception { tree.insert(0, "V0"); assertTreeDump("LNode {0: V0}"); } public void test_insert_02() throws Exception { tree.insert(1, "V1"); tree.insert(0, "V0"); assertTreeDump("LNode {0: V0, 1: V1}"); } public void test_insert_03() throws Exception { tree.insert(2, "V2"); tree.insert(0, "V0"); tree.insert(1, "V1"); assertTreeDump("LNode {0: V0, 1: V1, 2: V2}"); } public void test_insert_05() throws Exception { insertValues(5); assertTreeDump( "INode {", " LNode {0: V0, 1: V1}", " 2", " LNode {2: V2, 3: V3, 4: V4}", "}"); } public void test_insert_09() throws Exception { insertValues(9); assertTreeDump( "INode {", " LNode {0: V0, 1: V1}", " 2", " LNode {2: V2, 3: V3}", " 4", " LNode {4: V4, 5: V5}", " 6", " LNode {6: V6, 7: V7, 8: V8}", "}"); } public void test_insert_innerSplitLeft() throws Exception { // Prepare a tree with '0' key missing. for (int i = 1; i < 12; i++) { tree.insert(i, "V" + i); } assertTreeDump( "INode {", " LNode {1: V1, 2: V2}", " 3", " LNode {3: V3, 4: V4}", " 5", " LNode {5: V5, 6: V6}", " 7", " LNode {7: V7, 8: V8}", " 9", " LNode {9: V9, 10: V10, 11: V11}", "}"); // Split and insert into the 'left' child. tree.insert(0, "V0"); assertTreeDump( "INode {", " INode {", " LNode {0: V0, 1: V1, 2: V2}", " 3", " LNode {3: V3, 4: V4}", " 5", " LNode {5: V5, 6: V6}", " }", " 7", " INode {", " LNode {7: V7, 8: V8}", " 9", " LNode {9: V9, 10: V10, 11: V11}", " }", "}"); } public void test_insert_innerSplitRight() throws Exception { insertValues(12); assertTreeDump( "INode {", " INode {", " LNode {0: V0, 1: V1}", " 2", " LNode {2: V2, 3: V3}", " 4", " LNode {4: V4, 5: V5}", " }", " 6", " INode {", " LNode {6: V6, 7: V7}", " 8", " LNode {8: V8, 9: V9, 10: V10, 11: V11}", " }", "}"); } public void test_insert_replace() throws Exception { tree.insert(0, "A"); tree.insert(1, "B"); tree.insert(2, "C"); assertTreeDump("LNode {0: A, 1: B, 2: C}"); tree.insert(2, "C2"); tree.insert(1, "B2"); tree.insert(0, "A2"); assertTreeDump("LNode {0: A2, 1: B2, 2: C2}"); } public void test_remove_internal_borrowLeft() throws Exception { createTree(10, 4); for (int i = 100; i < 125; i++) { tree.insert(i, "V" + i); } for (int i = 0; i < 10; i++) { tree.insert(i, "V" + i); } assertTreeDump( "INode {", " INode {", " LNode {0: V0, 1: V1}", " 2", " LNode {2: V2, 3: V3}", " 4", " LNode {4: V4, 5: V5}", " 6", " LNode {6: V6, 7: V7}", " 8", " LNode {8: V8, 9: V9, 100: V100, 101: V101}", " 102", " LNode {102: V102, 103: V103}", " 104", " LNode {104: V104, 105: V105}", " 106", " LNode {106: V106, 107: V107}", " 108", " LNode {108: V108, 109: V109}", " 110", " LNode {110: V110, 111: V111}", " }", " 112", " INode {", " LNode {112: V112, 113: V113}", " 114", " LNode {114: V114, 115: V115}", " 116", " LNode {116: V116, 117: V117}", " 118", " LNode {118: V118, 119: V119}", " 120", " LNode {120: V120, 121: V121}", " 122", " LNode {122: V122, 123: V123, 124: V124}", " }", "}"); assertEquals("V112", tree.remove(112)); assertTreeDump( "INode {", " INode {", " LNode {0: V0, 1: V1}", " 2", " LNode {2: V2, 3: V3}", " 4", " LNode {4: V4, 5: V5}", " 6", " LNode {6: V6, 7: V7}", " 8", " LNode {8: V8, 9: V9, 100: V100, 101: V101}", " 102", " LNode {102: V102, 103: V103}", " 104", " LNode {104: V104, 105: V105}", " }", " 106", " INode {", " LNode {106: V106, 107: V107}", " 108", " LNode {108: V108, 109: V109}", " 110", " LNode {110: V110, 111: V111}", " 112", " LNode {113: V113, 114: V114, 115: V115}", " 116", " LNode {116: V116, 117: V117}", " 118", " LNode {118: V118, 119: V119}", " 120", " LNode {120: V120, 121: V121}", " 122", " LNode {122: V122, 123: V123, 124: V124}", " }", "}"); } public void test_remove_internal_borrowRight() throws Exception { createTree(10, 4); for (int i = 100; i < 135; i++) { tree.insert(i, "V" + i); } assertTreeDump( "INode {", " INode {", " LNode {100: V100, 101: V101}", " 102", " LNode {102: V102, 103: V103}", " 104", " LNode {104: V104, 105: V105}", " 106", " LNode {106: V106, 107: V107}", " 108", " LNode {108: V108, 109: V109}", " 110", " LNode {110: V110, 111: V111}", " }", " 112", " INode {", " LNode {112: V112, 113: V113}", " 114", " LNode {114: V114, 115: V115}", " 116", " LNode {116: V116, 117: V117}", " 118", " LNode {118: V118, 119: V119}", " 120", " LNode {120: V120, 121: V121}", " 122", " LNode {122: V122, 123: V123}", " 124", " LNode {124: V124, 125: V125}", " 126", " LNode {126: V126, 127: V127}", " 128", " LNode {128: V128, 129: V129}", " 130", " LNode {130: V130, 131: V131}", " 132", " LNode {132: V132, 133: V133, 134: V134}", " }", "}"); assertEquals("V100", tree.remove(100)); assertTreeDump( "INode {", " INode {", " LNode {101: V101, 102: V102, 103: V103}", " 104", " LNode {104: V104, 105: V105}", " 106", " LNode {106: V106, 107: V107}", " 108", " LNode {108: V108, 109: V109}", " 110", " LNode {110: V110, 111: V111}", " 112", " LNode {112: V112, 113: V113}", " 114", " LNode {114: V114, 115: V115}", " 116", " LNode {116: V116, 117: V117}", " }", " 118", " INode {", " LNode {118: V118, 119: V119}", " 120", " LNode {120: V120, 121: V121}", " 122", " LNode {122: V122, 123: V123}", " 124", " LNode {124: V124, 125: V125}", " 126", " LNode {126: V126, 127: V127}", " 128", " LNode {128: V128, 129: V129}", " 130", " LNode {130: V130, 131: V131}", " 132", " LNode {132: V132, 133: V133, 134: V134}", " }", "}"); } public void test_remove_internal_mergeLeft() throws Exception { insertValues(15); assertTreeDump( "INode {", " INode {", " LNode {0: V0, 1: V1}", " 2", " LNode {2: V2, 3: V3}", " 4", " LNode {4: V4, 5: V5}", " }", " 6", " INode {", " LNode {6: V6, 7: V7}", " 8", " LNode {8: V8, 9: V9}", " 10", " LNode {10: V10, 11: V11}", " 12", " LNode {12: V12, 13: V13, 14: V14}", " }", "}"); assertEquals("V12", tree.remove(12)); assertEquals("V13", tree.remove(13)); assertEquals("V14", tree.remove(14)); assertTreeDump( "INode {", " INode {", " LNode {0: V0, 1: V1}", " 2", " LNode {2: V2, 3: V3}", " 4", " LNode {4: V4, 5: V5}", " }", " 6", " INode {", " LNode {6: V6, 7: V7}", " 8", " LNode {8: V8, 9: V9}", " 10", " LNode {10: V10, 11: V11}", " }", "}"); assertEquals("V8", tree.remove(8)); assertTreeDump( "INode {", " LNode {0: V0, 1: V1}", " 2", " LNode {2: V2, 3: V3}", " 4", " LNode {4: V4, 5: V5}", " 6", " LNode {6: V6, 7: V7, 9: V9}", " 10", " LNode {10: V10, 11: V11}", "}"); } public void test_remove_internal_mergeRight() throws Exception { insertValues(12); assertTreeDump( "INode {", " INode {", " LNode {0: V0, 1: V1}", " 2", " LNode {2: V2, 3: V3}", " 4", " LNode {4: V4, 5: V5}", " }", " 6", " INode {", " LNode {6: V6, 7: V7}", " 8", " LNode {8: V8, 9: V9, 10: V10, 11: V11}", " }", "}"); assertEquals("V0", tree.remove(0)); assertTreeDump( "INode {", " LNode {1: V1, 2: V2, 3: V3}", " 4", " LNode {4: V4, 5: V5}", " 6", " LNode {6: V6, 7: V7}", " 8", " LNode {8: V8, 9: V9, 10: V10, 11: V11}", "}"); } public void test_remove_internal_notFound() throws Exception { insertValues(20); assertEquals(null, tree.remove(100)); } public void test_remove_leaf_borrowLeft() throws Exception { createTree(10, 10); for (int i = 20; i < 40; i++) { tree.insert(i, "V" + i); } for (int i = 0; i < 5; i++) { tree.insert(i, "V" + i); } assertTreeDump( "INode {", " LNode {0: V0, 1: V1, 2: V2, 3: V3, 4: V4, 20: V20, 21: V21, 22: V22, 23: V23, 24: V24}", " 25", " LNode {25: V25, 26: V26, 27: V27, 28: V28, 29: V29}", " 30", " LNode {30: V30, 31: V31, 32: V32, 33: V33, 34: V34, 35: V35, 36: V36, 37: V37, 38: V38, 39: V39}", "}"); assertEquals("V25", tree.remove(25)); assertTreeDump( "INode {", " LNode {0: V0, 1: V1, 2: V2, 3: V3, 4: V4, 20: V20, 21: V21}", " 22", " LNode {22: V22, 23: V23, 24: V24, 26: V26, 27: V27, 28: V28, 29: V29}", " 30", " LNode {30: V30, 31: V31, 32: V32, 33: V33, 34: V34, 35: V35, 36: V36, 37: V37, 38: V38, 39: V39}", "}"); } public void test_remove_leaf_borrowRight() throws Exception { createTree(10, 10); insertValues(15); assertTreeDump( "INode {", " LNode {0: V0, 1: V1, 2: V2, 3: V3, 4: V4}", " 5", " LNode {5: V5, 6: V6, 7: V7, 8: V8, 9: V9, 10: V10, 11: V11, 12: V12, 13: V13, 14: V14}", "}"); assertEquals("V0", tree.remove(0)); assertTreeDump( "INode {", " LNode {1: V1, 2: V2, 3: V3, 4: V4, 5: V5, 6: V6, 7: V7}", " 8", " LNode {8: V8, 9: V9, 10: V10, 11: V11, 12: V12, 13: V13, 14: V14}", "}"); } public void test_remove_leaf_mergeLeft() throws Exception { insertValues(9); assertTreeDump( "INode {", " LNode {0: V0, 1: V1}", " 2", " LNode {2: V2, 3: V3}", " 4", " LNode {4: V4, 5: V5}", " 6", " LNode {6: V6, 7: V7, 8: V8}", "}"); assertEquals("V2", tree.remove(2)); assertTreeDump( "INode {", " LNode {0: V0, 1: V1, 3: V3}", " 4", " LNode {4: V4, 5: V5}", " 6", " LNode {6: V6, 7: V7, 8: V8}", "}"); } public void test_remove_leaf_mergeRight() throws Exception { insertValues(9); assertTreeDump( "INode {", " LNode {0: V0, 1: V1}", " 2", " LNode {2: V2, 3: V3}", " 4", " LNode {4: V4, 5: V5}", " 6", " LNode {6: V6, 7: V7, 8: V8}", "}"); assertEquals("V1", tree.remove(1)); assertTreeDump( "INode {", " LNode {0: V0, 2: V2, 3: V3}", " 4", " LNode {4: V4, 5: V5}", " 6", " LNode {6: V6, 7: V7, 8: V8}", "}"); } public void test_remove_leaf_noReorder() throws Exception { insertValues(5); assertTreeDump( "INode {", " LNode {0: V0, 1: V1}", " 2", " LNode {2: V2, 3: V3, 4: V4}", "}"); assertEquals("V3", tree.remove(3)); assertTreeDump(// "INode {", " LNode {0: V0, 1: V1}", " 2", " LNode {2: V2, 4: V4}", "}"); } public void test_remove_leafRoot_becomesEmpty() throws Exception { insertValues(1); assertTreeDump("LNode {0: V0}"); assertEquals("V0", tree.remove(0)); assertTreeDump("LNode {}"); } public void test_remove_leafRoot_first() throws Exception { insertValues(3); assertTreeDump("LNode {0: V0, 1: V1, 2: V2}"); assertEquals("V0", tree.remove(0)); assertTreeDump("LNode {1: V1, 2: V2}"); } public void test_remove_leafRoot_last() throws Exception { insertValues(3); assertTreeDump("LNode {0: V0, 1: V1, 2: V2}"); assertEquals("V2", tree.remove(2)); assertTreeDump("LNode {0: V0, 1: V1}"); } public void test_remove_leafRoot_middle() throws Exception { insertValues(3); assertTreeDump("LNode {0: V0, 1: V1, 2: V2}"); assertEquals("V1", tree.remove(1)); assertTreeDump("LNode {0: V0, 2: V2}"); } public void test_stress_evenOdd() throws Exception { int count = 1000; // insert odd, forward for (int i = 1; i < count; i += 2) { tree.insert(i, "V" + i); } // insert even, backward for (int i = count - 2; i >= 0; i -= 2) { tree.insert(i, "V" + i); } // find every for (int i = 0; i < count; i++) { assertEquals("V" + i, tree.find(i)); } // remove odd, backward for (int i = count - 1; i >= 1; i -= 2) { assertEquals("V" + i, tree.remove(i)); } for (int i = 0; i < count; i++) { if ((i % 2) == 0) { assertEquals("V" + i, tree.find(i)); } else { assertEquals(null, tree.find(i)); } } // remove even, forward for (int i = 0; i < count; i += 2) { tree.remove(i); } for (int i = 0; i < count; i++) { assertEquals(null, tree.find(i)); } } public void test_stress_random() throws Exception { createTree(10, 10); int maxKey = 1000000; int tryCount = 1000; Set<Integer> keys = Sets.newHashSet(); for (int i = 0; i < tryCount; i++) { int key = (int) (Math.random() * maxKey); keys.add(key); tree.insert(key, "V" + key); } // find every for (int key : keys) { assertEquals("V" + key, tree.find(key)); } // remove random keys for (int key : Sets.newHashSet(keys)) { if (Math.random() > 0.5) { keys.remove(key); assertEquals("V" + key, tree.remove(key)); } } // find every remaining key for (int key : keys) { assertEquals("V" + key, tree.find(key)); } } @Override protected void setUp() throws Exception { createTree(4, 4); } private void assertTreeDump(String... lines) { String dump = getDump(); assertEquals(StringUtils.join(lines, "\n") + "\n", dump); } private void createTree(int maxIndexKeys, int maxLeafKeys) { nodeManager = new MemoryNodeManager<Integer, String>(maxIndexKeys, maxLeafKeys); tree = new BPlusTree<Integer, String, Integer>(INT_COMPARATOR, nodeManager); } private String getDump() { StringBuilder sb = new StringBuilder(); tree.writeOn(sb); String dump = sb.toString(); return dump; } private void insertValues(int count) { for (int i = 0; i < count; i++) { tree.insert(i, "V" + i); } } }