Package org.helidb.util.bplus

Source Code of org.helidb.util.bplus.BPlusTreeTest

/* HeliDB -- A simple database for Java, http://www.helidb.org
* Copyright (C) 2008, 2009 Karl Gustafsson
*
* This file is a part of HeliDB.
*
* HeliDB is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* HeliDB is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/
package org.helidb.util.bplus;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.ConcurrentModificationException;
import java.util.Iterator;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.Random;

import org.entityfs.ReadWritableFile;
import org.entityfs.support.log.LogAdapterHolder;
import org.entityfs.support.log.StdOutLogAdapter;
import org.entityfs.util.io.ReadWritableFileAdapter;
import org.helidb.lang.DatabaseException;
import org.helidb.lang.KeyExistsException;
import org.helidb.lang.KeyNotFoundException;
import org.helidb.lang.Record;
import org.helidb.lang.serializer.CharacterNullSerializer;
import org.helidb.lang.serializer.IntegerNullSerializer;
import org.helidb.lang.serializer.IntegerSerializer;
import org.helidb.lang.serializer.LongNullSerializer;
import org.helidb.lang.serializer.LongSerializer;
import org.helidb.lang.serializer.Serializer;
import org.helidb.lang.serializer.ShortNullSerializer;
import org.helidb.lang.serializer.ShortSerializer;
import org.helidb.search.SearchMode;
import org.helidb.test.support.FileSupport;
import org.junit.Test;

public class BPlusTreeTest extends AbstractBPlusTreeTest
{
  private static final Random RANDOM = new Random(System.currentTimeMillis());

  private static final boolean[] BOOLEANS = new boolean[] { true, false };
  private static final int[] POINTER_SIZES = new int[] { 2, 3, 6, 8 };

  private void assertLEquals(long l1, Long l2)
  {
    assertEquals(l1, l2.longValue());
  }

  private byte[] writeJunk(ReadWritableFile f)
  {
    byte[] barr = new byte[RANDOM.nextInt(16)];
    RANDOM.nextBytes(barr);
    try
    {
      OutputStream os = f.openForWrite();
      try
      {
        os.write(barr);
      }
      finally
      {
        os.close();
      }
    }
    catch (IOException e)
    {
      throw new RuntimeException(e);
    }
    return barr;
  }

  private void assertJunkUntouched(ReadWritableFile f, byte[] barr)
  {
    if (barr.length > 0)
    {
      byte[] barr2 = new byte[barr.length];
      try
      {
        InputStream is = f.openForRead();
        try
        {
          assertEquals(barr2.length, is.read(barr2));
        }
        finally
        {
          is.close();
        }
      }
      catch (IOException e)
      {
        throw new RuntimeException(e);
      }
      assertTrue(Arrays.equals(barr, barr2));
    }
  }

  @Test
  public void testInsertRecord()
  {
    List<Integer> l = new ArrayList<Integer>();
    BPlusTree.insertRecord(l, 50);
    assertTrue(Arrays.equals(l.toArray(), new Object[] { 50 }));
    BPlusTree.insertRecord(l, 30);
    assertTrue(Arrays.equals(l.toArray(), new Object[] { 30, 50 }));
    BPlusTree.insertRecord(l, 20);
    assertTrue(Arrays.equals(l.toArray(), new Object[] { 20, 30, 50 }));
    BPlusTree.insertRecord(l, 25);
    assertTrue(Arrays.equals(l.toArray(), new Object[] { 20, 25, 30, 50 }));
    BPlusTree.insertRecord(l, 40);
    assertTrue(Arrays.equals(l.toArray(), new Object[] { 20, 25, 30, 40, 50 }));
    BPlusTree.insertRecord(l, 60);
    assertTrue(Arrays.equals(l.toArray(), new Object[] { 20, 25, 30, 40, 50, 60 }));
    BPlusTree.insertRecord(l, 35);
    assertTrue(Arrays.equals(l.toArray(), new Object[] { 20, 25, 30, 35, 40, 50, 60 }));
    BPlusTree.insertRecord(l, 45);
    assertTrue(Arrays.equals(l.toArray(), new Object[] { 20, 25, 30, 35, 40, 45, 50, 60 }));
    BPlusTree.insertRecord(l, 55);
    assertTrue(Arrays.equals(l.toArray(), new Object[] { 20, 25, 30, 35, 40, 45, 50, 55, 60 }));
    BPlusTree.insertRecord(l, 15);
    assertTrue(Arrays.equals(l.toArray(), new Object[] { 15, 20, 25, 30, 35, 40, 45, 50, 55, 60 }));

    List<Integer> l2 = new ArrayList<Integer>();
    BPlusTree.insertRecord(l2, 50);
    BPlusTree.insertRecord(l2, 70);
    assertTrue(Arrays.equals(l2.toArray(), new Object[] { 50, 70 }));
  }

  @Test
  public void testFindAddressToNodeWhereKeyShouldGo()
  {
    File tmpFile = FileSupport.createTempFile();
    try
    {
      ReadWritableFile f = new ReadWritableFileAdapter(tmpFile);
      byte[] barr = writeJunk(f);
      LogAdapterHolder lah = new LogAdapterHolder(new StdOutLogAdapter());
      BPlusTree<Long, Long> btr = new BPlusTree<Long, Long>(new FileBackedNodeRepository<Long, Long>(f, false, f.getSize(), new NumberOfRecordsNodeSizeStrategy(2), false, LongNullSerializer.INSTANCE, LongSerializer.INSTANCE, 8, 8192, null, lah), lah);
      try
      {
        // A list with an odd size
        List<KeyAndValue<Long, Long>> records = new ArrayList<KeyAndValue<Long, Long>>(5);
        records.add(new KeyAndValue<Long, Long>(Long.valueOf(1L), Long.valueOf(3L)));
        records.add(new KeyAndValue<Long, Long>(Long.valueOf(3L), Long.valueOf(5L)));
        records.add(new KeyAndValue<Long, Long>(Long.valueOf(5L), Long.valueOf(7L)));
        records.add(new KeyAndValue<Long, Long>(Long.valueOf(7L), Long.valueOf(9L)));
        records.add(new KeyAndValue<Long, Long>(Long.valueOf(9L), Long.valueOf(11L)));

        assertLEquals(1L, btr.findLocationOfNodeWhereKeyShouldGo(Long.valueOf(-111L), 1L, null, records).getValue());
        assertLEquals(1L, btr.findLocationOfNodeWhereKeyShouldGo(Long.valueOf(0L), 1L, null, records).getValue());
        assertLEquals(3L, btr.findLocationOfNodeWhereKeyShouldGo(Long.valueOf(2L), 1L, null, records).getValue());
        assertLEquals(5L, btr.findLocationOfNodeWhereKeyShouldGo(Long.valueOf(4L), 1L, null, records).getValue());
        assertLEquals(7L, btr.findLocationOfNodeWhereKeyShouldGo(Long.valueOf(6L), 1L, null, records).getValue());
        assertLEquals(9L, btr.findLocationOfNodeWhereKeyShouldGo(Long.valueOf(8L), 1L, null, records).getValue());
        assertLEquals(11L, btr.findLocationOfNodeWhereKeyShouldGo(Long.valueOf(10L), 1L, null, records).getValue());
        assertLEquals(11L, btr.findLocationOfNodeWhereKeyShouldGo(Long.valueOf(100000L), 1L, null, records).getValue());

        // A list with an even size
        records = new ArrayList<KeyAndValue<Long, Long>>(4);
        records.add(new KeyAndValue<Long, Long>(Long.valueOf(1L), Long.valueOf(3L)));
        records.add(new KeyAndValue<Long, Long>(Long.valueOf(3L), Long.valueOf(5L)));
        records.add(new KeyAndValue<Long, Long>(Long.valueOf(5L), Long.valueOf(7L)));
        records.add(new KeyAndValue<Long, Long>(Long.valueOf(7L), Long.valueOf(9L)));

        assertLEquals(1L, btr.findLocationOfNodeWhereKeyShouldGo(Long.valueOf(-111L), 1L, null, records).getValue());
        assertLEquals(1L, btr.findLocationOfNodeWhereKeyShouldGo(Long.valueOf(0L), 1L, null, records).getValue());
        assertLEquals(3L, btr.findLocationOfNodeWhereKeyShouldGo(Long.valueOf(2L), 1L, null, records).getValue());
        assertLEquals(5L, btr.findLocationOfNodeWhereKeyShouldGo(Long.valueOf(4L), 1L, null, records).getValue());
        assertLEquals(7L, btr.findLocationOfNodeWhereKeyShouldGo(Long.valueOf(6L), 1L, null, records).getValue());
        assertLEquals(9L, btr.findLocationOfNodeWhereKeyShouldGo(Long.valueOf(8L), 1L, null, records).getValue());
        assertLEquals(9L, btr.findLocationOfNodeWhereKeyShouldGo(Long.valueOf(100000L), 1L, null, records).getValue());
      }
      finally
      {
        btr.close();
      }
      assertJunkUntouched(f, barr);
    }
    finally
    {
      assertTrue(tmpFile.delete());
    }
  }

  @Test
  public void testFindRecord()
  {
    File tmpFile = FileSupport.createTempFile();
    try
    {
      ReadWritableFile f = new ReadWritableFileAdapter(tmpFile);
      byte[] barr = writeJunk(f);
      LogAdapterHolder lah = new LogAdapterHolder(new StdOutLogAdapter());
      BPlusTree<Long, Long> btr = new BPlusTree<Long, Long>(new FileBackedNodeRepository<Long, Long>(f, false, f.getSize(), new NumberOfRecordsNodeSizeStrategy(2), false, LongNullSerializer.INSTANCE, LongSerializer.INSTANCE, 8, 8192, null, lah), lah);
      try
      {
        // A list with an odd size
        List<KeyAndValue<Long, Long>> records = new ArrayList<KeyAndValue<Long, Long>>(5);
        records.add(new KeyAndValue<Long, Long>(Long.valueOf(1L), Long.valueOf(3L)));
        records.add(new KeyAndValue<Long, Long>(Long.valueOf(3L), Long.valueOf(5L)));
        records.add(new KeyAndValue<Long, Long>(Long.valueOf(5L), Long.valueOf(7L)));
        records.add(new KeyAndValue<Long, Long>(Long.valueOf(7L), Long.valueOf(9L)));
        records.add(new KeyAndValue<Long, Long>(Long.valueOf(9L), Long.valueOf(11L)));

        assertEquals(0, btr.findRecord(Long.valueOf(1L), records));
        assertEquals(1, btr.findRecord(Long.valueOf(3L), records));
        assertEquals(2, btr.findRecord(Long.valueOf(5L), records));
        assertEquals(3, btr.findRecord(Long.valueOf(7L), records));
        assertEquals(4, btr.findRecord(Long.valueOf(9L), records));

        assertTrue(btr.findRecord(Long.valueOf(-11L), records) < 0);
        assertTrue(btr.findRecord(Long.valueOf(2L), records) < 0);
        assertTrue(btr.findRecord(Long.valueOf(6L), records) < 0);
        assertTrue(btr.findRecord(Long.valueOf(11L), records) < 0);

        // A list with an even size
        records = new ArrayList<KeyAndValue<Long, Long>>(5);
        records.add(new KeyAndValue<Long, Long>(Long.valueOf(1L), Long.valueOf(3L)));
        records.add(new KeyAndValue<Long, Long>(Long.valueOf(3L), Long.valueOf(5L)));
        records.add(new KeyAndValue<Long, Long>(Long.valueOf(5L), Long.valueOf(7L)));
        records.add(new KeyAndValue<Long, Long>(Long.valueOf(7L), Long.valueOf(9L)));
        records.add(new KeyAndValue<Long, Long>(Long.valueOf(9L), Long.valueOf(11L)));
        records.add(new KeyAndValue<Long, Long>(Long.valueOf(11L), Long.valueOf(11L)));

        assertEquals(0, btr.findRecord(Long.valueOf(1L), records));
        assertEquals(1, btr.findRecord(Long.valueOf(3L), records));
        assertEquals(2, btr.findRecord(Long.valueOf(5L), records));
        assertEquals(3, btr.findRecord(Long.valueOf(7L), records));
        assertEquals(4, btr.findRecord(Long.valueOf(9L), records));
        assertEquals(5, btr.findRecord(Long.valueOf(11L), records));

        assertTrue(btr.findRecord(Long.valueOf(-11L), records) < 0);
        assertTrue(btr.findRecord(Long.valueOf(2L), records) < 0);
        assertTrue(btr.findRecord(Long.valueOf(6L), records) < 0);
        assertTrue(btr.findRecord(Long.valueOf(110L), records) < 0);
      }
      finally
      {
        btr.close();
      }
      assertJunkUntouched(f, barr);
    }
    finally
    {
      assertTrue(tmpFile.delete());
    }
  }

  @Test
  public void testGetFirstLastRecord()
  {
    LongConverter lconv = new LongConverter();
    File tmpFile = FileSupport.createTempFile();
    try
    {
      ReadWritableFile f = new ReadWritableFileAdapter(tmpFile);
      byte[] barr = writeJunk(f);
      LogAdapterHolder lah = new LogAdapterHolder(new StdOutLogAdapter());
      BPlusTree<Long, Long> btr = new BPlusTree<Long, Long>(new FileBackedNodeRepository<Long, Long>(f, false, f.getSize(), new NumberOfRecordsNodeSizeStrategy(2), false, LongNullSerializer.INSTANCE, LongSerializer.INSTANCE, 8, 8192, null, lah), lah);
      try
      {
        assertNull(btr.getFirstRecord());
        assertNull(btr.getLastRecord());

        KeyAndValue<Long, Long> r1 = createLRecord(lconv, lconv, 2, 2);
        btr.insert(r1.getKey(), r1.getValue());

        assertEquals(r1, btr.getFirstRecord());
        assertEquals(r1, btr.getLastRecord());

        KeyAndValue<Long, Long> r2 = createLRecord(lconv, lconv, 1, 1);
        btr.insert(r2.getKey(), r2.getValue());

        assertEquals(r2, btr.getFirstRecord());
        assertEquals(r1, btr.getLastRecord());

        KeyAndValue<Long, Long> r3 = createLRecord(lconv, lconv, 3, 3);
        btr.insert(r3.getKey(), r3.getValue());

        assertEquals(r2, btr.getFirstRecord());
        assertEquals(r3, btr.getLastRecord());

        KeyAndValue<Long, Long> r4 = createLRecord(lconv, lconv, 4, 4);
        btr.insert(r4.getKey(), r4.getValue());
        KeyAndValue<Long, Long> r5 = createLRecord(lconv, lconv, 5, 5);
        btr.insert(r5.getKey(), r5.getValue());
        KeyAndValue<Long, Long> r6 = createLRecord(lconv, lconv, 6, 6);
        btr.insert(r6.getKey(), r6.getValue());

        assertEquals(r2, btr.getFirstRecord());
        assertEquals(r6, btr.getLastRecord());
      }
      finally
      {
        btr.close();
      }
      assertJunkUntouched(f, barr);
    }
    finally
    {
      assertTrue(tmpFile.delete());
    }
  }

  @Test
  public void testGetNextAndPreviousRecordsOnTreeThatDoesNotSupportThem()
  {
    LongConverter lconv = new LongConverter();
    File tmpFile = FileSupport.createTempFile();
    try
    {
      ReadWritableFile f = new ReadWritableFileAdapter(tmpFile);
      byte[] barr = writeJunk(f);
      LogAdapterHolder lah = new LogAdapterHolder(new StdOutLogAdapter());
      BPlusTree<Long, Long> btr = new BPlusTree<Long, Long>(new FileBackedNodeRepository<Long, Long>(f, false, f.getSize(), new NumberOfRecordsNodeSizeStrategy(2), false, LongNullSerializer.INSTANCE, LongSerializer.INSTANCE, 8, 8192, null, lah), lah);
      try
      {
        KeyAndValue<Long, Long> r1 = createLRecord(lconv, lconv, 2, 2);
        btr.insert(r1.getKey(), r1.getValue());

        try
        {
          btr.getNextRecord(2L);
          fail();
        }
        catch (UnsupportedOperationException e)
        {
          // ok
        }

        try
        {
          btr.getPreviousRecord(2L);
          fail();
        }
        catch (UnsupportedOperationException e)
        {
          // ok
        }
      }
      finally
      {
        btr.close();
      }
      assertJunkUntouched(f, barr);
    }
    finally
    {
      assertTrue(tmpFile.delete());
    }
  }

  @Test
  public void testGetNextAndPreviousRecords()
  {
    LongConverter lconv = new LongConverter();
    File tmpFile = FileSupport.createTempFile();
    try
    {
      ReadWritableFile f = new ReadWritableFileAdapter(tmpFile);
      byte[] barr = writeJunk(f);
      LogAdapterHolder lah = new LogAdapterHolder(new StdOutLogAdapter());
      BPlusTree<Long, Long> btr = new BPlusTree<Long, Long>(new FileBackedNodeRepository<Long, Long>(f, false, f.getSize(), new NumberOfRecordsNodeSizeStrategy(2), true, LongNullSerializer.INSTANCE, LongSerializer.INSTANCE, 8, 8192, null, lah), lah);
      try
      {
        assertNull(btr.getNextRecord(1L));
        assertNull(btr.getPreviousRecord(1L));

        KeyAndValue<Long, Long> r1 = createLRecord(lconv, lconv, 2, 2);
        btr.insert(r1.getKey(), r1.getValue());

        assertNull(btr.getPreviousRecord(1L));
        assertEquals(r1, btr.getNextRecord(1L));
        assertNull(btr.getPreviousRecord(2L));
        assertNull(btr.getNextRecord(2L));
        assertEquals(r1, btr.getPreviousRecord(3L));
        assertNull(btr.getNextRecord(3L));

        KeyAndValue<Long, Long> r2 = createLRecord(lconv, lconv, 5, 5);
        btr.insert(r2.getKey(), r2.getValue());

        assertNull(btr.getPreviousRecord(1L));
        assertEquals(r1, btr.getNextRecord(1L));
        assertNull(btr.getPreviousRecord(2L));
        assertEquals(r2, btr.getNextRecord(2L));
        assertEquals(r1, btr.getPreviousRecord(3L));
        assertEquals(r2, btr.getNextRecord(3L));
        assertEquals(r1, btr.getPreviousRecord(5L));
        assertNull(btr.getNextRecord(5L));
        assertEquals(r2, btr.getPreviousRecord(7L));
        assertNull(btr.getNextRecord(7L));

        KeyAndValue<Long, Long> r3 = createLRecord(lconv, lconv, 8, 8);
        btr.insert(r3.getKey(), r3.getValue());
        KeyAndValue<Long, Long> r4 = createLRecord(lconv, lconv, 11, 11);
        btr.insert(r4.getKey(), r4.getValue());
        KeyAndValue<Long, Long> r5 = createLRecord(lconv, lconv, 14, 14);
        btr.insert(r5.getKey(), r5.getValue());

        assertNull(btr.getPreviousRecord(1L));
        assertEquals(r1, btr.getNextRecord(1L));
        assertNull(btr.getPreviousRecord(2L));
        assertEquals(r2, btr.getNextRecord(2L));
        assertEquals(r1, btr.getPreviousRecord(3L));
        assertEquals(r2, btr.getNextRecord(3L));
        assertEquals(r1, btr.getPreviousRecord(5L));
        assertEquals(r3, btr.getNextRecord(5L));
        assertEquals(r2, btr.getPreviousRecord(7L));
        assertEquals(r3, btr.getNextRecord(7L));
        assertEquals(r2, btr.getPreviousRecord(8L));
        assertEquals(r4, btr.getNextRecord(8L));
        assertEquals(r3, btr.getPreviousRecord(9L));
        assertEquals(r4, btr.getNextRecord(9L));
        assertEquals(r3, btr.getPreviousRecord(11L));
        assertEquals(r5, btr.getNextRecord(11L));
        assertEquals(r4, btr.getPreviousRecord(12L));
        assertEquals(r5, btr.getNextRecord(12L));
        assertEquals(r4, btr.getPreviousRecord(14L));
        assertNull(btr.getNextRecord(14L));
        assertEquals(r5, btr.getPreviousRecord(15L));
        assertNull(btr.getNextRecord(15L));
      }
      finally
      {
        btr.close();
      }
      assertJunkUntouched(f, barr);
    }
    finally
    {
      assertTrue(tmpFile.delete());
    }
  }

  private <K extends Comparable<K>> void assertNodeDeleted(NodeRepository<K> nr, long pos)
  {
    try
    {
      // Either this returns null if pos is beyond the end of the file,
      // or it throws an exception if the node cannot be read.
      assertNull(nr.readNode(pos, null));
    }
    catch (DatabaseException e)
    {
      assertTrue(e.getMessage().contains("deleted") || e.getMessage().contains("Wanted to read"));
    }
  }

  @SuppressWarnings("unchecked")
  private <K extends Comparable<K>, V> void testInsertFromOneToNineWithTwoRecordsPerNodeInternal(NodeRepository<K> nr, ValueConverter<K> kconv, ValueConverter<V> vconv, long nonLeafNodeSize, long leafNodeSize)
  {
    NodePositions np = new NodePositions(nonLeafNodeSize, leafNodeSize, nr.getPositionOfRootNode());
    BPlusTree<K, V> btr = new BPlusTree<K, V>(nr, new LogAdapterHolder(new StdOutLogAdapter()));
    try
    {
      // *0[1, ]
      KeyAndValue<K, V> r1 = createLRecord(kconv, vconv, 1, 1);
      btr.insert(r1.getKey(), r1.getValue());

      BPlusTreeNode<K, ?> rn = nr.readNode(np.get(0), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(0), createLRecordList(r1), true, false, null, null), rn);

      // 0[1,2]
      KeyAndValue<K, V> r2 = createLRecord(kconv, vconv, 2, 2);
      btr.insert(r2.getKey(), r2.getValue());

      rn = nr.readNode(np.get(0), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(0), createLRecordList(r1, r2), true, true, null, null), rn);

      // 0[(1),2(2), ]
      // *1[1, ] *2[2,3]
      KeyAndValue<K, V> r3 = createLRecord(kconv, vconv, 3, 3);
      btr.insert(r3.getKey(), r3.getValue());
      np.addLeaf();
      np.addLeaf();

      rn = nr.readNode(np.get(0), null);
      assertNodesEqual(new BPlusTreeNonLeafNode<K>(np.get(0), null, np.get(1), createLRecordList(createLRecord(2, np.get(2), kconv)), true, false), rn);
      rn = nr.readNode(np.get(1), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(1), createLRecordList(r1), false, false, null, np.get(2)), rn);
      rn = nr.readNode(np.get(2), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(2), createLRecordList(r2, r3), false, true, np.get(1), null), rn);

      // 0[(1),2(2),3(3)]
      // 1[1, ] 2[2, ] *3[3,4]
      KeyAndValue<K, V> r4 = createLRecord(kconv, vconv, 4, 4);
      btr.insert(r4.getKey(), r4.getValue());
      np.addLeaf();

      rn = nr.readNode(np.get(0), null);
      assertNodesEqual(new BPlusTreeNonLeafNode<K>(np.get(0), null, np.get(1), createLRecordList(createLRecord(2, np.get(2), kconv), createLRecord(3, np.get(3), kconv)), true, true), rn);
      rn = nr.readNode(np.get(1), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(1), createLRecordList(r1), false, false, null, np.get(2)), rn);
      rn = nr.readNode(np.get(2), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(2), createLRecordList(r2), false, false, np.get(1), np.get(3)), rn);
      rn = nr.readNode(np.get(3), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(3), createLRecordList(r3, r4), false, true, np.get(2), null), rn);

      // 0[(5),3(6), ]
      // *5[(1),2(2), ] *6[(3),4(4), ]
      // 1[1, ] 2[2, ] 3[3, ] *4[4, 5]
      KeyAndValue<K, V> r5 = createLRecord(kconv, vconv, 5, 5);
      btr.insert(r5.getKey(), r5.getValue());
      np.addLeaf();
      np.addNonLeaf();
      np.addNonLeaf();

      rn = nr.readNode(np.get(0), null);
      assertNodesEqual(new BPlusTreeNonLeafNode<K>(np.get(0), null, np.get(5), createLRecordList(createLRecord(3, np.get(6), kconv)), true, false), rn);
      rn = nr.readNode(np.get(1), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(1), createLRecordList(r1), false, false, null, np.get(2)), rn);
      rn = nr.readNode(np.get(2), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(2), createLRecordList(r2), false, false, np.get(1), np.get(3)), rn);
      rn = nr.readNode(np.get(3), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(3), createLRecordList(r3), false, false, np.get(2), np.get(4)), rn);
      rn = nr.readNode(np.get(4), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(4), createLRecordList(r4, r5), false, true, np.get(3), null), rn);
      rn = nr.readNode(np.get(5), null);
      assertNodesEqual(new BPlusTreeNonLeafNode<K>(np.get(5), null, np.get(1), createLRecordList(createLRecord(2, Long.valueOf(np.get(2)), kconv)), false, false), rn);
      rn = nr.readNode(np.get(6), kconv.convert(3));
      assertNodesEqual(new BPlusTreeNonLeafNode<K>(np.get(6), kconv.convert(3), np.get(3), createLRecordList(createLRecord(4, Long.valueOf(np.get(4)), kconv)), false, false), rn);

      // 0[(5),3(6), ]
      // 5[(1),2(2), ] 6[(3),4(4),5(7)]
      // 1[1, ] 2[2, ] 3[3, ] 4[4, ] *7[5,6]
      KeyAndValue<K, V> r6 = createLRecord(kconv, vconv, 6, 6);
      btr.insert(r6.getKey(), r6.getValue());
      np.addLeaf();

      rn = nr.readNode(np.get(0), null);
      assertNodesEqual(new BPlusTreeNonLeafNode<K>(np.get(0), null, np.get(5), createLRecordList(createLRecord(3, np.get(6), kconv)), true, false), rn);
      rn = nr.readNode(np.get(4), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(4), createLRecordList(r4), false, false, np.get(3), np.get(7)), rn);
      rn = nr.readNode(np.get(6), kconv.convert(3));
      assertNodesEqual(new BPlusTreeNonLeafNode<K>(np.get(6), kconv.convert(3), np.get(3), createLRecordList(createLRecord(4, Long.valueOf(np.get(4)), kconv), createLRecord(5, Long.valueOf(np.get(7)), kconv)), false, true), rn);
      rn = nr.readNode(np.get(7), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(7), createLRecordList(r5, r6), false, true, np.get(4), null), rn);

      // 0[(5),3(6),5(9)]
      // 5[(1),2(2), ] 6[(3),4(4), ] *9[(7),6(8),]
      // 1[1, ] 2[2, ] 3[3, ] 4[4, ] 7[5, ] *8[6,7]
      KeyAndValue<K, V> r7 = createLRecord(kconv, vconv, 7, 7);
      btr.insert(r7.getKey(), r7.getValue());
      np.addLeaf();
      np.addNonLeaf();

      rn = nr.readNode(np.get(0), null);
      assertNodesEqual(new BPlusTreeNonLeafNode<K>(np.get(0), null, np.get(5), createLRecordList(createLRecord(3, np.get(6), kconv), createLRecord(5, np.get(9), kconv)), true, true), rn);
      rn = nr.readNode(np.get(6), kconv.convert(3));
      assertNodesEqual(new BPlusTreeNonLeafNode<K>(np.get(6), kconv.convert(3), np.get(3), createLRecordList(createLRecord(4, np.get(4), kconv)), false, false), rn);
      rn = nr.readNode(np.get(7), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(7), createLRecordList(r5), false, false, np.get(4), np.get(8)), rn);
      rn = nr.readNode(np.get(8), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(8), createLRecordList(r6, r7), false, true, np.get(7), null), rn);
      rn = nr.readNode(np.get(9), kconv.convert(5));
      assertNodesEqual(new BPlusTreeNonLeafNode<K>(np.get(9), kconv.convert(5), np.get(7), createLRecordList(createLRecord(6, np.get(8), kconv)), false, false), rn);

      // 0[(5),3(6),5(9)]
      // 5[(1),2(2), ] 6[(3),4(4), ] 9[(7),6(8),7(10)]
      // 1[1, ] 2[2, ] 3[3, ] 4[4, ] 7[5, ] 8[6, ] *10[7,8]
      KeyAndValue<K, V> r8 = createLRecord(kconv, vconv, 8, 8);
      btr.insert(r8.getKey(), r8.getValue());
      np.addLeaf();

      rn = nr.readNode(np.get(0), null);
      assertNodesEqual(new BPlusTreeNonLeafNode<K>(np.get(0), null, np.get(5), createLRecordList(createLRecord(3, np.get(6), kconv), createLRecord(5, np.get(9), kconv)), true, true), rn);
      rn = nr.readNode(np.get(6), kconv.convert(3));
      assertNodesEqual(new BPlusTreeNonLeafNode<K>(np.get(6), kconv.convert(3), np.get(3), createLRecordList(createLRecord(4, np.get(4), kconv)), false, false), rn);
      rn = nr.readNode(np.get(7), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(7), createLRecordList(r5), false, false, np.get(4), np.get(8)), rn);
      rn = nr.readNode(np.get(8), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(8), createLRecordList(r6), false, false, np.get(7), np.get(10)), rn);
      rn = nr.readNode(np.get(9), kconv.convert(5));
      assertNodesEqual(new BPlusTreeNonLeafNode<K>(np.get(9), kconv.convert(5), np.get(7), createLRecordList(createLRecord(6, np.get(8), kconv), createLRecord(7, np.get(10), kconv)), false, true), rn);
      rn = nr.readNode(np.get(10), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(10), createLRecordList(r7, r8), false, true, np.get(8), null), rn);

      // 0[(13),5(14), ]
      // *13[(5),3(6), ] *14[(9),7(12), ]
      // 5[(1),2(2), ] 6[(3),4(4), ] 9[(7),6(8), ] *12[(10),8(11), ]
      // 1[1, ] 2[2, ] 3[3, ] 4[4, ] 7[5, ] 8[6, ] 10[7, ] *11[8,9]
      KeyAndValue<K, V> r9 = createLRecord(kconv, vconv, 9, 9);
      btr.insert(r9.getKey(), r9.getValue());
      np.addLeaf();
      np.addNonLeaf();
      np.addNonLeaf();
      np.addNonLeaf();

      rn = nr.readNode(np.get(0), null);
      assertNodesEqual(new BPlusTreeNonLeafNode<K>(np.get(0), null, np.get(13), createLRecordList(createLRecord(5, np.get(14), kconv)), true, false), rn);
      rn = nr.readNode(np.get(1), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(1), createLRecordList(r1), false, false, null, np.get(2)), rn);
      rn = nr.readNode(np.get(2), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(2), createLRecordList(r2), false, false, np.get(1), np.get(3)), rn);
      rn = nr.readNode(np.get(3), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(3), createLRecordList(r3), false, false, np.get(2), np.get(4)), rn);
      rn = nr.readNode(np.get(4), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(4), createLRecordList(r4), false, false, np.get(3), np.get(7)), rn);
      rn = nr.readNode(np.get(5), null);
      assertNodesEqual(new BPlusTreeNonLeafNode<K>(np.get(5), null, np.get(1), createLRecordList(createLRecord(2, np.get(2), kconv)), false, false), rn);
      rn = nr.readNode(np.get(6), kconv.convert(3));
      assertNodesEqual(new BPlusTreeNonLeafNode<K>(np.get(6), kconv.convert(3), np.get(3), createLRecordList(createLRecord(4, np.get(4), kconv)), false, false), rn);
      rn = nr.readNode(np.get(7), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(7), createLRecordList(r5), false, false, np.get(4), np.get(8)), rn);
      rn = nr.readNode(np.get(8), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(8), createLRecordList(r6), false, false, np.get(7), np.get(10)), rn);
      rn = nr.readNode(np.get(9), kconv.convert(5));
      assertNodesEqual(new BPlusTreeNonLeafNode<K>(np.get(9), kconv.convert(5), np.get(7), createLRecordList(createLRecord(6, np.get(8), kconv)), false, false), rn);
      rn = nr.readNode(np.get(10), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(10), createLRecordList(r7), false, false, np.get(8), np.get(11)), rn);
      rn = nr.readNode(np.get(11), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(11), createLRecordList(r8, r9), false, true, np.get(10), null), rn);
      rn = nr.readNode(np.get(12), kconv.convert(7));
      assertNodesEqual(new BPlusTreeNonLeafNode<K>(np.get(12), kconv.convert(7), np.get(10), createLRecordList(createLRecord(8, np.get(11), kconv)), false, false), rn);
      rn = nr.readNode(np.get(13), null);
      assertNodesEqual(new BPlusTreeNonLeafNode<K>(np.get(13), null, np.get(5), createLRecordList(createLRecord(3, np.get(6), kconv)), false, false), rn);
      rn = nr.readNode(np.get(14), kconv.convert(5));
      assertNodesEqual(new BPlusTreeNonLeafNode<K>(np.get(14), kconv.convert(5), np.get(9), createLRecordList(createLRecord(7, np.get(12), kconv)), false, false), rn);
      assertEquals(9, btr.size());

      // Delete 1
      // 0[(5),5(9),7(12)]
      // 5[(1),3(3),4(4)] 9[(7),6(8), ] 12[(10),8(11), ]
      // 1[2, ] 3[3, ] 4[4, ] 7[5, ] 8[6, ] 10[7, ] 11[8,9]
      assertEquals(r1.getValue(), btr.delete(r1.getKey()));
      rn = nr.readNode(np.get(0), null);
      assertNodesEqual(new BPlusTreeNonLeafNode<K>(np.get(0), null, np.get(5), createLRecordList(createLRecord(5, np.get(9), kconv), createLRecord(7, np.get(12), kconv)), true, true), rn);
      rn = nr.readNode(np.get(1), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(1), createLRecordList(r2), false, false, null, np.get(3)), rn);
      assertNodeDeleted(nr, np.get(2));
      rn = nr.readNode(np.get(3), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(3), createLRecordList(r3), false, false, np.get(1), np.get(4)), rn);
      rn = nr.readNode(np.get(4), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(4), createLRecordList(r4), false, false, np.get(3), np.get(7)), rn);
      rn = nr.readNode(np.get(5), null);
      assertNodesEqual(new BPlusTreeNonLeafNode<K>(np.get(5), null, np.get(1), createLRecordList(createLRecord(3, np.get(3), kconv), createLRecord(4, np.get(4), kconv)), false, true), rn);
      assertNodeDeleted(nr, np.get(6));
      rn = nr.readNode(np.get(7), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(7), createLRecordList(r5), false, false, np.get(4), np.get(8)), rn);
      rn = nr.readNode(np.get(8), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(8), createLRecordList(r6), false, false, np.get(7), np.get(10)), rn);
      rn = nr.readNode(np.get(9), kconv.convert(5));
      assertNodesEqual(new BPlusTreeNonLeafNode<K>(np.get(9), kconv.convert(5), np.get(7), createLRecordList(createLRecord(6, np.get(8), kconv)), false, false), rn);
      rn = nr.readNode(np.get(10), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(10), createLRecordList(r7), false, false, np.get(8), np.get(11)), rn);
      rn = nr.readNode(np.get(11), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(11), createLRecordList(r8, r9), false, true, np.get(10), null), rn);
      rn = nr.readNode(np.get(12), kconv.convert(7));
      assertNodesEqual(new BPlusTreeNonLeafNode<K>(np.get(12), kconv.convert(7), np.get(10), createLRecordList(createLRecord(8, np.get(11), kconv)), false, false), rn);
      assertNodeDeleted(nr, np.get(13));
      assertNodeDeleted(nr, np.get(14));

      // Delete 2
      // 0[(5),5(9),7(12)]
      // 5[(3),4(4), ] 9[(7),6(8), ] 12[(10),8(11), ]
      // 1[3, ] 4[4, ] 7[5, ] 8[6, ] 10[7, ] 11[8,9]
      assertEquals(r2.getValue(), btr.delete(r2.getKey()));
      rn = nr.readNode(np.get(1), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(1), createLRecordList(r3), false, false, null, np.get(4)), rn);
      assertNodeDeleted(nr, np.get(3));
      rn = nr.readNode(np.get(4), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(4), createLRecordList(r4), false, false, np.get(1), np.get(7)), rn);
      rn = nr.readNode(np.get(5), null);
      assertNodesEqual(new BPlusTreeNonLeafNode<K>(np.get(5), null, np.get(1), createLRecordList(createLRecord(4, np.get(4), kconv)), false, false), rn);

      // Delete 3
      // 0[(5),7(12)]
      // 5[(1),5(7),6(8)] 12[(10),8(11), ]
      // 1[4, ] 7[5, ] 8[6, ] 10[7, ] 11[8,9]
      assertEquals(r3.getValue(), btr.delete(r3.getKey()));
      rn = nr.readNode(np.get(0), null);
      assertNodesEqual(new BPlusTreeNonLeafNode<K>(np.get(0), null, np.get(5), createLRecordList(createLRecord(7, np.get(12), kconv)), true, false), rn);
      rn = nr.readNode(np.get(1), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(1), createLRecordList(r4), false, false, null, np.get(7)), rn);
      assertNodeDeleted(nr, np.get(4));
      rn = nr.readNode(np.get(5), null);
      assertNodesEqual(new BPlusTreeNonLeafNode<K>(np.get(5), null, np.get(1), createLRecordList(createLRecord(5, np.get(7), kconv), createLRecord(6, np.get(8), kconv)), false, true), rn);
      rn = nr.readNode(np.get(7), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(7), createLRecordList(r5), false, false, np.get(1), np.get(8)), rn);

      // Delete 4
      // 0[(5),7(12)]
      // 5[(1),6(8), ] 12[(10),8(11), ]
      // 1[5, ] 8[6, ] 10[7, ] 11[8,9]
      assertEquals(r4.getValue(), btr.delete(r4.getKey()));

      rn = nr.readNode(np.get(1), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(1), createLRecordList(r5), false, false, null, np.get(8)), rn);
      rn = nr.readNode(np.get(5), null);
      assertNodesEqual(new BPlusTreeNonLeafNode<K>(np.get(5), null, np.get(1), createLRecordList(createLRecord(6, np.get(8), kconv)), false, false), rn);
      assertNodeDeleted(nr, np.get(7));
      rn = nr.readNode(np.get(8), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(8), createLRecordList(r6), false, false, np.get(1), np.get(10)), rn);

      // Delete 5
      // 0[(1),7(10),8(11)]
      // 1[6, ] 10[7, ] 11[8,9]
      assertEquals(r5.getValue(), btr.delete(r5.getKey()));

      rn = nr.readNode(np.get(0), null);
      assertNodesEqual(new BPlusTreeNonLeafNode<K>(np.get(0), null, np.get(1), createLRecordList(createLRecord(7, np.get(10), kconv), createLRecord(8, np.get(11), kconv)), true, true), rn);
      rn = nr.readNode(np.get(1), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(1), createLRecordList(r6), false, false, null, np.get(10)), rn);
      assertNodeDeleted(nr, np.get(5));
      assertNodeDeleted(nr, np.get(8));
      assertNodeDeleted(nr, np.get(12));
      rn = nr.readNode(np.get(10), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(10), createLRecordList(r7), false, false, np.get(1), np.get(11)), rn);
      assertEquals(4, btr.size());

      // Delete 6
      // 0[(1),8(11)]
      // 1[7, ] 11[8,9]
      assertEquals(r6.getValue(), btr.delete(r6.getKey()));

      rn = nr.readNode(np.get(0), null);
      assertNodesEqual(new BPlusTreeNonLeafNode<K>(np.get(0), null, np.get(1), createLRecordList(createLRecord(8, np.get(11), kconv)), true, false), rn);
      rn = nr.readNode(np.get(1), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(1), createLRecordList(r7), false, false, null, np.get(11)), rn);
      assertNodeDeleted(nr, np.get(10));
      rn = nr.readNode(np.get(11), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(11), createLRecordList(r8, r9), false, true, np.get(1), null), rn);

      // Delete 7
      // 0[8,9]
      assertEquals(r7.getValue(), btr.delete(r7.getKey()));

      rn = nr.readNode(np.get(0), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(0), createLRecordList(r8, r9), true, true, null, null), rn);
      assertNodeDeleted(nr, np.get(1));
      assertNodeDeleted(nr, np.get(11));

      // Delete 8
      // 0[9]
      assertEquals(r8.getValue(), btr.delete(r8.getKey()));

      rn = nr.readNode(np.get(0), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(0), createLRecordList(r9), true, false, null, null), rn);

      // Delete 9
      // 0[,]
      assertEquals(r9.getValue(), btr.delete(r9.getKey()));

      rn = nr.readNode(np.get(0), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(0), Collections.EMPTY_LIST, true, false, null, null), rn);
    }
    finally
    {
      btr.close();
    }
  }

  private <K extends Comparable<K>, V> void testInsertFromOneToNineWithTwoRecordsPerNodeAndLruCacheInternal(Serializer<K> keySerializer, ValueConverter<K> kconv, Serializer<V> valueSerializer, ValueConverter<V> vconv, int cacheSize, int pointerSize, boolean leafNodePointers)
  {
    File tmpFile = FileSupport.createTempFile();
    try
    {
      ReadWritableFile f = new ReadWritableFileAdapter(tmpFile);
      byte[] barr = writeJunk(f);
      LogAdapterHolder lah = new LogAdapterHolder(new StdOutLogAdapter());
      NodeRepository<K> nr = new FileBackedNodeRepository<K, V>(f, false, f.getSize(), new NumberOfRecordsNodeSizeStrategy(2), leafNodePointers, keySerializer, valueSerializer, pointerSize, 8192, null, lah);
      if (cacheSize > 0)
      {
        nr = new LruCacheNodeRepository<K, V>(nr, cacheSize);
      }
      testInsertFromOneToNineWithTwoRecordsPerNodeInternal(nr, kconv, vconv, getNonLeafNodeSize(keySerializer.getSerializedSize(), pointerSize, 2), getLeafNodeSize(keySerializer.getSerializedSize(), valueSerializer.getSerializedSize(), pointerSize, 2, leafNodePointers));
      assertJunkUntouched(f, barr);
    }
    finally
    {
      assertTrue(tmpFile.delete());
    }
  }

  private void testInsertFromOneToNineWithTwoRecordsPerNodeInternal(int cacheSize)
  {
    for (int pointerSize : POINTER_SIZES)
    {
      for (boolean lnp : BOOLEANS)
      {
        testInsertFromOneToNineWithTwoRecordsPerNodeAndLruCacheInternal(LongNullSerializer.INSTANCE, LONG_CONVERTER, LongSerializer.INSTANCE, LONG_CONVERTER, cacheSize, pointerSize, lnp);
        testInsertFromOneToNineWithTwoRecordsPerNodeAndLruCacheInternal(LongNullSerializer.INSTANCE, LONG_CONVERTER, IntegerSerializer.INSTANCE, INTEGER_CONVERTER, cacheSize, pointerSize, lnp);
        testInsertFromOneToNineWithTwoRecordsPerNodeAndLruCacheInternal(LongNullSerializer.INSTANCE, LONG_CONVERTER, ShortSerializer.INSTANCE, SHORT_CONVERTER, cacheSize, pointerSize, lnp);
        testInsertFromOneToNineWithTwoRecordsPerNodeAndLruCacheInternal(IntegerNullSerializer.INSTANCE, INTEGER_CONVERTER, LongSerializer.INSTANCE, LONG_CONVERTER, cacheSize, pointerSize, lnp);
        testInsertFromOneToNineWithTwoRecordsPerNodeAndLruCacheInternal(IntegerNullSerializer.INSTANCE, INTEGER_CONVERTER, IntegerSerializer.INSTANCE, INTEGER_CONVERTER, cacheSize, pointerSize, lnp);
        testInsertFromOneToNineWithTwoRecordsPerNodeAndLruCacheInternal(IntegerNullSerializer.INSTANCE, INTEGER_CONVERTER, ShortSerializer.INSTANCE, SHORT_CONVERTER, cacheSize, pointerSize, lnp);
        testInsertFromOneToNineWithTwoRecordsPerNodeAndLruCacheInternal(ShortNullSerializer.INSTANCE, SHORT_CONVERTER, LongSerializer.INSTANCE, LONG_CONVERTER, cacheSize, pointerSize, lnp);
        testInsertFromOneToNineWithTwoRecordsPerNodeAndLruCacheInternal(ShortNullSerializer.INSTANCE, SHORT_CONVERTER, IntegerSerializer.INSTANCE, INTEGER_CONVERTER, cacheSize, pointerSize, lnp);
        testInsertFromOneToNineWithTwoRecordsPerNodeAndLruCacheInternal(ShortNullSerializer.INSTANCE, SHORT_CONVERTER, ShortSerializer.INSTANCE, SHORT_CONVERTER, cacheSize, pointerSize, lnp);
      }
    }
  }

  @Test
  public void testInsertFromOneToNineWithTwoRecordsPerNode()
  {
    testInsertFromOneToNineWithTwoRecordsPerNodeInternal(0);
  }

  @Test
  public void testInsertFromOneToNineWithTwoRecordsPerNodeAndSmallLruCache()
  {
    testInsertFromOneToNineWithTwoRecordsPerNodeInternal(2);
  }

  @Test
  public void testInsertFromOneToNineWithTwoRecordsPerNodeAndLargeLruCache()
  {
    testInsertFromOneToNineWithTwoRecordsPerNodeInternal(20);
  }

  @SuppressWarnings("unchecked")
  private <K extends Comparable<K>, V> void testInsertFrom15ToOneWithTwoRecordsPerNodeInternal(NodeRepository<K> nr, ValueConverter<K> kconv, ValueConverter<V> vconv, long nonLeafNodeSize, long leafNodeSize)
  {
    NodePositions np = new NodePositions(nonLeafNodeSize, leafNodeSize, nr.getPositionOfRootNode());
    BPlusTree<K, V> btr = new BPlusTree<K, V>(nr, new LogAdapterHolder(new StdOutLogAdapter()));
    try
    {
      // *0[15, ]
      KeyAndValue<K, V> r15 = createLRecord(kconv, vconv, 15, 15);
      btr.insert(r15.getKey(), r15.getValue());

      BPlusTreeNode<K, ?> rn = nr.readNode(np.get(0), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(0), createLRecordList(r15), true, false, null, null), rn);

      // 0[14,15]
      KeyAndValue<K, V> r14 = createLRecord(kconv, vconv, 14, 14);
      btr.insert(r14.getKey(), r14.getValue());

      rn = nr.readNode(np.get(0), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(0), createLRecordList(r14, r15), true, true, null, null), rn);

      // 0[(1),14(2), ]
      // *1[13, ] *2[14, 15]
      KeyAndValue<K, V> r13 = createLRecord(kconv, vconv, 13, 13);
      btr.insert(r13.getKey(), r13.getValue());
      np.addLeaf();
      np.addLeaf();

      rn = nr.readNode(np.get(0), null);
      assertNodesEqual(new BPlusTreeNonLeafNode<K>(np.get(0), null, np.get(1), createLRecordList(createLRecord(14, np.get(2), kconv)), true, false), rn);
      rn = nr.readNode(np.get(1), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(1), createLRecordList(r13), false, false, null, np.get(2)), rn);
      rn = nr.readNode(np.get(2), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(2), createLRecordList(r14, r15), false, true, np.get(1), null), rn);

      // 0[(1),14(2), ]
      // 1[12,13] 2[14, 15]
      KeyAndValue<K, V> r12 = createLRecord(kconv, vconv, 12, 12);
      btr.insert(r12.getKey(), r12.getValue());

      rn = nr.readNode(np.get(1), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(1), createLRecordList(r12, r13), false, true, null, np.get(2)), rn);

      // 0[(1),12(3),14(2)]
      // 1[11, ] *3[12,13] 2[14, 15]
      KeyAndValue<K, V> r11 = createLRecord(kconv, vconv, 11, 11);
      btr.insert(r11.getKey(), r11.getValue());
      np.addLeaf();

      rn = nr.readNode(np.get(0), null);
      assertNodesEqual(new BPlusTreeNonLeafNode<K>(np.get(0), null, np.get(1), createLRecordList(createLRecord(12, np.get(3), kconv), createLRecord(14, np.get(2), kconv)), true, true), rn);
      rn = nr.readNode(np.get(1), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(1), createLRecordList(r11), false, false, null, np.get(3)), rn);
      rn = nr.readNode(np.get(2), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(2), createLRecordList(r14, r15), false, true, np.get(3), null), rn);
      rn = nr.readNode(np.get(3), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(3), createLRecordList(r12, r13), false, true, np.get(1), np.get(2)), rn);

      // 0[(1),12(3),14(2)]
      // 1[10,11] 3[12,13] 2[14,15]
      KeyAndValue<K, V> r10 = createLRecord(kconv, vconv, 10, 10);
      btr.insert(r10.getKey(), r10.getValue());

      rn = nr.readNode(np.get(1), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(1), createLRecordList(r10, r11), false, true, null, np.get(3)), rn);

      // 0[(5),12(6), ]
      // *5[(1),10(4), ] *6[(3),14(2), ]
      // 1[9, ] *4[10,11] 3[12,13] 2[14,15]
      KeyAndValue<K, V> r9 = createLRecord(kconv, vconv, 9, 9);
      btr.insert(r9.getKey(), r9.getValue());
      np.addLeaf();
      np.addNonLeaf();
      np.addNonLeaf();

      rn = nr.readNode(np.get(0), null);
      assertNodesEqual(new BPlusTreeNonLeafNode<K>(np.get(0), null, np.get(5), createLRecordList(createLRecord(12, np.get(6), kconv)), true, false), rn);
      rn = nr.readNode(np.get(1), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(1), createLRecordList(r9), false, false, null, np.get(4)), rn);
      rn = nr.readNode(np.get(3), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(3), createLRecordList(r12, r13), false, true, np.get(4), np.get(2)), rn);
      rn = nr.readNode(np.get(4), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(4), createLRecordList(r10, r11), false, true, np.get(1), np.get(3)), rn);
      rn = nr.readNode(np.get(5), null);
      assertNodesEqual(new BPlusTreeNonLeafNode<K>(np.get(5), null, np.get(1), createLRecordList(createLRecord(10, np.get(4), kconv)), false, false), rn);
      rn = nr.readNode(np.get(6), kconv.convert(12));
      assertNodesEqual(new BPlusTreeNonLeafNode<K>(np.get(6), kconv.convert(12), np.get(3), createLRecordList(createLRecord(14, np.get(2), kconv)), false, false), rn);

      // 0[(5),12(6), ]
      // 5[(1),10(4), ] 6[(3),14(2), ]
      // 1[8,9] 4[10,11] 3[12,13] 2[14,15]
      KeyAndValue<K, V> r8 = createLRecord(kconv, vconv, 8, 8);
      btr.insert(r8.getKey(), r8.getValue());

      rn = nr.readNode(np.get(0), null);
      assertNodesEqual(new BPlusTreeNonLeafNode<K>(np.get(0), null, np.get(5), createLRecordList(createLRecord(12, np.get(6), kconv)), true, false), rn);
      rn = nr.readNode(np.get(1), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(1), createLRecordList(r8, r9), false, true, null, np.get(4)), rn);

      // 0[(5),12(6), ]
      // 5[(1),8(7),10(4)] 6[(3),14(2), ]
      // 1[7, ] *7[8,9] 4[10,11] 3[12,13] 2[14,15]
      KeyAndValue<K, V> r7 = createLRecord(kconv, vconv, 7, 7);
      btr.insert(r7.getKey(), r7.getValue());
      np.addLeaf();

      rn = nr.readNode(np.get(0), null);
      assertNodesEqual(new BPlusTreeNonLeafNode<K>(np.get(0), null, np.get(5), createLRecordList(createLRecord(12, np.get(6), kconv)), true, false), rn);
      rn = nr.readNode(np.get(1), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(1), createLRecordList(r7), false, false, null, np.get(7)), rn);
      rn = nr.readNode(np.get(4), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(4), createLRecordList(r10, r11), false, true, np.get(7), np.get(3)), rn);
      rn = nr.readNode(np.get(5), null);
      assertNodesEqual(new BPlusTreeNonLeafNode<K>(np.get(5), null, np.get(1), createLRecordList(createLRecord(8, np.get(7), kconv), createLRecord(10, np.get(4), kconv)), false, true), rn);
      rn = nr.readNode(np.get(7), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(7), createLRecordList(r8, r9), false, true, np.get(1), np.get(4)), rn);

      // 0[(5),12(6), ]
      // 5[(1),8(7),10(4)] 6[(3),14(2), ]
      // 1[6,7] 7[8,9] 4[10,11] 3[12,13] 2[14,15]
      KeyAndValue<K, V> r6 = createLRecord(kconv, vconv, 6, 6);
      btr.insert(r6.getKey(), r6.getValue());

      rn = nr.readNode(np.get(0), null);
      assertNodesEqual(new BPlusTreeNonLeafNode<K>(np.get(0), null, np.get(5), createLRecordList(createLRecord(12, np.get(6), kconv)), true, false), rn);
      rn = nr.readNode(np.get(1), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(1), createLRecordList(r6, r7), false, true, null, np.get(7)), rn);

      // 0[(5),8(9),12(6)]
      // 5[(1),6(8), ] *9[(7),10(4), ] 6[(3),14(2), ]
      // 1[5, ] *8[6,7] 7[8,9] 4[10,11] 3[12,13] 2[14,15]
      KeyAndValue<K, V> r5 = createLRecord(kconv, vconv, 5, 5);
      btr.insert(r5.getKey(), r5.getValue());
      np.addLeaf();
      np.addNonLeaf();

      rn = nr.readNode(np.get(0), null);
      assertNodesEqual(new BPlusTreeNonLeafNode<K>(np.get(0), null, np.get(5), createLRecordList(createLRecord(8, np.get(9), kconv), createLRecord(12, np.get(6), kconv)), true, true), rn);
      rn = nr.readNode(np.get(1), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(1), createLRecordList(r5), false, false, null, np.get(8)), rn);
      rn = nr.readNode(np.get(5), null);
      assertNodesEqual(new BPlusTreeNonLeafNode<K>(np.get(5), null, np.get(1), createLRecordList(createLRecord(6, np.get(8), kconv)), false, false), rn);
      rn = nr.readNode(np.get(7), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(7), createLRecordList(r8, r9), false, true, np.get(8), np.get(4)), rn);
      rn = nr.readNode(np.get(8), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(8), createLRecordList(r6, r7), false, true, np.get(1), np.get(7)), rn);
      rn = nr.readNode(np.get(9), kconv.convert(8));
      assertNodesEqual(new BPlusTreeNonLeafNode<K>(np.get(9), kconv.convert(8), np.get(7), createLRecordList(createLRecord(10, np.get(4), kconv)), false, false), rn);

      // 0[(5),8(9),12(6)]
      // 5[(1),6(8), ] 9[(7),10(4), ] 6[(3),14(2), ]
      // 1[4,5] 8[6,7] 7[8,9] 4[10,11] 3[12,13] 2[14,15]
      KeyAndValue<K, V> r4 = createLRecord(kconv, vconv, 4, 4);
      btr.insert(r4.getKey(), r4.getValue());

      rn = nr.readNode(np.get(0), null);
      assertNodesEqual(new BPlusTreeNonLeafNode<K>(np.get(0), null, np.get(5), createLRecordList(createLRecord(8, np.get(9), kconv), createLRecord(12, np.get(6), kconv)), true, true), rn);
      rn = nr.readNode(np.get(1), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(1), createLRecordList(r4, r5), false, true, null, np.get(8)), rn);

      // 0[(5),8(9),12(6)]
      // 5[(1),4(10),6(8)] 9[(7),10(4), ] 6[(3),14(2), ]
      // 1[3, ] *10[4,5] 8[6,7] 7[8,9] 4[10,11] 3[12,13] 2[14,15]
      KeyAndValue<K, V> r3 = createLRecord(kconv, vconv, 3, 3);
      btr.insert(r3.getKey(), r3.getValue());
      np.addLeaf();

      rn = nr.readNode(np.get(0), null);
      assertNodesEqual(new BPlusTreeNonLeafNode<K>(np.get(0), null, np.get(5), createLRecordList(createLRecord(8, np.get(9), kconv), createLRecord(12, np.get(6), kconv)), true, true), rn);
      rn = nr.readNode(np.get(1), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(1), createLRecordList(r3), false, false, null, np.get(10)), rn);
      rn = nr.readNode(np.get(5), null);
      assertNodesEqual(new BPlusTreeNonLeafNode<K>(np.get(5), null, np.get(1), createLRecordList(createLRecord(4, np.get(10), kconv), createLRecord(6, np.get(8), kconv)), false, true), rn);
      rn = nr.readNode(np.get(8), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(8), createLRecordList(r6, r7), false, true, np.get(10), np.get(7)), rn);
      rn = nr.readNode(np.get(10), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(10), createLRecordList(r4, r5), false, true, np.get(1), np.get(8)), rn);

      // 0[(5),8(9),12(6)]
      // 5[(1),4(10),6(8)] 9[(7),10(4), ] 6[(3),14(2), ]
      // 1[2, 3] 10[4,5] 8[6,7] 7[8,9] 4[10,11] 3[12,13] 2[14,15]
      KeyAndValue<K, V> r2 = createLRecord(kconv, vconv, 2, 2);
      btr.insert(r2.getKey(), r2.getValue());

      rn = nr.readNode(np.get(0), null);
      assertNodesEqual(new BPlusTreeNonLeafNode<K>(np.get(0), null, np.get(5), createLRecordList(createLRecord(8, np.get(9), kconv), createLRecord(12, np.get(6), kconv)), true, true), rn);
      rn = nr.readNode(np.get(1), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(1), createLRecordList(r2, r3), false, true, null, np.get(10)), rn);
      rn = nr.readNode(np.get(5), null);
      assertNodesEqual(new BPlusTreeNonLeafNode<K>(np.get(5), null, np.get(1), createLRecordList(createLRecord(4, np.get(10), kconv), createLRecord(6, np.get(8), kconv)), false, true), rn);

      // 0[(13),8(14), ]
      // *13[(5),4(12), ] *14[(9),12(6), ]
      // 5[(1),2(11), ] *12[(10),6(8), ] 9[(7),10(4), ] 6[(3),14(2), ]
      // 1[1,] *11[2,3] 10[4,5] 8[6,7] 7[8,9] 4[10,11] 3[12,13]
      // 2[14,15]
      KeyAndValue<K, V> r1 = createLRecord(kconv, vconv, 1, 1);
      btr.insert(r1.getKey(), r1.getValue());
      np.addLeaf();
      np.addNonLeaf();
      np.addNonLeaf();
      np.addNonLeaf();

      rn = nr.readNode(np.get(0), null);
      assertNodesEqual(new BPlusTreeNonLeafNode<K>(np.get(0), null, np.get(13), createLRecordList(createLRecord(8, np.get(14), kconv)), true, false), rn);
      rn = nr.readNode(np.get(1), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(1), createLRecordList(r1), false, false, null, np.get(11)), rn);
      rn = nr.readNode(np.get(2), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(2), createLRecordList(r14, r15), false, true, np.get(3), null), rn);
      rn = nr.readNode(np.get(3), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(3), createLRecordList(r12, r13), false, true, np.get(4), np.get(2)), rn);
      rn = nr.readNode(np.get(4), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(4), createLRecordList(r10, r11), false, true, np.get(7), np.get(3)), rn);
      rn = nr.readNode(np.get(5), null);
      assertNodesEqual(new BPlusTreeNonLeafNode<K>(np.get(5), null, np.get(1), createLRecordList(createLRecord(2, np.get(11), kconv)), false, false), rn);
      rn = nr.readNode(np.get(6), kconv.convert(12));
      assertNodesEqual(new BPlusTreeNonLeafNode<K>(np.get(6), kconv.convert(12), np.get(3), createLRecordList(createLRecord(14, np.get(2), kconv)), false, false), rn);
      rn = nr.readNode(np.get(7), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(7), createLRecordList(r8, r9), false, true, np.get(8), np.get(4)), rn);
      rn = nr.readNode(np.get(8), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(8), createLRecordList(r6, r7), false, true, np.get(10), np.get(7)), rn);
      rn = nr.readNode(np.get(9), kconv.convert(8));
      assertNodesEqual(new BPlusTreeNonLeafNode<K>(np.get(9), kconv.convert(8), np.get(7), createLRecordList(createLRecord(10, np.get(4), kconv)), false, false), rn);
      rn = nr.readNode(np.get(10), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(10), createLRecordList(r4, r5), false, true, np.get(11), np.get(8)), rn);
      rn = nr.readNode(np.get(11), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(11), createLRecordList(r2, r3), false, true, np.get(1), np.get(10)), rn);
      rn = nr.readNode(np.get(12), kconv.convert(4));
      assertNodesEqual(new BPlusTreeNonLeafNode<K>(np.get(12), kconv.convert(4), np.get(10), createLRecordList(createLRecord(6, np.get(8), kconv)), false, false), rn);
      rn = nr.readNode(np.get(13), null);
      assertNodesEqual(new BPlusTreeNonLeafNode<K>(np.get(13), null, np.get(5), createLRecordList(createLRecord(4, np.get(12), kconv)), false, false), rn);
      rn = nr.readNode(np.get(14), kconv.convert(8));
      assertNodesEqual(new BPlusTreeNonLeafNode<K>(np.get(14), kconv.convert(8), np.get(9), createLRecordList(createLRecord(12, np.get(6), kconv)), false, false), rn);

      // Delete 15
      // 0[(13),8(14),]
      // 13[(5),4(12),] 14[(9),12(6),]
      // 5[(1),2(11),] 12[(10),6(8),] 9[(7),10(4),] 6[(3),14(2),]
      // 1[1,] 11[2,3] 10[4,5] 8[6,7] 7[8,9] 4[10,11] 3[12,13] 2[14,]
      assertEquals(r15.getValue(), btr.delete(r15.getKey()));

      rn = nr.readNode(np.get(2), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(2), createLRecordList(r14), false, false, np.get(3), null), rn);

      // Delete 14
      // 0[(5),4(12),8(6)]
      // 5[(1),2(11),] 12[(10),6(8),] 6[(7),10(4),12(2)]
      // 1[1,] 11[2,3] 10[4,5] 8[6,7] 7[8,9] 4[10,11] 2[12,13]
      assertEquals(r14.getValue(), btr.delete(r14.getKey()));

      rn = nr.readNode(np.get(0), null);
      assertNodesEqual(new BPlusTreeNonLeafNode<K>(np.get(0), null, np.get(5), createLRecordList(createLRecord(4, np.get(12), kconv), createLRecord(8, np.get(6), kconv)), true, true), rn);
      rn = nr.readNode(np.get(2), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(2), createLRecordList(r12, r13), false, true, np.get(4), null), rn);
      assertNodeDeleted(nr, np.get(3));
      rn = nr.readNode(np.get(4), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(4), createLRecordList(r10, r11), false, true, np.get(7), np.get(2)), rn);
      rn = nr.readNode(np.get(6), kconv.convert(8));
      assertNodesEqual(new BPlusTreeNonLeafNode<K>(np.get(6), kconv.convert(8), np.get(7), createLRecordList(createLRecord(10, np.get(4), kconv), createLRecord(12, np.get(2), kconv)), false, true), rn);
      assertNodeDeleted(nr, np.get(9));
      assertNodeDeleted(nr, np.get(13));
      assertNodeDeleted(nr, np.get(14));

      // Delete 13
      // 0[(5),4(12),8(6)]
      // 5[(1),2(11),] 12[(10),6(8),] 6[(7),10(4),12(2)]
      // 1[1,] 11[2,3] 10[4,5] 8[6,7] 7[8,9] 4[10,11] 2[12]
      assertEquals(r13.getValue(), btr.delete(r13.getKey()));

      rn = nr.readNode(np.get(2), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(2), createLRecordList(r12), false, false, np.get(4), null), rn);

      // Delete 12
      // 0[(5),4(12),8(6)]
      // 5[(1),2(11),] 12[(10),6(8),] 6[(7),10(2),]
      // 1[1,] 11[2,3] 10[4,5] 8[6,7] 7[8,9] 2[10,11]
      assertEquals(r12.getValue(), btr.delete(r12.getKey()));

      rn = nr.readNode(np.get(2), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(2), createLRecordList(r10, r11), false, true, np.get(7), null), rn);
      assertNodeDeleted(nr, np.get(4));
      rn = nr.readNode(np.get(6), kconv.convert(8));
      assertNodesEqual(new BPlusTreeNonLeafNode<K>(np.get(6), kconv.convert(8), np.get(7), createLRecordList(createLRecord(10, np.get(2), kconv)), false, false), rn);
      rn = nr.readNode(np.get(7), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(7), createLRecordList(r8, r9), false, true, np.get(8), np.get(2)), rn);

      // Delete 11
      // 0[(5),4(12),8(6)]
      // 5[(1),2(11),] 12[(10),6(8),] 6[(7),10(2),]
      // 1[1,] 11[2,3] 10[4,5] 8[6,7] 7[8,9] 2[10,]
      assertEquals(r11.getValue(), btr.delete(r11.getKey()));

      rn = nr.readNode(np.get(2), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(2), createLRecordList(r10), false, false, np.get(7), null), rn);

      // Delete 10
      // 0[(5),4(6),)]
      // 5[(1),2(11),] 6[(10),6(8),8(2)]
      // 1[1,] 11[2,3] 10[4,5] 8[6,7] 2[8,9]
      assertEquals(r10.getValue(), btr.delete(r10.getKey()));

      rn = nr.readNode(np.get(0), null);
      assertNodesEqual(new BPlusTreeNonLeafNode<K>(np.get(0), null, np.get(5), createLRecordList(createLRecord(4, np.get(6), kconv)), true, false), rn);
      rn = nr.readNode(np.get(2), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(2), createLRecordList(r8, r9), false, true, np.get(8), null), rn);
      rn = nr.readNode(np.get(6), kconv.convert(4));
      assertNodesEqual(new BPlusTreeNonLeafNode<K>(np.get(6), kconv.convert(4), np.get(10), createLRecordList(createLRecord(6, np.get(8), kconv), createLRecord(8, np.get(2), kconv)), false, true), rn);
      assertNodeDeleted(nr, np.get(7));
      rn = nr.readNode(np.get(8), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(8), createLRecordList(r6, r7), false, true, np.get(10), np.get(2)), rn);
      assertNodeDeleted(nr, np.get(12));

      // Delete 9
      // 0[(5),4(6),)]
      // 5[(1),2(11),] 6[(10),6(8),8(2)]
      // 1[1,] 11[2,3] 10[4,5] 8[6,7] 2[8,]
      assertEquals(r9.getValue(), btr.delete(r9.getKey()));

      rn = nr.readNode(np.get(2), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(2), createLRecordList(r8), false, false, np.get(8), null), rn);

      // Delete 8
      // 0[(5),4(6),)]
      // 5[(1),2(11),] 6[(10),6(2),]
      // 1[1,] 11[2,3] 10[4,5] 2[6,7]
      assertEquals(r8.getValue(), btr.delete(r8.getKey()));

      rn = nr.readNode(np.get(2), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(2), createLRecordList(r6, r7), false, true, np.get(10), null), rn);
      rn = nr.readNode(np.get(6), kconv.convert(4));
      assertNodesEqual(new BPlusTreeNonLeafNode<K>(np.get(6), kconv.convert(4), np.get(10), createLRecordList(createLRecord(6, np.get(2), kconv)), false, false), rn);
      assertNodeDeleted(nr, np.get(8));
      rn = nr.readNode(np.get(10), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(10), createLRecordList(r4, r5), false, true, np.get(11), np.get(2)), rn);

      // Delete 7
      // 0[(5),4(6),)]
      // 5[(1),2(11),] 6[(10),6(2),]
      // 1[1,] 11[2,3] 10[4,5] 2[6,]
      assertEquals(r7.getValue(), btr.delete(r7.getKey()));

      rn = nr.readNode(np.get(2), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(2), createLRecordList(r6), false, false, np.get(10), null), rn);

      // Delete 6
      // 0[(1),2(11),4(2)]
      // 1[1,] 11[2,3] 2[4,5]
      assertEquals(r6.getValue(), btr.delete(r6.getKey()));

      rn = nr.readNode(np.get(0), null);
      assertNodesEqual(new BPlusTreeNonLeafNode<K>(np.get(0), null, np.get(1), createLRecordList(createLRecord(2, np.get(11), kconv), createLRecord(4, np.get(2), kconv)), true, true), rn);
      rn = nr.readNode(np.get(2), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(2), createLRecordList(r4, r5), false, true, np.get(11), null), rn);
      assertNodeDeleted(nr, np.get(5));
      assertNodeDeleted(nr, np.get(6));
      assertNodeDeleted(nr, np.get(10));
      rn = nr.readNode(np.get(11), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(11), createLRecordList(r2, r3), false, true, np.get(1), np.get(2)), rn);

      // Delete 5
      // 0[(1),2(11),4(2)]
      // 1[1,] 11[2,3] 2[4,]
      assertEquals(r5.getValue(), btr.delete(r5.getKey()));

      rn = nr.readNode(np.get(2), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(2), createLRecordList(r4), false, false, np.get(11), null), rn);

      // Delete 4
      // 0[(1),2(2),]
      // 1[1,] 2[2,3]
      assertEquals(r4.getValue(), btr.delete(r4.getKey()));

      rn = nr.readNode(np.get(0), null);
      assertNodesEqual(new BPlusTreeNonLeafNode<K>(np.get(0), null, np.get(1), createLRecordList(createLRecord(2, np.get(2), kconv)), true, false), rn);
      rn = nr.readNode(np.get(1), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(1), createLRecordList(r1), false, false, null, np.get(2)), rn);
      rn = nr.readNode(np.get(2), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(2), createLRecordList(r2, r3), false, true, np.get(1), null), rn);
      assertNodeDeleted(nr, np.get(11));

      // Delete 3
      // 0[(1),2(2),]
      // 1[1,] 2[2,]
      assertEquals(r3.getValue(), btr.delete(r3.getKey()));

      rn = nr.readNode(np.get(2), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(2), createLRecordList(r2), false, false, np.get(1), null), rn);

      // Delete 2
      // 0[1,]
      assertEquals(r2.getValue(), btr.delete(r2.getKey()));

      rn = nr.readNode(np.get(0), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(0), createLRecordList(r1), true, false, null, null), rn);
      assertNodeDeleted(nr, np.get(1));
      assertNodeDeleted(nr, np.get(2));

      // Delete 1
      // 0[,]
      assertEquals(r1.getValue(), btr.delete(r1.getKey()));

      rn = nr.readNode(np.get(0), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(0), Collections.EMPTY_LIST, true, false, null, null), rn);
      assertEquals(0, btr.size());
    }
    finally
    {
      btr.close();
    }
  }

  private <K extends Comparable<K>, V> void testInsertFrom15ToOneWithTwoRecordsPerNodeAndLruCacheInternal(Serializer<K> keySerializer, ValueConverter<K> kconv, Serializer<V> valueSerializer, ValueConverter<V> vconv, int cacheSize, int pointerSize, boolean leafNodePointers)
  {
    File tmpFile = FileSupport.createTempFile();
    try
    {
      ReadWritableFile f = new ReadWritableFileAdapter(tmpFile);
      byte[] barr = writeJunk(f);
      NodeRepository<K> nr = new FileBackedNodeRepository<K, V>(f, false, f.getSize(), new NumberOfRecordsNodeSizeStrategy(2), leafNodePointers, keySerializer, valueSerializer, pointerSize, 8192, null, new LogAdapterHolder(new StdOutLogAdapter()));
      if (cacheSize > 0)
      {
        nr = new LruCacheNodeRepository<K, V>(nr, cacheSize);
      }
      testInsertFrom15ToOneWithTwoRecordsPerNodeInternal(nr, kconv, vconv, getNonLeafNodeSize(keySerializer.getSerializedSize(), pointerSize, 2), getLeafNodeSize(keySerializer.getSerializedSize(), valueSerializer.getSerializedSize(), pointerSize, 2, leafNodePointers));
      assertJunkUntouched(f, barr);
    }
    finally
    {
      assertTrue(tmpFile.delete());
    }
  }

  private void testInsertFrom15ToOneWithTwoRecordsPerNodeInternal(int cacheSize)
  {
    for (int pointerSize : POINTER_SIZES)
    {
      for (boolean lnp : BOOLEANS)
      {
        testInsertFrom15ToOneWithTwoRecordsPerNodeAndLruCacheInternal(LongNullSerializer.INSTANCE, LONG_CONVERTER, LongSerializer.INSTANCE, LONG_CONVERTER, cacheSize, pointerSize, lnp);
        testInsertFrom15ToOneWithTwoRecordsPerNodeAndLruCacheInternal(LongNullSerializer.INSTANCE, LONG_CONVERTER, IntegerSerializer.INSTANCE, INTEGER_CONVERTER, cacheSize, pointerSize, lnp);
        testInsertFrom15ToOneWithTwoRecordsPerNodeAndLruCacheInternal(LongNullSerializer.INSTANCE, LONG_CONVERTER, ShortSerializer.INSTANCE, SHORT_CONVERTER, cacheSize, pointerSize, lnp);
        testInsertFrom15ToOneWithTwoRecordsPerNodeAndLruCacheInternal(IntegerNullSerializer.INSTANCE, INTEGER_CONVERTER, LongSerializer.INSTANCE, LONG_CONVERTER, cacheSize, pointerSize, lnp);
        testInsertFrom15ToOneWithTwoRecordsPerNodeAndLruCacheInternal(IntegerNullSerializer.INSTANCE, INTEGER_CONVERTER, IntegerSerializer.INSTANCE, INTEGER_CONVERTER, cacheSize, pointerSize, lnp);
        testInsertFrom15ToOneWithTwoRecordsPerNodeAndLruCacheInternal(IntegerNullSerializer.INSTANCE, INTEGER_CONVERTER, ShortSerializer.INSTANCE, SHORT_CONVERTER, cacheSize, pointerSize, lnp);
        testInsertFrom15ToOneWithTwoRecordsPerNodeAndLruCacheInternal(ShortNullSerializer.INSTANCE, SHORT_CONVERTER, LongSerializer.INSTANCE, LONG_CONVERTER, cacheSize, pointerSize, lnp);
        testInsertFrom15ToOneWithTwoRecordsPerNodeAndLruCacheInternal(ShortNullSerializer.INSTANCE, SHORT_CONVERTER, IntegerSerializer.INSTANCE, INTEGER_CONVERTER, cacheSize, pointerSize, lnp);
        testInsertFrom15ToOneWithTwoRecordsPerNodeAndLruCacheInternal(ShortNullSerializer.INSTANCE, SHORT_CONVERTER, ShortSerializer.INSTANCE, SHORT_CONVERTER, cacheSize, pointerSize, lnp);
      }
    }
  }

  @Test
  public void testInsertFrom15ToOneWithTwoRecordsPerNode()
  {
    testInsertFrom15ToOneWithTwoRecordsPerNodeInternal(0);
  }

  @Test
  public void testInsertFrom15ToOneWithTwoRecordsPerNodeAndSmallLruCache()
  {
    testInsertFrom15ToOneWithTwoRecordsPerNodeInternal(2);
  }

  @Test
  public void testInsertFrom15ToOneWithTwoRecordsPerNodeAndLargeLruCache()
  {
    testInsertFrom15ToOneWithTwoRecordsPerNodeInternal(20);
  }

  @SuppressWarnings("unchecked")
  private <K extends Comparable<K>, V> void testInsertFromOneToTenInRandomOrderWithTwoRecordsPerNodeInternal(NodeRepository<K> nr, ValueConverter<K> kconv, ValueConverter<V> vconv, long nonLeafNodeSize, long leafNodeSize)
  {
    NodePositions np = new NodePositions(nonLeafNodeSize, leafNodeSize, nr.getPositionOfRootNode());
    BPlusTree<K, V> btr = new BPlusTree<K, V>(nr, new LogAdapterHolder(new StdOutLogAdapter()));
    try
    {
      // Insert 4, 2, 3, 9, 8, 6, 5, 1, 10, 7
      // *0[4, ]
      KeyAndValue<K, V> r4 = createLRecord(kconv, vconv, 4, 4);
      btr.insert(r4.getKey(), r4.getValue());

      BPlusTreeNode<K, ?> rn = nr.readNode(np.get(0), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(0), createLRecordList(r4), true, false, null, null), rn);

      // 0[2,4]
      KeyAndValue<K, V> r2 = createLRecord(kconv, vconv, 2, 2);
      btr.insert(r2.getKey(), r2.getValue());

      rn = nr.readNode(np.get(0), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(0), createLRecordList(r2, r4), true, true, null, null), rn);

      // 0[(1),3(2), ]
      // *1[2, ] *2[3,4]
      KeyAndValue<K, V> r3 = createLRecord(kconv, vconv, 3, 3);
      btr.insert(r3.getKey(), r3.getValue());
      np.addLeaf();
      np.addLeaf();

      rn = nr.readNode(np.get(0), null);
      assertNodesEqual(new BPlusTreeNonLeafNode<K>(np.get(0), null, np.get(1), createLRecordList(createLRecord(3, np.get(2), kconv)), true, false), rn);
      rn = nr.readNode(np.get(1), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(1), createLRecordList(r2), false, false, null, np.get(2)), rn);
      rn = nr.readNode(np.get(2), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(2), createLRecordList(r3, r4), false, true, np.get(1), null), rn);

      // 0[(1),3(2),4(3)]
      // 1[2, ] 2[3, ] *3[4,9]
      KeyAndValue<K, V> r9 = createLRecord(kconv, vconv, 9, 9);
      btr.insert(r9.getKey(), r9.getValue());
      np.addLeaf();

      rn = nr.readNode(np.get(0), null);
      assertNodesEqual(new BPlusTreeNonLeafNode<K>(np.get(0), null, np.get(1), createLRecordList(createLRecord(3, np.get(2), kconv), createLRecord(4, np.get(3), kconv)), true, true), rn);
      rn = nr.readNode(np.get(1), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(1), createLRecordList(r2), false, false, null, np.get(2)), rn);
      rn = nr.readNode(np.get(2), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(2), createLRecordList(r3), false, false, np.get(1), np.get(3)), rn);
      rn = nr.readNode(np.get(3), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(3), createLRecordList(r4, r9), false, true, np.get(2), null), rn);
      assertEquals(4, btr.size());

      // 0[(5),4(6), ]
      // *5[(1),3(2), ] *6[(3),8(4), ]
      // 1[2, ] 2[3, ] 3[4, ] *4[8,9]
      KeyAndValue<K, V> r8 = createLRecord(kconv, vconv, 8, 8);
      btr.insert(r8.getKey(), r8.getValue());
      np.addLeaf();
      np.addNonLeaf();
      np.addNonLeaf();

      rn = nr.readNode(np.get(0), null);
      assertNodesEqual(new BPlusTreeNonLeafNode<K>(np.get(0), null, np.get(5), createLRecordList(createLRecord(4, np.get(6), kconv)), true, false), rn);
      rn = nr.readNode(np.get(1), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(1), createLRecordList(r2), false, false, null, np.get(2)), rn);
      rn = nr.readNode(np.get(2), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(2), createLRecordList(r3), false, false, np.get(1), np.get(3)), rn);
      rn = nr.readNode(np.get(3), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(3), createLRecordList(r4), false, false, np.get(2), np.get(4)), rn);
      rn = nr.readNode(np.get(4), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(4), createLRecordList(r8, r9), false, true, np.get(3), null), rn);
      rn = nr.readNode(np.get(5), null);
      assertNodesEqual(new BPlusTreeNonLeafNode<K>(np.get(5), null, np.get(1), createLRecordList(createLRecord(3, np.get(2), kconv)), false, false), rn);
      rn = nr.readNode(np.get(6), kconv.convert(4));
      assertNodesEqual(new BPlusTreeNonLeafNode<K>(np.get(6), kconv.convert(4), np.get(3), createLRecordList(createLRecord(8, np.get(4), kconv)), false, false), rn);

      // 0[(5),4(6), ]
      // 5[(1),3(2), ] 6[(3),8(4), ]
      // 1[2, ] 2[3, ] 3[4,6] 4[8,9]
      KeyAndValue<K, V> r6 = createLRecord(kconv, vconv, 6, 6);
      btr.insert(r6.getKey(), r6.getValue());

      rn = nr.readNode(np.get(3), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(3), createLRecordList(r4, r6), false, true, np.get(2), np.get(4)), rn);

      // 0[(5),4(6), ]
      // 5[(1),3(2), ] 6[(3),5(7),8(4)]
      // 1[2, ] 2[3, ] 3[4, ] *7[5,6] 4[8,9]
      KeyAndValue<K, V> r5 = createLRecord(kconv, vconv, 5, 5);
      btr.insert(r5.getKey(), r5.getValue());
      np.addLeaf();

      rn = nr.readNode(np.get(3), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(3), createLRecordList(r4), false, false, np.get(2), np.get(7)), rn);
      rn = nr.readNode(np.get(4), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(4), createLRecordList(r8, r9), false, true, np.get(7), null), rn);
      rn = nr.readNode(np.get(6), kconv.convert(4));
      assertNodesEqual(new BPlusTreeNonLeafNode<K>(np.get(6), kconv.convert(4), np.get(3), createLRecordList(createLRecord(5, np.get(7), kconv), createLRecord(8, np.get(4), kconv)), false, true), rn);
      rn = nr.readNode(np.get(7), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(7), createLRecordList(r5, r6), false, true, np.get(3), np.get(4)), rn);

      // 0[(5),4(6), ]
      // 5[(1),3(2), ] 6[(3),5(7),8(4)]
      // 1[1,2] 2[3, ] 3[4, ] 7[5,6] 4[8,9]
      KeyAndValue<K, V> r1 = createLRecord(kconv, vconv, 1, 1);
      btr.insert(r1.getKey(), r1.getValue());

      rn = nr.readNode(np.get(1), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(1), createLRecordList(r1, r2), false, true, null, np.get(2)), rn);

      // 0[(5),4(6),8(9)]
      // 5[(1),3(2), ] 6[(3),5(7), ] *9[(4),9(8), ]
      // 1[1,2] 2[3, ] 3[4, ] 7[5,6] 4[8, ] *8[9,10]
      KeyAndValue<K, V> r10 = createLRecord(kconv, vconv, 10, 10);
      btr.insert(r10.getKey(), r10.getValue());
      np.addLeaf();
      np.addNonLeaf();

      rn = nr.readNode(np.get(0), null);
      assertNodesEqual(new BPlusTreeNonLeafNode<K>(np.get(0), null, np.get(5), createLRecordList(createLRecord(4, np.get(6), kconv), createLRecord(8, np.get(9), kconv)), true, true), rn);
      rn = nr.readNode(np.get(4), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(4), createLRecordList(r8), false, false, np.get(7), np.get(8)), rn);
      rn = nr.readNode(np.get(6), kconv.convert(4));
      assertNodesEqual(new BPlusTreeNonLeafNode<K>(np.get(6), kconv.convert(4), np.get(3), createLRecordList(createLRecord(5, np.get(7), kconv)), false, false), rn);
      rn = nr.readNode(np.get(8), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(8), createLRecordList(r9, r10), false, true, np.get(4), null), rn);
      rn = nr.readNode(np.get(9), kconv.convert(8));
      assertNodesEqual(new BPlusTreeNonLeafNode<K>(np.get(9), kconv.convert(8), np.get(4), createLRecordList(createLRecord(9, np.get(8), kconv)), false, false), rn);

      // 0[(5),4(6),8(9)]
      // 5[(1),3(2), ] 6[(3),5(7),6(10)] 9[(4),9(8), ]
      // 1[1,2] 2[3, ] 3[4, ] 7[5, ] *10[6,7] 4[8, ] 8[9,10]
      KeyAndValue<K, V> r7 = createLRecord(kconv, vconv, 7, 7);
      btr.insert(r7.getKey(), r7.getValue());
      np.addLeaf();

      rn = nr.readNode(np.get(0), null);
      assertNodesEqual(new BPlusTreeNonLeafNode<K>(np.get(0), null, np.get(5), createLRecordList(createLRecord(4, np.get(6), kconv), createLRecord(8, np.get(9), kconv)), true, true), rn);
      rn = nr.readNode(np.get(1), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(1), createLRecordList(r1, r2), false, true, null, np.get(2)), rn);
      rn = nr.readNode(np.get(2), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(2), createLRecordList(r3), false, false, np.get(1), np.get(3)), rn);
      rn = nr.readNode(np.get(3), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(3), createLRecordList(r4), false, false, np.get(2), np.get(7)), rn);
      rn = nr.readNode(np.get(4), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(4), createLRecordList(r8), false, false, np.get(10), np.get(8)), rn);
      rn = nr.readNode(np.get(5), null);
      assertNodesEqual(new BPlusTreeNonLeafNode<K>(np.get(5), null, np.get(1), createLRecordList(createLRecord(3, np.get(2), kconv)), false, false), rn);
      rn = nr.readNode(np.get(6), kconv.convert(4));
      assertNodesEqual(new BPlusTreeNonLeafNode<K>(np.get(6), kconv.convert(4), np.get(3), createLRecordList(createLRecord(5, np.get(7), kconv), createLRecord(6, np.get(10), kconv)), false, true), rn);
      rn = nr.readNode(np.get(7), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(7), createLRecordList(r5), false, false, np.get(3), np.get(10)), rn);
      rn = nr.readNode(np.get(8), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(8), createLRecordList(r9, r10), false, true, np.get(4), null), rn);
      rn = nr.readNode(np.get(9), kconv.convert(8));
      assertNodesEqual(new BPlusTreeNonLeafNode<K>(np.get(9), kconv.convert(8), np.get(4), createLRecordList(createLRecord(9, np.get(8), kconv)), false, false), rn);
      rn = nr.readNode(np.get(10), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(10), createLRecordList(r6, r7), false, true, np.get(7), np.get(4)), rn);
      assertEquals(10, btr.size());

      // Delete 4, 5, 6, 7, 2, 3, 9, 1, 8, 10

      // Delete 4
      // 0[(5),5(6),8(9)]
      // 5[(1),3(2),] 6[(3),6(10),] 9[(4),9(8),]
      // 1[1,2] 2[3,] 3[5,] 10[6,7] 4[8,] 8[9,10]
      assertEquals(r4.getValue(), btr.delete(r4.getKey()));

      rn = nr.readNode(np.get(0), null);
      assertNodesEqual(new BPlusTreeNonLeafNode<K>(np.get(0), null, np.get(5), createLRecordList(createLRecord(5, np.get(6), kconv), createLRecord(8, np.get(9), kconv)), true, true), rn);
      rn = nr.readNode(np.get(3), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(3), createLRecordList(r5), false, false, np.get(2), np.get(10)), rn);
      rn = nr.readNode(np.get(6), kconv.convert(5));
      assertNodesEqual(new BPlusTreeNonLeafNode<K>(np.get(6), kconv.convert(5), np.get(3), createLRecordList(createLRecord(6, np.get(10), kconv)), false, false), rn);
      assertNodeDeleted(nr, np.get(7));
      rn = nr.readNode(np.get(10), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(10), createLRecordList(r6, r7), false, true, np.get(3), np.get(4)), rn);

      // Delete 5
      // 0[(6),8(9),]
      // 6[(1),3(2),6(3)] 9[(4),9(8),]
      // 1[1,2] 2[3,] 3[6,7] 4[8,] 8[9,10]
      assertEquals(r5.getValue(), btr.delete(r5.getKey()));

      rn = nr.readNode(np.get(0), null);
      assertNodesEqual(new BPlusTreeNonLeafNode<K>(np.get(0), null, np.get(6), createLRecordList(createLRecord(8, np.get(9), kconv)), true, false), rn);
      rn = nr.readNode(np.get(3), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(3), createLRecordList(r6, r7), false, true, np.get(2), np.get(4)), rn);
      rn = nr.readNode(np.get(4), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(4), createLRecordList(r8), false, false, np.get(3), np.get(8)), rn);
      assertNodeDeleted(nr, np.get(5));
      rn = nr.readNode(np.get(6), null);
      assertNodesEqual(new BPlusTreeNonLeafNode<K>(np.get(6), null, np.get(1), createLRecordList(createLRecord(3, np.get(2), kconv), createLRecord(6, np.get(3), kconv)), false, true), rn);
      assertNodeDeleted(nr, np.get(10));

      // Delete 6
      // 0[(6),8(9),]
      // 6[(1),3(2),7(3)] 9[(4),9(8),]
      // 1[1,2] 2[3,] 3[7,] 4[8,] 8[9,10]
      assertEquals(r6.getValue(), btr.delete(r6.getKey()));

      rn = nr.readNode(np.get(3), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(3), createLRecordList(r7), false, false, np.get(2), np.get(4)), rn);
      rn = nr.readNode(np.get(6), null);
      assertNodesEqual(new BPlusTreeNonLeafNode<K>(np.get(6), null, np.get(1), createLRecordList(createLRecord(3, np.get(2), kconv), createLRecord(7, np.get(3), kconv)), false, true), rn);

      // Delete 7
      // 0[(6),8(9),]
      // 6[(1),3(3),] 9[(4),9(8),]
      // 1[1,2] 3[3,] 4[8,] 8[9,10]
      assertEquals(r7.getValue(), btr.delete(r7.getKey()));

      rn = nr.readNode(np.get(1), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(1), createLRecordList(r1, r2), false, true, null, np.get(3)), rn);
      assertNodeDeleted(nr, np.get(2));
      rn = nr.readNode(np.get(3), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(3), createLRecordList(r3), false, false, np.get(1), np.get(4)), rn);
      rn = nr.readNode(np.get(6), null);
      assertNodesEqual(new BPlusTreeNonLeafNode<K>(np.get(6), null, np.get(1), createLRecordList(createLRecord(3, np.get(3), kconv)), false, false), rn);

      // Delete 2
      // 0[(6),8(9),]
      // 6[(1),3(3),] 9[(4),9(8),]
      // 1[1,] 3[3,] 4[8,] 8[9,10]
      assertEquals(r2.getValue(), btr.delete(r2.getKey()));

      rn = nr.readNode(np.get(1), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(1), createLRecordList(r1), false, false, null, np.get(3)), rn);

      // Delete 3
      // 0[(3),8(4),9(8)]
      // 3[1,] 4[8,] 8[9,10]
      assertEquals(r3.getValue(), btr.delete(r3.getKey()));

      rn = nr.readNode(np.get(0), null);
      assertNodesEqual(new BPlusTreeNonLeafNode<K>(np.get(0), null, np.get(3), createLRecordList(createLRecord(8, np.get(4), kconv), createLRecord(9, np.get(8), kconv)), true, true), rn);
      assertNodeDeleted(nr, np.get(1));
      rn = nr.readNode(np.get(3), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(3), createLRecordList(r1), false, false, null, np.get(4)), rn);
      assertNodeDeleted(nr, np.get(6));
      assertNodeDeleted(nr, np.get(9));

      // Delete 9
      // 0[(3),8(4),10(8)]
      // 3[1,] 4[8,] 8[10]
      assertEquals(r9.getValue(), btr.delete(r9.getKey()));

      rn = nr.readNode(np.get(0), null);
      assertNodesEqual(new BPlusTreeNonLeafNode<K>(np.get(0), null, np.get(3), createLRecordList(createLRecord(8, np.get(4), kconv), createLRecord(10, np.get(8), kconv)), true, true), rn);
      rn = nr.readNode(np.get(8), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(8), createLRecordList(r10), false, false, np.get(4), null), rn);

      // Delete 1
      // 0[(3),10(8)]
      // 3[8,] 8[10]
      assertEquals(r1.getValue(), btr.delete(r1.getKey()));

      rn = nr.readNode(np.get(0), null);
      assertNodesEqual(new BPlusTreeNonLeafNode<K>(np.get(0), null, np.get(3), createLRecordList(createLRecord(10, np.get(8), kconv)), true, false), rn);
      rn = nr.readNode(np.get(3), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(3), createLRecordList(r8), false, false, null, np.get(8)), rn);
      assertNodeDeleted(nr, np.get(4));
      rn = nr.readNode(np.get(8), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(8), createLRecordList(r10), false, false, np.get(3), null), rn);

      // Delete 8
      // 0[10,]
      assertEquals(r8.getValue(), btr.delete(r8.getKey()));

      rn = nr.readNode(np.get(0), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(0), createLRecordList(r10), true, false, null, null), rn);
      assertNodeDeleted(nr, np.get(3));
      assertNodeDeleted(nr, np.get(8));
      assertEquals(1, btr.size());

      // Delete 10
      // 0[,]
      assertEquals(r10.getValue(), btr.delete(r10.getKey()));

      rn = nr.readNode(np.get(0), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(0), Collections.EMPTY_LIST, true, false, null, null), rn);
    }
    finally
    {
      btr.close();
    }
  }

  private <K extends Comparable<K>, V> void testInsertFromOneToTenInRandomOrderWithTwoRecordsPerNodeInternal(Serializer<K> keySerializer, ValueConverter<K> kconv, Serializer<V> valueSerializer, ValueConverter<V> vconv, int cacheSize, int pointerSize, boolean leafNodePointers)
  {
    File tmpFile = FileSupport.createTempFile();
    try
    {
      ReadWritableFile f = new ReadWritableFileAdapter(tmpFile);
      byte[] barr = writeJunk(f);
      NodeRepository<K> nr = new FileBackedNodeRepository<K, V>(f, false, f.getSize(), new NumberOfRecordsNodeSizeStrategy(2), leafNodePointers, keySerializer, valueSerializer, pointerSize, 8192, null, new LogAdapterHolder(new StdOutLogAdapter()));
      if (cacheSize > 0)
      {
        nr = new LruCacheNodeRepository<K, V>(nr, cacheSize);
      }
      testInsertFromOneToTenInRandomOrderWithTwoRecordsPerNodeInternal(nr, kconv, vconv, getNonLeafNodeSize(keySerializer.getSerializedSize(), pointerSize, 2), getLeafNodeSize(keySerializer.getSerializedSize(), valueSerializer.getSerializedSize(), pointerSize, 2, leafNodePointers));
      assertJunkUntouched(f, barr);
    }
    finally
    {
      assertTrue(tmpFile.delete());
    }
  }

  private void testInsertFromOneToTenInRandomOrderWithTwoRecordsPerNodeInternal(int cacheSize)
  {
    for (int pointerSize : POINTER_SIZES)
    {
      for (boolean lnp : BOOLEANS)
      {
        testInsertFromOneToTenInRandomOrderWithTwoRecordsPerNodeInternal(LongNullSerializer.INSTANCE, LONG_CONVERTER, LongSerializer.INSTANCE, LONG_CONVERTER, cacheSize, pointerSize, lnp);
        testInsertFromOneToTenInRandomOrderWithTwoRecordsPerNodeInternal(LongNullSerializer.INSTANCE, LONG_CONVERTER, IntegerSerializer.INSTANCE, INTEGER_CONVERTER, cacheSize, pointerSize, lnp);
        testInsertFromOneToTenInRandomOrderWithTwoRecordsPerNodeInternal(LongNullSerializer.INSTANCE, LONG_CONVERTER, ShortSerializer.INSTANCE, SHORT_CONVERTER, cacheSize, pointerSize, lnp);
        testInsertFromOneToTenInRandomOrderWithTwoRecordsPerNodeInternal(IntegerNullSerializer.INSTANCE, INTEGER_CONVERTER, LongSerializer.INSTANCE, LONG_CONVERTER, cacheSize, pointerSize, lnp);
        testInsertFromOneToTenInRandomOrderWithTwoRecordsPerNodeInternal(IntegerNullSerializer.INSTANCE, INTEGER_CONVERTER, IntegerSerializer.INSTANCE, INTEGER_CONVERTER, cacheSize, pointerSize, lnp);
        testInsertFromOneToTenInRandomOrderWithTwoRecordsPerNodeInternal(IntegerNullSerializer.INSTANCE, INTEGER_CONVERTER, ShortSerializer.INSTANCE, SHORT_CONVERTER, cacheSize, pointerSize, lnp);
        testInsertFromOneToTenInRandomOrderWithTwoRecordsPerNodeInternal(ShortNullSerializer.INSTANCE, SHORT_CONVERTER, LongSerializer.INSTANCE, LONG_CONVERTER, cacheSize, pointerSize, lnp);
        testInsertFromOneToTenInRandomOrderWithTwoRecordsPerNodeInternal(ShortNullSerializer.INSTANCE, SHORT_CONVERTER, IntegerSerializer.INSTANCE, INTEGER_CONVERTER, cacheSize, pointerSize, lnp);
        testInsertFromOneToTenInRandomOrderWithTwoRecordsPerNodeInternal(ShortNullSerializer.INSTANCE, SHORT_CONVERTER, ShortSerializer.INSTANCE, SHORT_CONVERTER, cacheSize, pointerSize, lnp);
      }
    }
  }

  @Test
  public void testInsertFromOneToTenInRandomOrderWithTwoRecordsPerNode()
  {
    testInsertFromOneToTenInRandomOrderWithTwoRecordsPerNodeInternal(0);
  }

  @Test
  public void testInsertFromOneToTenInRandomOrderWithTwoRecordsPerNodeAndSmallLruCache()
  {
    testInsertFromOneToTenInRandomOrderWithTwoRecordsPerNodeInternal(2);
  }

  @Test
  public void testInsertFromOneToTenInRandomOrderWithTwoRecordsPerNodeLargeLruCache()
  {
    testInsertFromOneToTenInRandomOrderWithTwoRecordsPerNodeInternal(20);
  }

  @SuppressWarnings("unchecked")
  private <K extends Comparable<K>, V> void testInsertWithTreeRecordsPerNodeInternal(NodeRepository<K> nr, ValueConverter<K> kconv, ValueConverter<V> vconv, long nonLeafNodeSize, long leafNodeSize)
  {
    NodePositions np = new NodePositions(nonLeafNodeSize, leafNodeSize, nr.getPositionOfRootNode());
    BPlusTree<K, V> btr = new BPlusTree<K, V>(nr, new LogAdapterHolder(new StdOutLogAdapter()));
    try
    {
      // Insert 5, 10, 30, 50, 25, 15, 20, 55, 75, 80, 85, 60, 65, 90,
      // 28, 70, 95, 72

      // 5, 10, 30
      // 0[5,10,30]
      KeyAndValue<K, V> r5 = createLRecord(kconv, vconv, 5, 5);
      btr.insert(r5.getKey(), r5.getValue());
      KeyAndValue<K, V> r10 = createLRecord(kconv, vconv, 10, 10);
      btr.insert(r10.getKey(), r10.getValue());
      KeyAndValue<K, V> r30 = createLRecord(kconv, vconv, 30, 30);
      btr.insert(r30.getKey(), r30.getValue());

      BPlusTreeNode<K, ?> rn = nr.readNode(np.get(0), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(0), createLRecordList(r5, r10, r30), true, true, null, null), rn);

      // 50
      // 0[(1),30(2),,]
      // *1[5,10,] *2[30,50,]
      KeyAndValue<K, V> r50 = createLRecord(kconv, vconv, 50, 50);
      btr.insert(r50.getKey(), r50.getValue());
      np.addLeaf();
      np.addLeaf();

      rn = nr.readNode(np.get(0), null);
      assertNodesEqual(new BPlusTreeNonLeafNode<K>(np.get(0), null, np.get(1), createLRecordList(createLRecord(30, np.get(2), kconv)), true, false), rn);
      rn = nr.readNode(np.get(1), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(1), createLRecordList(r5, r10), false, false, null, np.get(2)), rn);
      rn = nr.readNode(np.get(2), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(2), createLRecordList(r30, r50), false, false, np.get(1), null), rn);

      // 25
      // 0[(1),30(2),,]
      // 1[5,10,25] 2[30,50,]
      KeyAndValue<K, V> r25 = createLRecord(kconv, vconv, 25, 25);
      btr.insert(r25.getKey(), r25.getValue());

      rn = nr.readNode(np.get(1), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(1), createLRecordList(r5, r10, r25), false, true, null, np.get(2)), rn);

      // 15
      // 0[(1),15(3),30(2),]
      // 1[5,10,] *3[15,25,] 2[30,50,]
      KeyAndValue<K, V> r15 = createLRecord(kconv, vconv, 15, 15);
      btr.insert(r15.getKey(), r15.getValue());
      np.addLeaf();

      rn = nr.readNode(np.get(0), null);
      assertNodesEqual(new BPlusTreeNonLeafNode<K>(np.get(0), null, np.get(1), createLRecordList(createLRecord(15, np.get(3), kconv), createLRecord(30, np.get(2), kconv)), true, false), rn);
      rn = nr.readNode(np.get(1), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(1), createLRecordList(r5, r10), false, false, null, np.get(3)), rn);
      rn = nr.readNode(np.get(2), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(2), createLRecordList(r30, r50), false, false, np.get(3), null), rn);
      rn = nr.readNode(np.get(3), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(3), createLRecordList(r15, r25), false, false, np.get(1), np.get(2)), rn);

      // 20
      // 0[(1),15(3),30(2),]
      // 1[5,10,] 3[15,20,25] 2[30,50,]
      KeyAndValue<K, V> r20 = createLRecord(kconv, vconv, 20, 20);
      btr.insert(r20.getKey(), r20.getValue());

      rn = nr.readNode(np.get(3), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(3), createLRecordList(r15, r20, r25), false, true, np.get(1), np.get(2)), rn);

      // 55
      // 0[(1),15(3),30(2),]
      // 1[5,10,] 3[15,20,25] 2[30,50,55]
      KeyAndValue<K, V> r55 = createLRecord(kconv, vconv, 55, 55);
      btr.insert(r55.getKey(), r55.getValue());

      rn = nr.readNode(np.get(2), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(2), createLRecordList(r30, r50, r55), false, true, np.get(3), null), rn);

      // 75
      // 0[(1),15(3),30(2),55(4)]
      // 1[5,10,] 3[15,20,25] 2[30,50,] *4[55,75,]
      KeyAndValue<K, V> r75 = createLRecord(kconv, vconv, 75, 75);
      btr.insert(r75.getKey(), r75.getValue());
      np.addLeaf();

      rn = nr.readNode(np.get(0), null);
      assertNodesEqual(new BPlusTreeNonLeafNode<K>(np.get(0), null, np.get(1), createLRecordList(createLRecord(15, np.get(3), kconv), createLRecord(30, np.get(2), kconv), createLRecord(55, np.get(4), kconv)), true, true), rn);
      rn = nr.readNode(np.get(2), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(2), createLRecordList(r30, r50), false, false, np.get(3), np.get(4)), rn);
      rn = nr.readNode(np.get(4), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(4), createLRecordList(r55, r75), false, false, np.get(2), null), rn);

      // 80
      // 0[(1),15(3),30(2),55(4)]
      // 1[5,10,] 3[15,20,25] 2[30,50,] 4[55,75,80]
      KeyAndValue<K, V> r80 = createLRecord(kconv, vconv, 80, 80);
      btr.insert(r80.getKey(), r80.getValue());

      rn = nr.readNode(np.get(4), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(4), createLRecordList(r55, r75, r80), false, true, np.get(2), null), rn);

      // 85
      // 0[(6),55(7),,]
      // *6[(1),15(3),30(2),] *7[(4),80(5),,]
      // 1[5,10,] 3[15,20,25] 2[30,50,] 4[55,75,] *5[80,85,]
      KeyAndValue<K, V> r85 = createLRecord(kconv, vconv, 85, 85);
      btr.insert(r85.getKey(), r85.getValue());
      np.addLeaf();
      np.addNonLeaf();
      np.addNonLeaf();

      rn = nr.readNode(np.get(0), null);
      assertNodesEqual(new BPlusTreeNonLeafNode<K>(np.get(0), null, np.get(6), createLRecordList(createLRecord(55, np.get(7), kconv)), true, false), rn);
      rn = nr.readNode(np.get(4), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(4), createLRecordList(r55, r75), false, false, np.get(2), np.get(5)), rn);
      rn = nr.readNode(np.get(5), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(5), createLRecordList(r80, r85), false, false, np.get(4), null), rn);
      rn = nr.readNode(np.get(6), null);
      assertNodesEqual(new BPlusTreeNonLeafNode<K>(np.get(6), null, np.get(1), createLRecordList(createLRecord(15, np.get(3), kconv), createLRecord(30, np.get(2), kconv)), false, false), rn);
      rn = nr.readNode(np.get(7), kconv.convert(55));
      assertNodesEqual(new BPlusTreeNonLeafNode<K>(np.get(7), kconv.convert(55), np.get(4), createLRecordList(createLRecord(80, np.get(5), kconv)), false, false), rn);

      // 60
      // 0[(6),55(7),,]
      // 6[(1),15(3),30(2),] 7[(4),80(5),,]
      // 1[5,10,] 3[15,20,25] 2[30,50,] 4[55,60,75] 5[80,85,]
      KeyAndValue<K, V> r60 = createLRecord(kconv, vconv, 60, 60);
      btr.insert(r60.getKey(), r60.getValue());

      rn = nr.readNode(np.get(4), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(4), createLRecordList(r55, r60, r75), false, true, np.get(2), np.get(5)), rn);

      // 65
      // 0[(6),55(7),,]
      // 6[(1),15(3),30(2),] 7[(4),65(8),80(5),]
      // 1[5,10,] 3[15,20,25] 2[30,50,] 4[55,60,] *8[65,75,] 5[80,85,]
      KeyAndValue<K, V> r65 = createLRecord(kconv, vconv, 65, 65);
      btr.insert(r65.getKey(), r65.getValue());
      np.addLeaf();

      rn = nr.readNode(np.get(4), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(4), createLRecordList(r55, r60), false, false, np.get(2), np.get(8)), rn);
      rn = nr.readNode(np.get(5), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(5), createLRecordList(r80, r85), false, false, np.get(8), null), rn);
      rn = nr.readNode(np.get(7), kconv.convert(55));
      assertNodesEqual(new BPlusTreeNonLeafNode<K>(np.get(7), kconv.convert(55), np.get(4), createLRecordList(createLRecord(65, np.get(8), kconv), createLRecord(80, np.get(5), kconv)), false, false), rn);
      rn = nr.readNode(np.get(8), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(8), createLRecordList(r65, r75), false, false, np.get(4), np.get(5)), rn);

      // 90
      // 0[(6),55(7),,]
      // 6[(1),15(3),30(2),] 7[(4),65(8),80(5),]
      // 1[5,10,] 3[15,20,25] 2[30,50,] 4[55,60,] 8[65,75,]
      // 5[80,85,90]
      KeyAndValue<K, V> r90 = createLRecord(kconv, vconv, 90, 90);
      btr.insert(r90.getKey(), r90.getValue());

      rn = nr.readNode(np.get(5), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(5), createLRecordList(r80, r85, r90), false, true, np.get(8), null), rn);

      // 28
      // 0[(6),55(7),,]
      // 6[(1),15(3),25(9),30(2)] 7[(4),65(8),80(5),]
      // 1[5,10,] 3[15,20,] *9[25,28,] 2[30,50,] 4[55,60,] 8[65,75,]
      // 5[80,85,90]
      KeyAndValue<K, V> r28 = createLRecord(kconv, vconv, 28, 28);
      btr.insert(r28.getKey(), r28.getValue());
      np.addLeaf();

      rn = nr.readNode(np.get(2), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(2), createLRecordList(r30, r50), false, false, np.get(9), np.get(4)), rn);
      rn = nr.readNode(np.get(3), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(3), createLRecordList(r15, r20), false, false, np.get(1), np.get(9)), rn);
      rn = nr.readNode(np.get(6), null);
      assertNodesEqual(new BPlusTreeNonLeafNode<K>(np.get(6), null, np.get(1), createLRecordList(createLRecord(15, np.get(3), kconv), createLRecord(25, np.get(9), kconv), createLRecord(30, np.get(2), kconv)), false, true), rn);
      rn = nr.readNode(np.get(9), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(9), createLRecordList(r25, r28), false, false, np.get(3), np.get(2)), rn);

      // 70
      // 0[(6),55(7),,]
      // 6[(1),15(3),25(9),30(2)] 7[(4),65(8),80(5),]
      // 1[5,10,] 3[15,20,] 9[25,28,] 2[30,50,] 4[55,60,] 8[65,70,75]
      // 5[80,85,90]
      KeyAndValue<K, V> r70 = createLRecord(kconv, vconv, 70, 70);
      btr.insert(r70.getKey(), r70.getValue());

      rn = nr.readNode(np.get(8), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(8), createLRecordList(r65, r70, r75), false, true, np.get(4), np.get(5)), rn);

      // 95
      // 0[(6),55(7),,]
      // 6[(1),15(3),25(9),30(2)] 7[(4),65(8),80(5),90(10)]
      // 1[5,10,] 3[15,20,] 9[25,28,] 2[30,50,] 4[55,60,] 8[65,70,75]
      // 5[80,85,] *10[90,95,]
      KeyAndValue<K, V> r95 = createLRecord(kconv, vconv, 95, 95);
      btr.insert(r95.getKey(), r95.getValue());
      np.addLeaf();

      rn = nr.readNode(np.get(5), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(5), createLRecordList(r80, r85), false, false, np.get(8), np.get(10)), rn);
      rn = nr.readNode(np.get(7), kconv.convert(55));
      assertNodesEqual(new BPlusTreeNonLeafNode<K>(np.get(7), kconv.convert(55), np.get(4), createLRecordList(createLRecord(65, np.get(8), kconv), createLRecord(80, np.get(5), kconv), createLRecord(90, np.get(10), kconv)), false, true), rn);
      rn = nr.readNode(np.get(10), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(10), createLRecordList(r90, r95), false, false, np.get(5), null), rn);

      // 72
      // 0[(6),55(7),80(12),]
      // 6[(1),15(3),25(9),30(2)] 7[(4),65(8),72(11),]
      // *12[(5),90(10),,]
      // 1[5,10,] 3[15,20,] 9[25,28,] 2[30,50,] 4[55,60,] 8[65,70,]
      // *11[72,75,] 5[80,85,] 10[90,95,]
      KeyAndValue<K, V> r72 = createLRecord(kconv, vconv, 72, 72);
      btr.insert(r72.getKey(), r72.getValue());
      np.addLeaf();
      np.addNonLeaf();

      rn = nr.readNode(np.get(0), null);
      assertNodesEqual(new BPlusTreeNonLeafNode<K>(np.get(0), null, np.get(6), createLRecordList(createLRecord(55, np.get(7), kconv), createLRecord(80, np.get(12), kconv)), true, false), rn);
      rn = nr.readNode(np.get(1), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(1), createLRecordList(r5, r10), false, false, null, np.get(3)), rn);
      rn = nr.readNode(np.get(2), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(2), createLRecordList(r30, r50), false, false, np.get(9), np.get(4)), rn);
      rn = nr.readNode(np.get(3), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(3), createLRecordList(r15, r20), false, false, np.get(1), np.get(9)), rn);
      rn = nr.readNode(np.get(4), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(4), createLRecordList(r55, r60), false, false, np.get(2), np.get(8)), rn);
      rn = nr.readNode(np.get(5), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(5), createLRecordList(r80, r85), false, false, np.get(11), np.get(10)), rn);
      rn = nr.readNode(np.get(6), null);
      assertNodesEqual(new BPlusTreeNonLeafNode<K>(np.get(6), null, np.get(1), createLRecordList(createLRecord(15, np.get(3), kconv), createLRecord(25, np.get(9), kconv), createLRecord(30, np.get(2), kconv)), false, true), rn);
      rn = nr.readNode(np.get(7), kconv.convert(55));
      assertNodesEqual(new BPlusTreeNonLeafNode<K>(np.get(7), kconv.convert(55), np.get(4), createLRecordList(createLRecord(65, np.get(8), kconv), createLRecord(72, np.get(11), kconv)), false, false), rn);
      rn = nr.readNode(np.get(8), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(8), createLRecordList(r65, r70), false, false, np.get(4), np.get(11)), rn);
      rn = nr.readNode(np.get(9), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(9), createLRecordList(r25, r28), false, false, np.get(3), np.get(2)), rn);
      rn = nr.readNode(np.get(10), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(10), createLRecordList(r90, r95), false, false, np.get(5), null), rn);
      rn = nr.readNode(np.get(11), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(11), createLRecordList(r72, r75), false, false, np.get(8), np.get(5)), rn);
      rn = nr.readNode(np.get(12), kconv.convert(80));
      assertNodesEqual(new BPlusTreeNonLeafNode<K>(np.get(12), kconv.convert(80), np.get(5), createLRecordList(createLRecord(90, np.get(10), kconv)), false, false), rn);

      // Delete 75, 30, 20, 10, 72, 90, 5, 70, 65, 95, 25, 28, 50, 60,
      // 80, 55, 15, 85

      // Delete 75
      // 0[(6),55(7),80(12),]
      // 6[(1),15(3),25(9),30(2)] 7[(4),65(8),72(11),]
      // 12[(5),90(10),,]
      // 1[5,10,] 3[15,20,] 9[25,28,] 2[30,50,] 4[55,60,] 8[65,70,]
      // 11[72,,] 5[80,85,] 10[90,95,]
      assertEquals(r75.getValue(), btr.delete(r75.getKey()));

      rn = nr.readNode(np.get(11), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(11), createLRecordList(r72), false, false, np.get(8), np.get(5)), rn);

      // Delete 30
      // 0[(6),55(7),80(12),]
      // 6[(1),15(3),25(9),50(2)] 7[(4),65(8),72(11),]
      // 12[(5),90(10),,]
      // 1[5,10,] 3[15,20,] 9[25,28,] 2[50,,] 4[55,60,] 8[65,70,]
      // 11[72,,] 5[80,85,] 10[90,95,]
      assertEquals(r30.getValue(), btr.delete(r30.getKey()));

      rn = nr.readNode(np.get(2), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(2), createLRecordList(r50), false, false, np.get(9), np.get(4)), rn);
      rn = nr.readNode(np.get(6), null);
      assertNodesEqual(new BPlusTreeNonLeafNode<K>(np.get(6), null, np.get(1), createLRecordList(createLRecord(15, np.get(3), kconv), createLRecord(25, np.get(9), kconv), createLRecord(50, np.get(2), kconv)), false, true), rn);

      // Delete 20
      // 0[(6),55(7),80(12),]
      // 6[(1),15(3),25(9),50(2)] 7[(4),65(8),72(11),]
      // 12[(5),90(10),,]
      // 1[5,10,] 3[15,,] 9[25,28,] 2[50,,] 4[55,60,] 8[65,70,]
      // 11[72,,] 5[80,85,] 10[90,95,]
      assertEquals(r20.getValue(), btr.delete(r20.getKey()));

      rn = nr.readNode(np.get(3), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(3), createLRecordList(r15), false, false, np.get(1), np.get(9)), rn);

      // Delete 10
      // 0[(6),55(7),80(12),]
      // 6[(1),15(3),25(9),50(2)] 7[(4),65(8),72(11),]
      // 12[(5),90(10),,]
      // 1[5,,] 3[15,,] 9[25,28,] 2[50,,] 4[55,60,] 8[65,70,] 11[72,,]
      // 5[80,85,] 10[90,95,]
      assertEquals(r10.getValue(), btr.delete(r10.getKey()));

      rn = nr.readNode(np.get(1), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(1), createLRecordList(r5), false, false, null, np.get(3)), rn);

      // Delete 72
      // 0[(6),55(7),80(12),]
      // 6[(1),15(3),25(9),50(2)] 7[(4),65(11),,] 12[(5),90(10),,]
      // 1[5,,] 3[15,,] 9[25,28,] 2[50,,] 4[55,60,] 11[65,70,]
      // 5[80,85,] 10[90,95,]
      assertEquals(r72.getValue(), btr.delete(r72.getKey()));

      rn = nr.readNode(np.get(4), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(4), createLRecordList(r55, r60), false, false, np.get(2), np.get(11)), rn);
      rn = nr.readNode(np.get(7), kconv.convert(55));
      assertNodesEqual(new BPlusTreeNonLeafNode<K>(np.get(7), kconv.convert(55), np.get(4), createLRecordList(createLRecord(65, np.get(11), kconv)), false, false), rn);
      assertNodeDeleted(nr, np.get(8));
      rn = nr.readNode(np.get(11), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(11), createLRecordList(r65, r70), false, false, np.get(4), np.get(5)), rn);

      // Delete 90
      // 0[(6),55(7),80(12),]
      // 6[(1),15(3),25(9),50(2)] 7[(4),65(11),,] 12[(5),95(10),,]
      // 1[5,,] 3[15,,] 9[25,28,] 2[50,,] 4[55,60,] 11[65,70,]
      // 5[80,85,] 10[95,,]
      assertEquals(r90.getValue(), btr.delete(r90.getKey()));

      rn = nr.readNode(np.get(10), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(10), createLRecordList(r95), false, false, np.get(5), null), rn);
      rn = nr.readNode(np.get(12), kconv.convert(80));
      assertNodesEqual(new BPlusTreeNonLeafNode<K>(np.get(12), kconv.convert(80), np.get(5), createLRecordList(createLRecord(95, np.get(10), kconv)), false, false), rn);

      // Delete 5
      // 0[(6),55(7),80(12),]
      // 6[(1),25(9),50(2),] 7[(4),65(11),,] 12[(5),95(10),,]
      // 1[15,,] 9[25,28,] 2[50,,] 4[55,60,] 11[65,70,] 5[80,85,]
      // 10[95,,]
      assertEquals(r5.getValue(), btr.delete(r5.getKey()));

      rn = nr.readNode(np.get(1), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(1), createLRecordList(r15), false, false, null, np.get(9)), rn);
      assertNodeDeleted(nr, np.get(3));
      rn = nr.readNode(np.get(6), null);
      assertNodesEqual(new BPlusTreeNonLeafNode<K>(np.get(6), null, np.get(1), createLRecordList(createLRecord(25, np.get(9), kconv), createLRecord(50, np.get(2), kconv)), false, false), rn);
      rn = nr.readNode(np.get(9), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(9), createLRecordList(r25, r28), false, false, np.get(1), np.get(2)), rn);

      // Delete 70
      // 0[(6),55(7),80(12),]
      // 6[(1),25(9),50(2),] 7[(4),65(11),,] 12[(5),95(10),,]
      // 1[15,,] 9[25,28,] 2[50,,] 4[55,60,] 11[65,,] 5[80,85,]
      // 10[95,,]
      assertEquals(r70.getValue(), btr.delete(r70.getKey()));

      rn = nr.readNode(np.get(11), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(11), createLRecordList(r65), false, false, np.get(4), np.get(5)), rn);

      // Delete 65
      // 0[(7),80(12),,]
      // 7[(1),25(9),50(2),55(11)] 12[(5),95(10),,]
      // 1[15,,] 9[25,28,] 2[50,,] 11[55,60,] 5[80,85,] 10[95,,]
      assertEquals(r65.getValue(), btr.delete(r65.getKey()));

      rn = nr.readNode(np.get(0), null);
      assertNodesEqual(new BPlusTreeNonLeafNode<K>(np.get(0), null, np.get(7), createLRecordList(createLRecord(80, np.get(12), kconv)), true, false), rn);
      rn = nr.readNode(np.get(2), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(2), createLRecordList(r50), false, false, np.get(9), np.get(11)), rn);
      assertNodeDeleted(nr, np.get(4));
      assertNodeDeleted(nr, np.get(6));
      rn = nr.readNode(np.get(7), null);
      assertNodesEqual(new BPlusTreeNonLeafNode<K>(np.get(7), null, np.get(1), createLRecordList(createLRecord(25, np.get(9), kconv), createLRecord(50, np.get(2), kconv), createLRecord(55, np.get(11), kconv)), false, true), rn);
      rn = nr.readNode(np.get(11), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(11), createLRecordList(r55, r60), false, false, np.get(2), np.get(5)), rn);

      // Delete 95
      // 0[(7),55(12),,]
      // 7[(1),25(9),50(2),] 12[(11),80(10),,]
      // 1[15,,] 9[25,28,] 2[50,,] 11[55,60,] 10[80,85,]
      assertEquals(r95.getValue(), btr.delete(r95.getKey()));

      rn = nr.readNode(np.get(0), null);
      assertNodesEqual(new BPlusTreeNonLeafNode<K>(np.get(0), null, np.get(7), createLRecordList(createLRecord(55, np.get(12), kconv)), true, false), rn);
      assertNodeDeleted(nr, np.get(5));
      rn = nr.readNode(np.get(7), null);
      assertNodesEqual(new BPlusTreeNonLeafNode<K>(np.get(7), null, np.get(1), createLRecordList(createLRecord(25, np.get(9), kconv), createLRecord(50, np.get(2), kconv)), false, false), rn);
      rn = nr.readNode(np.get(10), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(10), createLRecordList(r80, r85), false, false, np.get(11), null), rn);
      rn = nr.readNode(np.get(11), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(11), createLRecordList(r55, r60), false, false, np.get(2), np.get(10)), rn);
      rn = nr.readNode(np.get(12), kconv.convert(55));
      assertNodesEqual(new BPlusTreeNonLeafNode<K>(np.get(12), kconv.convert(55), np.get(11), createLRecordList(createLRecord(80, np.get(10), kconv)), false, false), rn);

      // Delete 25
      // 0[(7),55(12),,]
      // 7[(1),25(9),50(2),] 12[(11),80(10),,]
      // 1[15,,] 9[28,,] 2[50,,] 11[55,60,] 10[80,85,]
      assertEquals(r25.getValue(), btr.delete(r25.getKey()));

      rn = nr.readNode(np.get(9), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(9), createLRecordList(r28), false, false, np.get(1), np.get(2)), rn);

      // Delete 28
      // 0[(7),55(12),,]
      // 7[(9),50(2),,] 12[(11),80(10),,]
      // 9[15,,] 2[50,,] 11[55,60,] 10[80,85,]
      assertEquals(r28.getValue(), btr.delete(r28.getKey()));

      assertNodeDeleted(nr, np.get(1));
      rn = nr.readNode(np.get(7), null);
      assertNodesEqual(new BPlusTreeNonLeafNode<K>(np.get(7), null, np.get(9), createLRecordList(createLRecord(50, np.get(2), kconv)), false, false), rn);
      rn = nr.readNode(np.get(9), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(9), createLRecordList(r15), false, false, null, np.get(2)), rn);

      // Delete 50
      // 0[(2),55(11),80(10),]
      // 2[15,,] 11[55,60,] 10[80,85,]
      assertEquals(r50.getValue(), btr.delete(r50.getKey()));

      rn = nr.readNode(np.get(0), null);
      assertNodesEqual(new BPlusTreeNonLeafNode<K>(np.get(0), null, np.get(2), createLRecordList(createLRecord(55, np.get(11), kconv), createLRecord(80, np.get(10), kconv)), true, false), rn);
      rn = nr.readNode(np.get(2), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(2), createLRecordList(r15), false, false, null, np.get(11)), rn);
      assertNodeDeleted(nr, np.get(7));
      assertNodeDeleted(nr, np.get(9));
      assertNodeDeleted(nr, np.get(12));

      // Delete 60
      // 0[(2),55(11),80(10),]
      // 2[15,,] 11[55,,] 10[80,85,]
      assertEquals(r60.getValue(), btr.delete(r60.getKey()));

      rn = nr.readNode(np.get(11), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(11), createLRecordList(r55), false, false, np.get(2), np.get(10)), rn);

      // Delete 80
      // 0[(2),55(11),85(10),]
      // 2[15,,] 11[55,,] 10[85,,]
      assertEquals(r80.getValue(), btr.delete(r80.getKey()));

      rn = nr.readNode(np.get(0), null);
      assertNodesEqual(new BPlusTreeNonLeafNode<K>(np.get(0), null, np.get(2), createLRecordList(createLRecord(55, np.get(11), kconv), createLRecord(85, np.get(10), kconv)), true, false), rn);
      rn = nr.readNode(np.get(10), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(10), createLRecordList(r85), false, false, np.get(11), null), rn);

      // Delete 55
      // 0[(2),85(10),,]
      // 11[15,,] 10[85,,]
      assertEquals(r55.getValue(), btr.delete(r55.getKey()));

      rn = nr.readNode(np.get(0), null);
      assertNodesEqual(new BPlusTreeNonLeafNode<K>(np.get(0), null, np.get(11), createLRecordList(createLRecord(85, np.get(10), kconv)), true, false), rn);
      assertNodeDeleted(nr, np.get(2));
      rn = nr.readNode(np.get(11), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(11), createLRecordList(r15), false, false, null, np.get(10)), rn);

      // Delete 15
      // 0[85,,]
      assertEquals(r15.getValue(), btr.delete(r15.getKey()));

      rn = nr.readNode(np.get(0), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(0), createLRecordList(r85), true, false, null, null), rn);
      assertNodeDeleted(nr, np.get(10));
      assertNodeDeleted(nr, np.get(11));

      // Delete 85
      // 0[,,]
      assertEquals(r85.getValue(), btr.delete(r85.getKey()));

      rn = nr.readNode(np.get(0), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(0), Collections.EMPTY_LIST, true, false, null, null), rn);
    }
    finally
    {
      btr.close();
    }
  }

  private <K extends Comparable<K>, V> void testInsertWithThreeRecordsPerNodeInternal(Serializer<K> keySerializer, ValueConverter<K> kconv, Serializer<V> valueSerializer, ValueConverter<V> vconv, int cacheSize, int pointerSize, boolean leafNodePointers)
  {
    File tmpFile = FileSupport.createTempFile();
    try
    {
      ReadWritableFile f = new ReadWritableFileAdapter(tmpFile);
      byte[] barr = writeJunk(f);
      NodeRepository<K> nr = new FileBackedNodeRepository<K, V>(f, false, f.getSize(), new NumberOfRecordsNodeSizeStrategy(3), leafNodePointers, keySerializer, valueSerializer, pointerSize, 8192, null, new LogAdapterHolder(new StdOutLogAdapter()));
      if (cacheSize > 0)
      {
        nr = new LruCacheNodeRepository<K, V>(nr, cacheSize);
      }
      testInsertWithTreeRecordsPerNodeInternal(nr, kconv, vconv, getNonLeafNodeSize(keySerializer.getSerializedSize(), pointerSize, 3), getLeafNodeSize(keySerializer.getSerializedSize(), valueSerializer.getSerializedSize(), pointerSize, 3, leafNodePointers));
      assertJunkUntouched(f, barr);
    }
    finally
    {
      assertTrue(tmpFile.delete());
    }
  }

  private void testInsertWithThreeRecordsPerNodeInternal(int cacheSize)
  {
    for (int pointerSize : POINTER_SIZES)
    {
      for (boolean lnp : BOOLEANS)
      {
        testInsertWithThreeRecordsPerNodeInternal(LongNullSerializer.INSTANCE, LONG_CONVERTER, LongSerializer.INSTANCE, LONG_CONVERTER, cacheSize, pointerSize, lnp);
        testInsertWithThreeRecordsPerNodeInternal(LongNullSerializer.INSTANCE, LONG_CONVERTER, IntegerSerializer.INSTANCE, INTEGER_CONVERTER, cacheSize, pointerSize, lnp);
        testInsertWithThreeRecordsPerNodeInternal(LongNullSerializer.INSTANCE, LONG_CONVERTER, ShortSerializer.INSTANCE, SHORT_CONVERTER, cacheSize, pointerSize, lnp);
        testInsertWithThreeRecordsPerNodeInternal(IntegerNullSerializer.INSTANCE, INTEGER_CONVERTER, LongSerializer.INSTANCE, LONG_CONVERTER, cacheSize, pointerSize, lnp);
        testInsertWithThreeRecordsPerNodeInternal(IntegerNullSerializer.INSTANCE, INTEGER_CONVERTER, IntegerSerializer.INSTANCE, INTEGER_CONVERTER, cacheSize, pointerSize, lnp);
        testInsertWithThreeRecordsPerNodeInternal(IntegerNullSerializer.INSTANCE, INTEGER_CONVERTER, ShortSerializer.INSTANCE, SHORT_CONVERTER, cacheSize, pointerSize, lnp);
        testInsertWithThreeRecordsPerNodeInternal(ShortNullSerializer.INSTANCE, SHORT_CONVERTER, LongSerializer.INSTANCE, LONG_CONVERTER, cacheSize, pointerSize, lnp);
        testInsertWithThreeRecordsPerNodeInternal(ShortNullSerializer.INSTANCE, SHORT_CONVERTER, IntegerSerializer.INSTANCE, INTEGER_CONVERTER, cacheSize, pointerSize, lnp);
        testInsertWithThreeRecordsPerNodeInternal(ShortNullSerializer.INSTANCE, SHORT_CONVERTER, ShortSerializer.INSTANCE, SHORT_CONVERTER, cacheSize, pointerSize, lnp);
      }
    }
  }

  @Test
  public void testInsertWithThreeRecordsPerNode()
  {
    testInsertWithThreeRecordsPerNodeInternal(0);
  }

  @Test
  public void testInsertWithThreeRecordsPerNodeAndSmallLruCache()
  {
    testInsertWithThreeRecordsPerNodeInternal(2);
  }

  @Test
  public void testInsertWithThreeRecordsPerNodeAndLargeLruCache()
  {
    testInsertWithThreeRecordsPerNodeInternal(20);
  }

  @SuppressWarnings("unchecked")
  private <K extends Comparable<K>, V> void testInsertWithFourRecordsPerNodeInternal(NodeRepository<K> nr, NodePositions np, ValueConverter<K> kconv, ValueConverter<V> vconv)
  {
    BPlusTree<K, V> btr = new BPlusTree<K, V>(nr, new LogAdapterHolder(new StdOutLogAdapter()));
    try
    {
      // Insert 5, 10, 30, 50, 25, 15, 20, 55, 75, 80, 85, 60, 65, 90, 73,
      // 100
      // 0[(1),25(2),50(3),75(4), ]
      // 1[5,10,15,20] 2[25,30,,] 3[50,55,60,65] 4[75,80,85,90]
      KeyAndValue<K, V> r5 = createLRecord(kconv, vconv, 5, 5);
      btr.insert(r5.getKey(), r5.getValue());
      KeyAndValue<K, V> r10 = createLRecord(kconv, vconv, 10, 10);
      btr.insert(r10.getKey(), r10.getValue());
      KeyAndValue<K, V> r30 = createLRecord(kconv, vconv, 30, 30);
      btr.insert(r30.getKey(), r30.getValue());
      KeyAndValue<K, V> r50 = createLRecord(kconv, vconv, 50, 50);
      btr.insert(r50.getKey(), r50.getValue());
      KeyAndValue<K, V> r25 = createLRecord(kconv, vconv, 25, 25);
      btr.insert(r25.getKey(), r25.getValue());
      KeyAndValue<K, V> r15 = createLRecord(kconv, vconv, 15, 15);
      btr.insert(r15.getKey(), r15.getValue());
      KeyAndValue<K, V> r20 = createLRecord(kconv, vconv, 20, 20);
      btr.insert(r20.getKey(), r20.getValue());
      KeyAndValue<K, V> r55 = createLRecord(kconv, vconv, 55, 55);
      btr.insert(r55.getKey(), r55.getValue());
      KeyAndValue<K, V> r75 = createLRecord(kconv, vconv, 75, 75);
      btr.insert(r75.getKey(), r75.getValue());
      KeyAndValue<K, V> r80 = createLRecord(kconv, vconv, 80, 80);
      btr.insert(r80.getKey(), r80.getValue());
      KeyAndValue<K, V> r85 = createLRecord(kconv, vconv, 85, 85);
      btr.insert(r85.getKey(), r85.getValue());
      KeyAndValue<K, V> r60 = createLRecord(kconv, vconv, 60, 60);
      btr.insert(r60.getKey(), r60.getValue());
      KeyAndValue<K, V> r65 = createLRecord(kconv, vconv, 65, 65);
      btr.insert(r65.getKey(), r65.getValue());
      KeyAndValue<K, V> r90 = createLRecord(kconv, vconv, 90, 90);
      btr.insert(r90.getKey(), r90.getValue());
      np.addLeaf();
      np.addLeaf();
      np.addLeaf();
      np.addLeaf();

      BPlusTreeNode<K, ?> rn = nr.readNode(np.get(0), null);
      assertNodesEqual(new BPlusTreeNonLeafNode<K>(np.get(0), null, np.get(1), createLRecordList(createLRecord(25, np.get(2), kconv), createLRecord(50, np.get(3), kconv), createLRecord(75, np.get(4), kconv)), true, false), rn);
      rn = nr.readNode(np.get(1), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(1), createLRecordList(r5, r10, r15, r20), false, true, null, np.get(2)), rn);
      rn = nr.readNode(np.get(2), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(2), createLRecordList(r25, r30), false, false, np.get(1), np.get(3)), rn);
      rn = nr.readNode(np.get(3), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(3), createLRecordList(r50, r55, r60, r65), false, true, np.get(2), np.get(4)), rn);
      rn = nr.readNode(np.get(4), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(4), createLRecordList(r75, r80, r85, r90), false, true, np.get(3), null), rn);

      // Insert 28
      // 0[(1),25(2),50(3),75(4), ]
      // 1[5,10,15,20] 2[25,28,30,] 3[50,55,60,65] 4[75,80,85,90]
      KeyAndValue<K, V> r28 = createLRecord(kconv, vconv, 28, 28);
      btr.insert(r28.getKey(), r28.getValue());

      rn = nr.readNode(np.get(2), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(2), createLRecordList(r25, r28, r30), false, false, np.get(1), np.get(3)), rn);

      // Insert 70
      // 0[(1),25(2),50(3),60(5),75(4)]
      // 1[5,10,15,20] 2[25,28,30,] 3[50,55,,] *5[60,65,70,]
      // 4[75,80,85,90]
      KeyAndValue<K, V> r70 = createLRecord(kconv, vconv, 70, 70);
      btr.insert(r70.getKey(), r70.getValue());
      np.addLeaf();

      rn = nr.readNode(np.get(0), null);
      assertNodesEqual(new BPlusTreeNonLeafNode<K>(np.get(0), null, np.get(1), createLRecordList(createLRecord(25, np.get(2), kconv), createLRecord(50, np.get(3), kconv), createLRecord(60, np.get(5), kconv), createLRecord(75, np.get(4), kconv)), true, true), rn);
      rn = nr.readNode(np.get(3), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(3), createLRecordList(r50, r55), false, false, np.get(2), np.get(5)), rn);
      rn = nr.readNode(np.get(4), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(4), createLRecordList(r75, r80, r85, r90), false, true, np.get(5), null), rn);
      rn = nr.readNode(np.get(5), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(5), createLRecordList(r60, r65, r70), false, false, np.get(3), np.get(4)), rn);

      // Insert 95
      // *0[(7),60(8),,,]
      // *7[(1),25(2),50(3),,] *8[(5),75(4),85(6),,]
      // 1[5,10,15,20] 2[25,28,30,] 3[50,55,,] 5[60,65,70,] 4[75,80,,]
      // *6[85,90,95,]
      KeyAndValue<K, V> r95 = createLRecord(kconv, vconv, 95, 95);
      btr.insert(r95.getKey(), r95.getValue());
      np.addLeaf();
      np.addNonLeaf();
      np.addNonLeaf();

      rn = nr.readNode(np.get(0), null);
      assertNodesEqual(new BPlusTreeNonLeafNode<K>(np.get(0), null, np.get(7), createLRecordList(createLRecord(60, np.get(8), kconv)), true, false), rn);
      rn = nr.readNode(np.get(4), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(4), createLRecordList(r75, r80), false, false, np.get(5), np.get(6)), rn);
      rn = nr.readNode(np.get(6), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(6), createLRecordList(r85, r90, r95), false, false, np.get(4), null), rn);
      rn = nr.readNode(np.get(7), null);
      assertNodesEqual(new BPlusTreeNonLeafNode<K>(np.get(7), null, np.get(1), createLRecordList(createLRecord(25, np.get(2), kconv), createLRecord(50, np.get(3), kconv)), false, false), rn);
      rn = nr.readNode(np.get(8), kconv.convert(60));
      assertNodesEqual(new BPlusTreeNonLeafNode<K>(np.get(8), kconv.convert(60), np.get(5), createLRecordList(createLRecord(75, np.get(4), kconv), createLRecord(85, np.get(6), kconv)), false, false), rn);

      // Insert 73
      // 0[(7),60(8),,,]
      // 7[(1),25(2),50(3),,] 8[(5),75(4),85(6),,]
      // 1[5,10,15,20] 2[25,28,30,] 3[50,55,,] *5[60,65,70,73] 4[75,80,,]
      // 6[85,90,95,]
      KeyAndValue<K, V> r73 = createLRecord(kconv, vconv, 73, 73);
      btr.insert(r73.getKey(), r73.getValue());
      np.addLeaf();

      rn = nr.readNode(np.get(5), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(5), createLRecordList(r60, r65, r70, r73), false, true, np.get(3), np.get(4)), rn);

      // Insert 100
      // 0[(7),60(8),,,]
      // 7[(1),25(2),50(3),,] 8[(5),75(4),85(6),,]
      // 1[5,10,15,20] 2[25,28,30,] 3[50,55,,] 5[60,65,70,73] 4[75,80,,]
      // *6[85,90,95,100]
      KeyAndValue<K, V> r100 = createLRecord(kconv, vconv, 100, 100);
      btr.insert(r100.getKey(), r100.getValue());
      np.addLeaf();

      rn = nr.readNode(np.get(0), null);
      assertNodesEqual(new BPlusTreeNonLeafNode<K>(np.get(0), null, np.get(7), createLRecordList(createLRecord(60, np.get(8), kconv)), true, false), rn);
      rn = nr.readNode(np.get(1), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(1), createLRecordList(r5, r10, r15, r20), false, true, null, np.get(2)), rn);
      rn = nr.readNode(np.get(2), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(2), createLRecordList(r25, r28, r30), false, false, np.get(1), np.get(3)), rn);
      rn = nr.readNode(np.get(3), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(3), createLRecordList(r50, r55), false, false, np.get(2), np.get(5)), rn);
      rn = nr.readNode(np.get(4), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(4), createLRecordList(r75, r80), false, false, np.get(5), np.get(6)), rn);
      rn = nr.readNode(np.get(5), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(5), createLRecordList(r60, r65, r70, r73), false, true, np.get(3), np.get(4)), rn);
      rn = nr.readNode(np.get(6), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(6), createLRecordList(r85, r90, r95, r100), false, true, np.get(4), null), rn);
      rn = nr.readNode(np.get(7), null);
      assertNodesEqual(new BPlusTreeNonLeafNode<K>(np.get(7), null, np.get(1), createLRecordList(createLRecord(25, np.get(2), kconv), createLRecord(50, np.get(3), kconv)), false, false), rn);
      rn = nr.readNode(np.get(8), kconv.convert(60));
      assertNodesEqual(new BPlusTreeNonLeafNode<K>(np.get(8), kconv.convert(60), np.get(5), createLRecordList(createLRecord(75, np.get(4), kconv), createLRecord(85, np.get(6), kconv)), false, false), rn);

      // Delete 75, 55, 85, 60, 5, 15, 30, 10, 50, 65, 25, 80, 90, 20,
      // 100,
      // 73, 28, 95, 70

      // Delete 75 (will steal leaf node 73)
      // 0[(7),60(8),,,]
      // 7[(1),25(2),50(3),,] 8[(5),73(4),85(6),,]
      // 1[5,10,15,20] 2[25,28,30,] 3[50,55,,] 5[60,65,70,] 4[73,80,,]
      // 6[85,90,95,100,]
      assertEquals(r75.getValue(), btr.delete(r75.getKey()));

      rn = nr.readNode(np.get(4), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(4), createLRecordList(r73, r80), false, false, np.get(5), np.get(6)), rn);
      rn = nr.readNode(np.get(5), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(5), createLRecordList(r60, r65, r70), false, false, np.get(3), np.get(4)), rn);
      rn = nr.readNode(np.get(8), kconv.convert(60));
      assertNodesEqual(new BPlusTreeNonLeafNode<K>(np.get(8), kconv.convert(60), np.get(5), createLRecordList(createLRecord(73, np.get(4), kconv), createLRecord(85, np.get(6), kconv)), false, false), rn);

      // Delete 55
      // 0[(1),25(3),60(5),73(4),85(6)]
      // 1[5,10,15,20] 3[25,28,30,50] 5[60,65,70,] 4[73,80,,]
      // 6[85,90,95,100,]
      assertEquals(r55.getValue(), btr.delete(r55.getKey()));

      rn = nr.readNode(np.get(0), null);
      assertNodesEqual(new BPlusTreeNonLeafNode<K>(np.get(0), null, np.get(1), createLRecordList(createLRecord(25, np.get(3), kconv), createLRecord(60, np.get(5), kconv), createLRecord(73, np.get(4), kconv), createLRecord(85, np.get(6), kconv)), true, true), rn);
      assertNodeDeleted(nr, np.get(2));
      rn = nr.readNode(np.get(3), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(3), createLRecordList(r25, r28, r30, r50), false, true, np.get(1), np.get(5)), rn);
      assertNodeDeleted(nr, np.get(7));
      assertNodeDeleted(nr, np.get(8));

      // Delete 85
      // 0[(1),25(3),60(5),73(4),90(6)]
      // 1[5,10,15,20] 3[25,28,30,50] 5[60,65,70,] 4[73,80,,]
      // 6[90,95,100,,]
      assertEquals(r85.getValue(), btr.delete(r85.getKey()));

      rn = nr.readNode(np.get(0), null);
      assertNodesEqual(new BPlusTreeNonLeafNode<K>(np.get(0), null, np.get(1), createLRecordList(createLRecord(25, np.get(3), kconv), createLRecord(60, np.get(5), kconv), createLRecord(73, np.get(4), kconv), createLRecord(90, np.get(6), kconv)), true, true), rn);
      rn = nr.readNode(np.get(6), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(6), createLRecordList(r90, r95, r100), false, false, np.get(4), null), rn);

      // Delete 60
      // 0[(1),25(3),65(5),73(4),90(6)]
      // 1[5,10,15,20] 3[25,28,30,50] 5[65,70,,] 4[73,80,,] 6[90,95,100,,]
      assertEquals(r60.getValue(), btr.delete(r60.getKey()));

      rn = nr.readNode(np.get(0), null);
      assertNodesEqual(new BPlusTreeNonLeafNode<K>(np.get(0), null, np.get(1), createLRecordList(createLRecord(25, np.get(3), kconv), createLRecord(65, np.get(5), kconv), createLRecord(73, np.get(4), kconv), createLRecord(90, np.get(6), kconv)), true, true), rn);
      rn = nr.readNode(np.get(5), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(5), createLRecordList(r65, r70), false, false, np.get(3), np.get(4)), rn);

      // Delete 5
      // 0[(1),25(3),65(5),73(4),90(6)]
      // 1[10,15,20,] 3[25,28,30,50] 5[65,70,,] 4[73,80,,] 6[90,95,100,,]
      assertEquals(r5.getValue(), btr.delete(r5.getKey()));

      rn = nr.readNode(np.get(1), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(1), createLRecordList(r10, r15, r20), false, false, null, np.get(3)), rn);

      // Delete 15
      // 0[(1),25(3),65(5),73(4),90(6)]
      // 1[10,20,,] 3[25,28,30,50] 5[65,70,,] 4[73,80,,] 6[90,95,100,,]
      assertEquals(r15.getValue(), btr.delete(r15.getKey()));

      rn = nr.readNode(np.get(1), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(1), createLRecordList(r10, r20), false, false, null, np.get(3)), rn);

      // Delete 30
      // 0[(1),25(3),65(5),73(4),90(6)]
      // 1[10,20,,] 3[25,28,50,] 5[65,70,,] 4[73,80,,] 6[90,95,100,,]
      assertEquals(r30.getValue(), btr.delete(r30.getKey()));

      rn = nr.readNode(np.get(3), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(3), createLRecordList(r25, r28, r50), false, false, np.get(1), np.get(5)), rn);

      // Delete 10
      // 0[(1),65(5),73(4),90(6),]
      // 1[20,25,28,50] 5[65,70,,] 4[73,80,,] 6[90,95,100,,]
      assertEquals(r10.getValue(), btr.delete(r10.getKey()));

      rn = nr.readNode(np.get(0), null);
      assertNodesEqual(new BPlusTreeNonLeafNode<K>(np.get(0), null, np.get(1), createLRecordList(createLRecord(65, np.get(5), kconv), createLRecord(73, np.get(4), kconv), createLRecord(90, np.get(6), kconv)), true, false), rn);
      rn = nr.readNode(np.get(1), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(1), createLRecordList(r20, r25, r28, r50), false, true, null, np.get(5)), rn);
      assertNodeDeleted(nr, np.get(3));
      rn = nr.readNode(np.get(5), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(5), createLRecordList(r65, r70), false, false, np.get(1), np.get(4)), rn);

      // Delete 50
      // 0[(1),65(5),73(4),90(6),]
      // 1[20,25,28,] 5[65,70,,] 4[73,80,,] 6[90,95,100,,]
      assertEquals(r50.getValue(), btr.delete(r50.getKey()));

      rn = nr.readNode(np.get(1), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(1), createLRecordList(r20, r25, r28), false, false, null, np.get(5)), rn);

      // Delete 65
      // 0[(5),73(4),90(6),]
      // 5[20,25,28,70] 4[73,80,,] 6[90,95,100,,]
      assertEquals(r65.getValue(), btr.delete(r65.getKey()));

      rn = nr.readNode(np.get(0), null);
      assertNodesEqual(new BPlusTreeNonLeafNode<K>(np.get(0), null, np.get(5), createLRecordList(createLRecord(73, np.get(4), kconv), createLRecord(90, np.get(6), kconv)), true, false), rn);
      assertNodeDeleted(nr, np.get(1));
      rn = nr.readNode(np.get(5), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(5), createLRecordList(r20, r25, r28, r70), false, true, null, np.get(4)), rn);

      // Delete 25
      // 0[(5),73(4),90(6),]
      // 5[20,28,70,] 4[73,80,,] 6[90,95,100,,]
      assertEquals(r25.getValue(), btr.delete(r25.getKey()));

      rn = nr.readNode(np.get(5), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(5), createLRecordList(r20, r28, r70), false, false, null, np.get(4)), rn);

      // Delete 80
      // 0[(4),90(6),,]
      // 4[20,28,70,73] 6[90,95,100,,]
      assertEquals(r80.getValue(), btr.delete(r80.getKey()));

      rn = nr.readNode(np.get(0), null);
      assertNodesEqual(new BPlusTreeNonLeafNode<K>(np.get(0), null, np.get(4), createLRecordList(createLRecord(90, np.get(6), kconv)), true, false), rn);
      rn = nr.readNode(np.get(4), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(4), createLRecordList(r20, r28, r70, r73), false, true, null, np.get(6)), rn);
      assertNodeDeleted(nr, np.get(5));

      // Delete 90
      // 0[(4),95(6),,]
      // 4[20,28,70,73] 6[95,100,,]
      assertEquals(r90.getValue(), btr.delete(r90.getKey()));

      rn = nr.readNode(np.get(0), null);
      assertNodesEqual(new BPlusTreeNonLeafNode<K>(np.get(0), null, np.get(4), createLRecordList(createLRecord(95, np.get(6), kconv)), true, false), rn);
      rn = nr.readNode(np.get(6), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(6), createLRecordList(r95, r100), false, false, np.get(4), null), rn);

      // Delete 20
      // 0[(4),95(6),,]
      // 4[28,70,73,] 6[95,100,,]
      assertEquals(r20.getValue(), btr.delete(r20.getKey()));

      rn = nr.readNode(np.get(4), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(4), createLRecordList(r28, r70, r73), false, false, null, np.get(6)), rn);

      // Delete 100
      // 0[28,70,73,95]
      assertEquals(r100.getValue(), btr.delete(r100.getKey()));

      rn = nr.readNode(np.get(0), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(0), createLRecordList(r28, r70, r73, r95), true, true, null, null), rn);
      assertNodeDeleted(nr, np.get(4));
      assertNodeDeleted(nr, np.get(6));

      // Delete 73
      // 0[28,70,95,]
      assertEquals(r73.getValue(), btr.delete(r73.getKey()));

      rn = nr.readNode(np.get(0), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(0), createLRecordList(r28, r70, r95), true, false, null, null), rn);

      // Delete 28
      // 0[70,95,,]
      assertEquals(r28.getValue(), btr.delete(r28.getKey()));

      rn = nr.readNode(np.get(0), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(0), createLRecordList(r70, r95), true, false, null, null), rn);

      // Delete 95
      // 0[70,,,]
      assertEquals(r95.getValue(), btr.delete(r95.getKey()));

      rn = nr.readNode(np.get(0), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(0), createLRecordList(r70), true, false, null, null), rn);

      // Delete 70
      // 0[,,,]
      assertEquals(r70.getValue(), btr.delete(r70.getKey()));

      rn = nr.readNode(np.get(0), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(0), Collections.EMPTY_LIST, true, false, null, null), rn);
    }
    finally
    {
      btr.close();
    }
  }

  @Test
  public void testInsertWithFourRecordsPerNodeAndASlightlyTooBigNodeSize()
  {
    File tmpFile = FileSupport.createTempFile();
    try
    {
      ReadWritableFile f = new ReadWritableFileAdapter(tmpFile);
      byte[] barr = writeJunk(f);
      int nodeSize = new NumberOfRecordsNodeSizeStrategy(4).getNodeSize(9, 2 * LongSerializer.DATA_SIZE, 0) + 3;
      NodeRepository<Long> nr = new FileBackedNodeRepository<Long, Long>(f, false, f.getSize(), new FixedSizeNodeSizeStrategy(nodeSize), false, LongNullSerializer.INSTANCE, LongSerializer.INSTANCE, 8, 8192, null, new LogAdapterHolder(new StdOutLogAdapter()));
      testInsertWithFourRecordsPerNodeInternal(nr, new NodePositions(nodeSize, nodeSize, nr.getPositionOfRootNode()), LONG_CONVERTER, LONG_CONVERTER);
      assertJunkUntouched(f, barr);
    }
    finally
    {
      assertTrue(tmpFile.delete());
    }
  }

  private <K extends Comparable<K>, V> void testInsertWithFourRecordsPerNodeInternal(Serializer<K> keySerializer, ValueConverter<K> kconv, Serializer<V> valueSerializer, ValueConverter<V> vconv, int cacheSize, int pointerSize, boolean leafNodePointers)
  {
    File tmpFile = FileSupport.createTempFile();
    try
    {
      ReadWritableFile f = new ReadWritableFileAdapter(tmpFile);
      byte[] barr = writeJunk(f);
      NodeRepository<K> nr = new FileBackedNodeRepository<K, V>(f, false, f.getSize(), new NumberOfRecordsNodeSizeStrategy(4), leafNodePointers, keySerializer, valueSerializer, pointerSize, 8192, null, new LogAdapterHolder(new StdOutLogAdapter()));
      if (cacheSize > 0)
      {
        nr = new LruCacheNodeRepository<K, V>(nr, cacheSize);
      }
      testInsertWithFourRecordsPerNodeInternal(nr, new NodePositions(getNonLeafNodeSize(keySerializer.getSerializedSize(), pointerSize, 4), getLeafNodeSize(keySerializer.getSerializedSize(), valueSerializer.getSerializedSize(), pointerSize, 4, leafNodePointers), nr.getPositionOfRootNode()), kconv, vconv);
      assertJunkUntouched(f, barr);
    }
    finally
    {
      assertTrue(tmpFile.delete());
    }
  }

  private void testInsertWithFourRecordsPerNodeInternal(int cacheSize)
  {
    for (int pointerSize : POINTER_SIZES)
    {
      for (boolean lnp : BOOLEANS)
      {
        testInsertWithFourRecordsPerNodeInternal(LongNullSerializer.INSTANCE, LONG_CONVERTER, LongSerializer.INSTANCE, LONG_CONVERTER, cacheSize, pointerSize, lnp);
        testInsertWithFourRecordsPerNodeInternal(LongNullSerializer.INSTANCE, LONG_CONVERTER, IntegerSerializer.INSTANCE, INTEGER_CONVERTER, cacheSize, pointerSize, lnp);
        testInsertWithFourRecordsPerNodeInternal(LongNullSerializer.INSTANCE, LONG_CONVERTER, ShortSerializer.INSTANCE, SHORT_CONVERTER, cacheSize, pointerSize, lnp);
        testInsertWithFourRecordsPerNodeInternal(IntegerNullSerializer.INSTANCE, INTEGER_CONVERTER, LongSerializer.INSTANCE, LONG_CONVERTER, cacheSize, pointerSize, lnp);
        testInsertWithFourRecordsPerNodeInternal(IntegerNullSerializer.INSTANCE, INTEGER_CONVERTER, IntegerSerializer.INSTANCE, INTEGER_CONVERTER, cacheSize, pointerSize, lnp);
        testInsertWithFourRecordsPerNodeInternal(IntegerNullSerializer.INSTANCE, INTEGER_CONVERTER, ShortSerializer.INSTANCE, SHORT_CONVERTER, cacheSize, pointerSize, lnp);
        testInsertWithFourRecordsPerNodeInternal(ShortNullSerializer.INSTANCE, SHORT_CONVERTER, LongSerializer.INSTANCE, LONG_CONVERTER, cacheSize, pointerSize, lnp);
        testInsertWithFourRecordsPerNodeInternal(ShortNullSerializer.INSTANCE, SHORT_CONVERTER, IntegerSerializer.INSTANCE, INTEGER_CONVERTER, cacheSize, pointerSize, lnp);
        testInsertWithFourRecordsPerNodeInternal(ShortNullSerializer.INSTANCE, SHORT_CONVERTER, ShortSerializer.INSTANCE, SHORT_CONVERTER, cacheSize, pointerSize, lnp);
      }
    }
  }

  @Test
  public void testInsertWithFourRecordsPerNode()
  {
    testInsertWithFourRecordsPerNodeInternal(0);
  }

  @Test
  public void testInsertWithFourRecordsPerNodeAndSmallLruCache()
  {
    testInsertWithFourRecordsPerNodeInternal(2);
  }

  @Test
  public void testInsertWithFourRecordsPerNodeAndLargeLruCache()
  {
    testInsertWithFourRecordsPerNodeInternal(20);
  }

  @Test
  public void testInsertDuplicateKey()
  {
    File tmpFile = FileSupport.createTempFile();
    try
    {
      ReadWritableFile f = new ReadWritableFileAdapter(tmpFile);
      byte[] barr = writeJunk(f);
      LogAdapterHolder lah = new LogAdapterHolder(new StdOutLogAdapter());
      NodeRepository<Long> nr = new FileBackedNodeRepository<Long, Long>(f, false, f.getSize(), new NumberOfRecordsNodeSizeStrategy(2), false, LongNullSerializer.INSTANCE, LongSerializer.INSTANCE, 8, 8192, null, lah);
      BPlusTree<Long, Long> btr = new BPlusTree<Long, Long>(nr, lah);
      try
      {
        btr.insert(2L, 3L);
        try
        {
          btr.insert(2L, 4L);
          fail();
        }
        catch (KeyExistsException e)
        {
          // ok
        }
        assertEquals(3L, btr.find(2L).longValue());
        assertEquals(2L, btr.findRecord(2L, SearchMode.EXACT_MATCH).getKey().longValue());
      }
      finally
      {
        btr.close();
      }
      assertJunkUntouched(f, barr);
    }
    finally
    {
      assertTrue(tmpFile.delete());
    }
  }

  @Test
  public void testInsertOrReplace()
  {
    File tmpFile = FileSupport.createTempFile();
    try
    {
      ReadWritableFile f = new ReadWritableFileAdapter(tmpFile);
      byte[] barr = writeJunk(f);
      LogAdapterHolder lah = new LogAdapterHolder(new StdOutLogAdapter());
      NodeRepository<Long> nr = new FileBackedNodeRepository<Long, Long>(f, false, f.getSize(), new NumberOfRecordsNodeSizeStrategy(2), false, LongNullSerializer.INSTANCE, LongSerializer.INSTANCE, 8, 8192, null, lah);
      BPlusTree<Long, Long> btr = new BPlusTree<Long, Long>(nr, lah);
      try
      {
        assertFalse(btr.insertOrReplace(2L, 3L));
        assertTrue(btr.insertOrReplace(2L, 4L));
        assertEquals(4L, btr.find(2L).longValue());
        assertEquals(2L, btr.findRecord(2L, SearchMode.EXACT_MATCH).getKey().longValue());
      }
      finally
      {
        btr.close();
      }
      assertJunkUntouched(f, barr);
    }
    finally
    {
      assertTrue(tmpFile.delete());
    }
  }

  private <K extends Comparable<K>, V> void insertTestRecords(BPlusTree<K, V> btr, List<KeyAndValue<K, V>> records)
  {
    for (KeyAndValue<K, V> record : records)
    {
      btr.insert(record.getKey(), record.getValue());
    }
  }

  private static final int[] TEST_KEYS = new int[] { 5, 10, 30, 50, 25, 15, 20, 55, 75, 80, 85, 60, 65, 90, 73, 100 };
  private static final int[] TEST_KEYS_SORTED;
  static
  {
    TEST_KEYS_SORTED = new int[TEST_KEYS.length];
    System.arraycopy(TEST_KEYS, 0, TEST_KEYS_SORTED, 0, TEST_KEYS.length);
    Arrays.sort(TEST_KEYS_SORTED);
  }

  /**
   * Insert 5, 10, 30, 50, 25, 15, 20, 55, 75, 80, 85, 60, 65, 90, 73, 100
   * @param btr
   */
  private <K extends Comparable<K>, V> void insertTestRecords(BPlusTree<K, V> btr, ValueConverter<K> kconv, ValueConverter<V> vconv)
  {
    btr.insert(kconv.convert(5), vconv.convert(5));
    btr.insert(kconv.convert(10), vconv.convert(10));
    btr.insert(kconv.convert(30), vconv.convert(30));
    btr.insert(kconv.convert(50), vconv.convert(50));
    btr.insert(kconv.convert(25), vconv.convert(25));
    btr.insert(kconv.convert(15), vconv.convert(15));
    btr.insert(kconv.convert(20), vconv.convert(20));
    btr.insert(kconv.convert(55), vconv.convert(55));
    btr.insert(kconv.convert(75), vconv.convert(75));
    btr.insert(kconv.convert(80), vconv.convert(80));
    btr.insert(kconv.convert(85), vconv.convert(85));
    btr.insert(kconv.convert(60), vconv.convert(60));
    btr.insert(kconv.convert(65), vconv.convert(65));
    btr.insert(kconv.convert(90), vconv.convert(90));
    btr.insert(kconv.convert(73), vconv.convert(73));
    btr.insert(kconv.convert(100), vconv.convert(100));
  }

  /**
   * @param expected Must be sorted.
   */
  private <K extends Comparable<K>> void verifyNextAndPreviousRecords(BPlusTree<K, ?> btr, int[] expected)
  {
    if (btr.isIteratorSupported())
    {
      // Forward
      KeyAndValue<K, ?> rec = btr.getFirstRecord();
      KeyAndValue<K, ?> prevRec = null;
      assertNull(btr.getPreviousRecord(rec.getKey()));
      for (int i = 0; i < expected.length; i++)
      {
        assertEquals(expected[i], ((Number) rec.getKey()).intValue());
        if (i > 0)
        {
          assertEquals(prevRec.getKey(), btr.getPreviousRecord(rec.getKey()).getKey());
          assertTrue(((Number) prevRec.getKey()).intValue() < ((Number) rec.getKey()).intValue());
        }
        prevRec = rec;
        rec = btr.getNextRecord(rec.getKey());
        if (i < (expected.length - 1))
        {
          assertNotNull(rec);
        }
      }
      assertNull(rec);

      // Backwards
      rec = prevRec;
      prevRec = null;
      for (int i = expected.length - 1; i >= 0; i--)
      {
        assertEquals(expected[i], ((Number) rec.getKey()).intValue());
        if (i < (expected.length - 1))
        {
          assertEquals(prevRec.getKey(), btr.getNextRecord(rec.getKey()).getKey());
        }
        prevRec = rec;
        rec = btr.getPreviousRecord(rec.getKey());
        if (i > 0)
        {
          assertNotNull(rec);
        }
      }
      assertNull(rec);
    }
  }

  /**
   * @param l (A copy) is sorted by this method.
   */
  private <K extends Comparable<K>> void verifyNextAndPreviousRecords(BPlusTree<K, ?> btr, List<? extends KeyAndValue<K, ?>> l)
  {
    if (btr.isIteratorSupported())
    {
      int[] expected = new int[l.size()];
      for (int i = 0; i < l.size(); i++)
      {
        expected[i] = ((Number) l.get(i).getKey()).intValue();
      }
      Arrays.sort(expected);
      verifyNextAndPreviousRecords(btr, expected);
    }
  }

  private <K extends Comparable<K>, V> List<KeyAndValue<K, V>> createInterval(int first, int last, ValueConverter<K> kconv, ValueConverter<V> vconv)
  {
    List<KeyAndValue<K, V>> res = new ArrayList<KeyAndValue<K, V>>(last - first + 1);
    for (int i = first; i <= last; i++)
    {
      res.add(createLRecord(kconv, vconv, i, i));
    }
    return res;
  }

  private <K extends Comparable<K>, V> List<KeyAndValue<K, V>> mergeIntervals(List<KeyAndValue<K, V>>... intervals)
  {
    List<KeyAndValue<K, V>> res = new ArrayList<KeyAndValue<K, V>>();
    for (List<KeyAndValue<K, V>> l : intervals)
    {
      res.addAll(l);
    }
    return res;
  }

  private <V> void testFindKeyInternal(NodeRepository<Character> nr, ValueConverter<Character> kconv, ValueConverter<V> vconv)
  {
    BPlusTree<Character, V> btr = new BPlusTree<Character, V>(nr, new LogAdapterHolder(new StdOutLogAdapter()));
    try
    {
      insertTestRecords(btr, kconv, vconv);

      assertNull(btr.findRecord(kconv.convert(4), SearchMode.EXACT_MATCH));
      assertEquals(5, btr.findRecord(kconv.convert(4), SearchMode.CLOSEST_ABOVE).getKey().charValue());
      assertNull(btr.findRecord(kconv.convert(4), SearchMode.CLOSEST_BELOW));
      assertEquals(5, btr.findRecord(kconv.convert(4), SearchMode.CLOSEST_MATCH).getKey().charValue());

      for (int i = 0; i < TEST_KEYS_SORTED.length - 1; i++)
      {
        int thisKeyValue = TEST_KEYS_SORTED[i];
        int nextKeyValue = TEST_KEYS_SORTED[i + 1];

        assertEquals(thisKeyValue, btr.findRecord(kconv.convert(TEST_KEYS_SORTED[i]), SearchMode.CLOSEST_BELOW).getKey().charValue());
        assertEquals(thisKeyValue, btr.findRecord(kconv.convert(TEST_KEYS_SORTED[i]), SearchMode.CLOSEST_ABOVE).getKey().charValue());
        assertEquals(thisKeyValue, btr.findRecord(kconv.convert(TEST_KEYS_SORTED[i]), SearchMode.CLOSEST_MATCH).getKey().charValue());

        for (int j = TEST_KEYS_SORTED[i] + 1; j < TEST_KEYS_SORTED[i + 1]; j++)
        {
          assertEquals(thisKeyValue, btr.findRecord(kconv.convert(j), SearchMode.CLOSEST_BELOW).getKey().charValue());
          assertEquals(nextKeyValue, btr.findRecord(kconv.convert(j), SearchMode.CLOSEST_ABOVE).getKey().charValue());
          if ((j - thisKeyValue) < (nextKeyValue - j))
          {
            assertEquals(thisKeyValue, btr.findRecord(kconv.convert(j), SearchMode.CLOSEST_MATCH).getKey().charValue());
          }
          else if ((nextKeyValue - j) < (j - thisKeyValue))
          {
            assertEquals(nextKeyValue, btr.findRecord(kconv.convert(j), SearchMode.CLOSEST_MATCH).getKey().charValue());
          }
          else
          {
            int val = btr.findRecord(kconv.convert(j), SearchMode.CLOSEST_MATCH).getKey();
            assertTrue(val == thisKeyValue || val == nextKeyValue);
          }
        }
      }

      assertNull(btr.findRecord(kconv.convert(101), SearchMode.EXACT_MATCH));
      assertNull(btr.findRecord(kconv.convert(101), SearchMode.CLOSEST_ABOVE));
      assertEquals(100, btr.findRecord(kconv.convert(101), SearchMode.CLOSEST_BELOW).getKey().charValue());
      assertEquals(100, btr.findRecord(kconv.convert(101), SearchMode.CLOSEST_MATCH).getKey().charValue());
    }
    finally
    {
      btr.close();
    }
  }

  private <V> void testFindKeyInternal(Serializer<Character> keySerializer, ValueConverter<Character> kconv, Serializer<V> valueSerializer, ValueConverter<V> vconv, int cacheSize, int pointerSize, boolean leafNodePointers)
  {
    File tmpFile = FileSupport.createTempFile();
    try
    {
      ReadWritableFile f = new ReadWritableFileAdapter(tmpFile);
      byte[] barr = writeJunk(f);
      NodeRepository<Character> nr = new FileBackedNodeRepository<Character, V>(f, false, f.getSize(), new NumberOfRecordsNodeSizeStrategy(4), leafNodePointers, keySerializer, valueSerializer, pointerSize, 8192, null, new LogAdapterHolder(new StdOutLogAdapter()));
      if (cacheSize > 0)
      {
        nr = new LruCacheNodeRepository<Character, V>(nr, cacheSize);
      }
      testFindKeyInternal(nr, kconv, vconv);
      assertJunkUntouched(f, barr);
    }
    finally
    {
      assertTrue(tmpFile.delete());
    }
  }

  private void testFindKeyInternal(int cacheSize)
  {
    for (int pointerSize : POINTER_SIZES)
    {
      // Must use Character since the compareTo methods of the Number
      // classes does not give distances as required.
      testFindKeyInternal(CharacterNullSerializer.INSTANCE, CHARACTER_CONVERTER, LongSerializer.INSTANCE, LONG_CONVERTER, cacheSize, pointerSize, true);
      testFindKeyInternal(CharacterNullSerializer.INSTANCE, CHARACTER_CONVERTER, IntegerSerializer.INSTANCE, INTEGER_CONVERTER, cacheSize, pointerSize, true);
      testFindKeyInternal(CharacterNullSerializer.INSTANCE, CHARACTER_CONVERTER, ShortSerializer.INSTANCE, SHORT_CONVERTER, cacheSize, pointerSize, true);
    }
  }

  @Test
  public void testFindKey()
  {
    testFindKeyInternal(0);
  }

  @Test
  public void testFindKeyWithLruCache()
  {
    testFindKeyInternal(10);
  }

  private <K extends Comparable<K>, V> void testReplaceInNodeInternal(NodeRepository<K> nr, ValueConverter<K> kconv, ValueConverter<V> vconv)
  {
    BPlusTree<K, V> btr = new BPlusTree<K, V>(nr, new LogAdapterHolder(new StdOutLogAdapter()));
    try
    {
      insertTestRecords(btr, kconv, vconv);

      btr.replace(kconv.convert(85), vconv.convert(10));

      assertEquals(vconv.convert(10), btr.find(kconv.convert(85)));
      assertEquals(kconv.convert(85), btr.findRecord(kconv.convert(85), SearchMode.EXACT_MATCH).getKey());

      verifyNextAndPreviousRecords(btr, TEST_KEYS_SORTED);
    }
    finally
    {
      btr.close();
    }
  }

  private <K extends Comparable<K>, V> void testReplaceInNodeInternal(Serializer<K> keySerializer, ValueConverter<K> kconv, Serializer<V> valueSerializer, ValueConverter<V> vconv, int cacheSize, int pointerSize, boolean leafNodePointers)
  {
    File tmpFile = FileSupport.createTempFile();
    try
    {
      ReadWritableFile f = new ReadWritableFileAdapter(tmpFile);
      byte[] barr = writeJunk(f);
      NodeRepository<K> nr = new FileBackedNodeRepository<K, V>(f, false, f.getSize(), new NumberOfRecordsNodeSizeStrategy(4), leafNodePointers, keySerializer, valueSerializer, pointerSize, 8192, null, new LogAdapterHolder(new StdOutLogAdapter()));
      if (cacheSize > 0)
      {
        nr = new LruCacheNodeRepository<K, V>(nr, cacheSize);
      }
      testReplaceInNodeInternal(nr, kconv, vconv);
      assertJunkUntouched(f, barr);
    }
    finally
    {
      assertTrue(tmpFile.delete());
    }
  }

  @Test
  public void testReplaceInNodeWithTooBigNodeSize()
  {
    File tmpFile = FileSupport.createTempFile();
    try
    {
      ReadWritableFile f = new ReadWritableFileAdapter(tmpFile);
      byte[] barr = writeJunk(f);
      NodeRepository<Long> nr = new FileBackedNodeRepository<Long, Long>(f, false, f.getSize(), new FixedSizeNodeSizeStrategy(new NumberOfRecordsNodeSizeStrategy(4).getNodeSize(9, 16, 0) + 3), false, LongNullSerializer.INSTANCE, LongSerializer.INSTANCE, 8, 8192, null, new LogAdapterHolder(new StdOutLogAdapter()));
      testReplaceInNodeInternal(nr, LONG_CONVERTER, LONG_CONVERTER);
      assertJunkUntouched(f, barr);
    }
    finally
    {
      assertTrue(tmpFile.delete());
    }
  }

  private void testReplaceInNodeInternal(int cacheSize)
  {
    for (int pointerSize : POINTER_SIZES)
    {
      for (boolean lnp : BOOLEANS)
      {
        testReplaceInNodeInternal(LongNullSerializer.INSTANCE, LONG_CONVERTER, LongSerializer.INSTANCE, LONG_CONVERTER, cacheSize, pointerSize, lnp);
        testReplaceInNodeInternal(LongNullSerializer.INSTANCE, LONG_CONVERTER, IntegerSerializer.INSTANCE, INTEGER_CONVERTER, cacheSize, pointerSize, lnp);
        testReplaceInNodeInternal(LongNullSerializer.INSTANCE, LONG_CONVERTER, ShortSerializer.INSTANCE, SHORT_CONVERTER, cacheSize, pointerSize, lnp);
        testReplaceInNodeInternal(IntegerNullSerializer.INSTANCE, INTEGER_CONVERTER, LongSerializer.INSTANCE, LONG_CONVERTER, cacheSize, pointerSize, lnp);
        testReplaceInNodeInternal(IntegerNullSerializer.INSTANCE, INTEGER_CONVERTER, IntegerSerializer.INSTANCE, INTEGER_CONVERTER, cacheSize, pointerSize, lnp);
        testReplaceInNodeInternal(IntegerNullSerializer.INSTANCE, INTEGER_CONVERTER, ShortSerializer.INSTANCE, SHORT_CONVERTER, cacheSize, pointerSize, lnp);
        testReplaceInNodeInternal(ShortNullSerializer.INSTANCE, SHORT_CONVERTER, LongSerializer.INSTANCE, LONG_CONVERTER, cacheSize, pointerSize, lnp);
        testReplaceInNodeInternal(ShortNullSerializer.INSTANCE, SHORT_CONVERTER, IntegerSerializer.INSTANCE, INTEGER_CONVERTER, cacheSize, pointerSize, lnp);
        testReplaceInNodeInternal(ShortNullSerializer.INSTANCE, SHORT_CONVERTER, ShortSerializer.INSTANCE, SHORT_CONVERTER, cacheSize, pointerSize, lnp);
      }
    }
  }

  @Test
  public void testReplaceInNode()
  {
    testReplaceInNodeInternal(0);
  }

  @Test
  public void testReplaceInNodeWithLruCache()
  {
    testReplaceInNodeInternal(10);
  }

  @Test
  public void testReplaceWhenKeyNotFound()
  {
    File tmpFile = FileSupport.createTempFile();
    try
    {
      ReadWritableFile f = new ReadWritableFileAdapter(tmpFile);
      byte[] barr = writeJunk(f);
      LogAdapterHolder lah = new LogAdapterHolder(new StdOutLogAdapter());
      NodeRepository<Long> nr = new FileBackedNodeRepository<Long, Long>(f, false, f.getSize(), new NumberOfRecordsNodeSizeStrategy(2), false, LongNullSerializer.INSTANCE, LongSerializer.INSTANCE, 8, 8192, null, lah);
      BPlusTree<Long, Long> btr = new BPlusTree<Long, Long>(nr, lah);
      try
      {
        btr.replace(2L, 2L);
        fail();
      }
      catch (KeyNotFoundException e)
      {
        // ok
      }
      finally
      {
        btr.close();
      }
      assertJunkUntouched(f, barr);
    }
    finally
    {
      assertTrue(tmpFile.delete());
    }
  }

  @SuppressWarnings("unchecked")
  private <K extends Comparable<K>, V> void testStealThreeLeafRecordsFromNodeToTheRightInternal(NodeRepository<K> nr, NodePositions np, ValueConverter<K> kconv, ValueConverter<V> vconv)
  {
    BPlusTree<K, V> btr = new BPlusTree<K, V>(nr, new LogAdapterHolder(new StdOutLogAdapter()));
    try
    {
      // 0[(1), 13(2)]
      // 1[1..6] 2[13..22]
      List<KeyAndValue<K, V>> records = mergeIntervals(createInterval(1, 6, kconv, vconv), createInterval(13, 22, kconv, vconv));
      insertTestRecords(btr, records);
      np.addLeaf();
      np.addLeaf();

      BPlusTreeNode<K, ?> rn = nr.readNode(np.get(0), null);
      assertNodesEqual(new BPlusTreeNonLeafNode<K>(np.get(0), null, np.get(1), createLRecordList(createLRecord(13, np.get(2), kconv)), true, false), rn);
      rn = nr.readNode(np.get(1), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(1), createInterval(1, 6, kconv, vconv), false, false, null, np.get(2)), rn);
      rn = nr.readNode(np.get(2), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(2), createInterval(13, 22, kconv, vconv), false, false, np.get(1), null), rn);
      verifyNextAndPreviousRecords(btr, records);

      // Delete 1
      // 0[(1), 16(2)]
      // 1[2..6,13..15] 2[16..22]
      assertEquals(vconv.convert(1), btr.delete(kconv.convert(1)));

      rn = nr.readNode(np.get(0), null);
      assertNodesEqual(new BPlusTreeNonLeafNode<K>(np.get(0), null, np.get(1), createLRecordList(createLRecord(16, np.get(2), kconv)), true, false), rn);
      rn = nr.readNode(np.get(1), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(1), createLRecordList(createLRecord(kconv, vconv, 2, 2), createLRecord(kconv, vconv, 3, 3), createLRecord(kconv, vconv, 4, 4), createLRecord(kconv, vconv, 5, 5), createLRecord(kconv, vconv, 6, 6), createLRecord(kconv, vconv, 13, 13), createLRecord(kconv, vconv, 14, 14), createLRecord(kconv, vconv, 15, 15)), false, false, null, np.get(2)), rn);
      rn = nr.readNode(np.get(2), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(2), createInterval(16, 22, kconv, vconv), false, false, np.get(1), null), rn);
      assertTrue(records.remove(new KeyAndValue<K, V>(kconv.convert(1), vconv.convert(1))));
      verifyNextAndPreviousRecords(btr, records);
    }
    finally
    {
      btr.close();
    }
  }

  @Test
  public void testStealThreeLeafRecordsFromNodeToTheRightWithTooBigNodeSize()
  {
    File tmpFile = FileSupport.createTempFile();
    try
    {
      ReadWritableFile f = new ReadWritableFileAdapter(tmpFile);
      byte[] barr = writeJunk(f);
      // 12 records per node
      int nodeSize = new NumberOfRecordsNodeSizeStrategy(12).getNodeSize(9, 2 * LongSerializer.DATA_SIZE, 0) + 3;
      NodeRepository<Long> nr = new FileBackedNodeRepository<Long, Long>(f, false, f.getSize(), new FixedSizeNodeSizeStrategy(nodeSize), false, LongNullSerializer.INSTANCE, LongSerializer.INSTANCE, 8, 8192, null, new LogAdapterHolder(new StdOutLogAdapter()));
      testStealThreeLeafRecordsFromNodeToTheRightInternal(nr, new NodePositions(nodeSize, nodeSize, nr.getPositionOfRootNode()), LONG_CONVERTER, LONG_CONVERTER);
      assertJunkUntouched(f, barr);
    }
    finally
    {
      assertTrue(tmpFile.delete());
    }
  }

  private <K extends Comparable<K>, V> void testStealThreeLeafRecordsFromNodeToTheRightInternal(Serializer<K> keySerializer, ValueConverter<K> kconv, Serializer<V> valueSerializer, ValueConverter<V> vconv, int cacheSize, int pointerSize, boolean leafNodePointers)
  {
    File tmpFile = FileSupport.createTempFile();
    try
    {
      ReadWritableFile f = new ReadWritableFileAdapter(tmpFile);
      byte[] barr = writeJunk(f);
      NodeRepository<K> nr = new FileBackedNodeRepository<K, V>(f, false, f.getSize(), new NumberOfRecordsNodeSizeStrategy(12), leafNodePointers, keySerializer, valueSerializer, pointerSize, 8192, null, new LogAdapterHolder(new StdOutLogAdapter()));
      if (cacheSize > 0)
      {
        nr = new LruCacheNodeRepository<K, V>(nr, cacheSize);
      }
      testStealThreeLeafRecordsFromNodeToTheRightInternal(nr, new NodePositions(getNonLeafNodeSize(keySerializer.getSerializedSize(), pointerSize, 12), getLeafNodeSize(keySerializer.getSerializedSize(), valueSerializer.getSerializedSize(), pointerSize, 12, leafNodePointers), nr.getPositionOfRootNode()), kconv, vconv);
      assertJunkUntouched(f, barr);
    }
    finally
    {
      assertTrue(tmpFile.delete());
    }
  }

  private void testStealThreeLeafRecordsFromNodeToTheRightInternal(int cacheSize)
  {
    for (int pointerSize : POINTER_SIZES)
    {
      for (boolean lnp : BOOLEANS)
      {
        testStealThreeLeafRecordsFromNodeToTheRightInternal(LongNullSerializer.INSTANCE, LONG_CONVERTER, LongSerializer.INSTANCE, LONG_CONVERTER, cacheSize, pointerSize, lnp);
        testStealThreeLeafRecordsFromNodeToTheRightInternal(LongNullSerializer.INSTANCE, LONG_CONVERTER, IntegerSerializer.INSTANCE, INTEGER_CONVERTER, cacheSize, pointerSize, lnp);
        testStealThreeLeafRecordsFromNodeToTheRightInternal(LongNullSerializer.INSTANCE, LONG_CONVERTER, ShortSerializer.INSTANCE, SHORT_CONVERTER, cacheSize, pointerSize, lnp);
        testStealThreeLeafRecordsFromNodeToTheRightInternal(IntegerNullSerializer.INSTANCE, INTEGER_CONVERTER, LongSerializer.INSTANCE, LONG_CONVERTER, cacheSize, pointerSize, lnp);
        testStealThreeLeafRecordsFromNodeToTheRightInternal(IntegerNullSerializer.INSTANCE, INTEGER_CONVERTER, IntegerSerializer.INSTANCE, INTEGER_CONVERTER, cacheSize, pointerSize, lnp);
        testStealThreeLeafRecordsFromNodeToTheRightInternal(IntegerNullSerializer.INSTANCE, INTEGER_CONVERTER, ShortSerializer.INSTANCE, SHORT_CONVERTER, cacheSize, pointerSize, lnp);
        testStealThreeLeafRecordsFromNodeToTheRightInternal(ShortNullSerializer.INSTANCE, SHORT_CONVERTER, LongSerializer.INSTANCE, LONG_CONVERTER, cacheSize, pointerSize, lnp);
        testStealThreeLeafRecordsFromNodeToTheRightInternal(ShortNullSerializer.INSTANCE, SHORT_CONVERTER, IntegerSerializer.INSTANCE, INTEGER_CONVERTER, cacheSize, pointerSize, lnp);
        testStealThreeLeafRecordsFromNodeToTheRightInternal(ShortNullSerializer.INSTANCE, SHORT_CONVERTER, ShortSerializer.INSTANCE, SHORT_CONVERTER, cacheSize, pointerSize, lnp);
      }
    }
  }

  @Test
  public void testStealThreeLeafRecordsFromNodeToTheRight()
  {
    testStealThreeLeafRecordsFromNodeToTheRightInternal(0);
  }

  @Test
  public void testStealThreeLeafRecordsFromNodeToTheRightWithLruCache()
  {
    testStealThreeLeafRecordsFromNodeToTheRightInternal(10);
  }

  @SuppressWarnings("unchecked")
  private <K extends Comparable<K>, V> void testStealThreeLeafRecordsFromNodeToTheLeftInternal(NodeRepository<K> nr, NodePositions np, ValueConverter<K> kconv, ValueConverter<V> vconv)
  {
    BPlusTree<K, V> btr = new BPlusTree<K, V>(nr, new LogAdapterHolder(new StdOutLogAdapter()));
    try
    {
      // 0[(1),13(2)]
      // 1[1..9] 2[13..19]
      List<KeyAndValue<K, V>> records = mergeIntervals(createInterval(1, 6, kconv, vconv), createInterval(13, 19, kconv, vconv), createInterval(7, 9, kconv, vconv));
      insertTestRecords(btr, records);
      np.addLeaf();
      np.addLeaf();

      BPlusTreeNode<K, ?> rn = nr.readNode(np.get(0), null);
      assertNodesEqual(new BPlusTreeNonLeafNode<K>(np.get(0), null, np.get(1), createLRecordList(createLRecord(13, np.get(2), kconv)), true, false), rn);
      rn = nr.readNode(np.get(1), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(1), createInterval(1, 9, kconv, vconv), false, false, null, np.get(2)), rn);
      rn = nr.readNode(np.get(2), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(2), createInterval(13, 19, kconv, vconv), false, false, np.get(1), null), rn);
      verifyNextAndPreviousRecords(btr, records);

      // Delete 19, 18
      // 0[(1),7(2)]
      // 1[1..6] 2[7..9,13..17]
      assertEquals(vconv.convert(19), btr.delete(kconv.convert(19)));
      assertEquals(vconv.convert(18), btr.delete(kconv.convert(18)));

      rn = nr.readNode(np.get(0), null);
      assertNodesEqual(new BPlusTreeNonLeafNode<K>(np.get(0), null, np.get(1), createLRecordList(createLRecord(7, np.get(2), kconv)), true, false), rn);
      rn = nr.readNode(np.get(1), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(1), createInterval(1, 6, kconv, vconv), false, false, null, np.get(2)), rn);
      rn = nr.readNode(np.get(2), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(2), createLRecordList(createLRecord(kconv, vconv, 7, 7), createLRecord(kconv, vconv, 8, 8), createLRecord(kconv, vconv, 9, 9), createLRecord(kconv, vconv, 13, 13), createLRecord(kconv, vconv, 14, 14), createLRecord(kconv, vconv, 15, 15), createLRecord(kconv, vconv, 16, 16), createLRecord(kconv, vconv, 17, 17)), false, false, np.get(1), null), rn);
      records.remove(new KeyAndValue<K, V>(kconv.convert(18), vconv.convert(18)));
      records.remove(new KeyAndValue<K, V>(kconv.convert(19), vconv.convert(19)));
      verifyNextAndPreviousRecords(btr, records);
    }
    finally
    {
      btr.close();
    }
  }

  private <K extends Comparable<K>, V> void testStealThreeLeafRecordsFromNodeToTheLeftInternal(Serializer<K> keySerializer, ValueConverter<K> kconv, Serializer<V> valueSerializer, ValueConverter<V> vconv, int cacheSize, int pointerSize, boolean leafNodePointers)
  {
    File tmpFile = FileSupport.createTempFile();
    try
    {
      ReadWritableFile f = new ReadWritableFileAdapter(tmpFile);
      byte[] barr = writeJunk(f);
      NodeRepository<K> nr = new FileBackedNodeRepository<K, V>(f, false, f.getSize(), new NumberOfRecordsNodeSizeStrategy(12), leafNodePointers, keySerializer, valueSerializer, pointerSize, 8192, null, new LogAdapterHolder(new StdOutLogAdapter()));
      if (cacheSize > 0)
      {
        nr = new LruCacheNodeRepository<K, V>(nr, cacheSize);
      }
      testStealThreeLeafRecordsFromNodeToTheLeftInternal(nr, new NodePositions(getNonLeafNodeSize(keySerializer.getSerializedSize(), pointerSize, 12), getLeafNodeSize(keySerializer.getSerializedSize(), valueSerializer.getSerializedSize(), pointerSize, 12, leafNodePointers), nr.getPositionOfRootNode()), kconv, vconv);
      assertJunkUntouched(f, barr);
    }
    finally
    {
      assertTrue(tmpFile.delete());
    }
  }

  @Test
  public void testStealThreeLeafRecordsFromNodeToTheLeftWithTooBigNodeSize()
  {
    File tmpFile = FileSupport.createTempFile();
    try
    {
      ReadWritableFile f = new ReadWritableFileAdapter(tmpFile);
      byte[] barr = writeJunk(f);
      // 12 records per node + 4 extra bytes
      int nodeSize = new NumberOfRecordsNodeSizeStrategy(12).getNodeSize(9, 2 * LongSerializer.DATA_SIZE, 0) + 4;
      NodeRepository<Long> nr = new FileBackedNodeRepository<Long, Long>(f, false, f.getSize(), new FixedSizeNodeSizeStrategy(nodeSize), false, LongNullSerializer.INSTANCE, LongSerializer.INSTANCE, 8, 8192, null, new LogAdapterHolder(new StdOutLogAdapter()));
      testStealThreeLeafRecordsFromNodeToTheLeftInternal(nr, new NodePositions(nodeSize, nodeSize, nr.getPositionOfRootNode()), LONG_CONVERTER, LONG_CONVERTER);
      assertJunkUntouched(f, barr);
    }
    finally
    {
      assertTrue(tmpFile.delete());
    }
  }

  private void testStealThreeLeafRecordsFromNodeToTheLeftInternal(int cacheSize)
  {
    for (int pointerSize : POINTER_SIZES)
    {
      for (boolean lnp : BOOLEANS)
      {
        testStealThreeLeafRecordsFromNodeToTheLeftInternal(LongNullSerializer.INSTANCE, LONG_CONVERTER, LongSerializer.INSTANCE, LONG_CONVERTER, cacheSize, pointerSize, lnp);
        testStealThreeLeafRecordsFromNodeToTheLeftInternal(LongNullSerializer.INSTANCE, LONG_CONVERTER, IntegerSerializer.INSTANCE, INTEGER_CONVERTER, cacheSize, pointerSize, lnp);
        testStealThreeLeafRecordsFromNodeToTheLeftInternal(LongNullSerializer.INSTANCE, LONG_CONVERTER, ShortSerializer.INSTANCE, SHORT_CONVERTER, cacheSize, pointerSize, lnp);
        testStealThreeLeafRecordsFromNodeToTheLeftInternal(IntegerNullSerializer.INSTANCE, INTEGER_CONVERTER, LongSerializer.INSTANCE, LONG_CONVERTER, cacheSize, pointerSize, lnp);
        testStealThreeLeafRecordsFromNodeToTheLeftInternal(IntegerNullSerializer.INSTANCE, INTEGER_CONVERTER, IntegerSerializer.INSTANCE, INTEGER_CONVERTER, cacheSize, pointerSize, lnp);
        testStealThreeLeafRecordsFromNodeToTheLeftInternal(IntegerNullSerializer.INSTANCE, INTEGER_CONVERTER, ShortSerializer.INSTANCE, SHORT_CONVERTER, cacheSize, pointerSize, lnp);
        testStealThreeLeafRecordsFromNodeToTheLeftInternal(ShortNullSerializer.INSTANCE, SHORT_CONVERTER, LongSerializer.INSTANCE, LONG_CONVERTER, cacheSize, pointerSize, lnp);
        testStealThreeLeafRecordsFromNodeToTheLeftInternal(ShortNullSerializer.INSTANCE, SHORT_CONVERTER, IntegerSerializer.INSTANCE, INTEGER_CONVERTER, cacheSize, pointerSize, lnp);
        testStealThreeLeafRecordsFromNodeToTheLeftInternal(ShortNullSerializer.INSTANCE, SHORT_CONVERTER, ShortSerializer.INSTANCE, SHORT_CONVERTER, cacheSize, pointerSize, lnp);
      }
    }
  }

  @Test
  public void testStealThreeLeafRecordsFromNodeToTheLeft()
  {
    testStealThreeLeafRecordsFromNodeToTheLeftInternal(0);
  }

  @Test
  public void testStealThreeLeafRecordsFromNodeToTheLeftWithLruCache()
  {
    testStealThreeLeafRecordsFromNodeToTheLeftInternal(10);
  }

  @SuppressWarnings("unchecked")
  private <K extends Comparable<K>, V> void testStealTwoNonLeafRecordsFromNodeToTheRightInternal(NodeRepository<K> nr, NodePositions np, ValueConverter<K> kconv, ValueConverter<V> vconv)
  {
    BPlusTree<K, V> btr = new BPlusTree<K, V>(nr, new LogAdapterHolder(new StdOutLogAdapter()));
    try
    {
      // 0[(11), 21(12)]
      // 11[(1),5(2),9(3),13(4),17(5),]
      // 12[(6),25(7),29(8),33(9),37(10),41(13),]
      // 1[1..4] 2[5..8] 3[9..12] 4[13..16] 5[17..20] 6[21..24]
      // 7[25..28] 8[29..32] 9[33..36] 10[37..40] 13[41..45]
      List<KeyAndValue<K, V>> records = createInterval(1, 45, kconv, vconv);
      insertTestRecords(btr, records);
      for (int i = 1; i <= 10; i++)
      {
        np.addLeaf();
      }
      np.addNonLeaf();
      np.addNonLeaf();
      np.addLeaf();

      BPlusTreeNode<K, ?> rn = nr.readNode(np.get(0), null);
      assertNodesEqual(new BPlusTreeNonLeafNode<K>(np.get(0), null, np.get(11), createLRecordList(createLRecord(21, np.get(12), kconv)), true, false), rn);
      rn = nr.readNode(np.get(1), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(1), createInterval(1, 4, kconv, vconv), false, false, null, np.get(2)), rn);
      rn = nr.readNode(np.get(2), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(2), createInterval(5, 8, kconv, vconv), false, false, np.get(1), np.get(3)), rn);
      rn = nr.readNode(np.get(3), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(3), createInterval(9, 12, kconv, vconv), false, false, np.get(2), np.get(4)), rn);
      rn = nr.readNode(np.get(4), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(4), createInterval(13, 16, kconv, vconv), false, false, np.get(3), np.get(5)), rn);
      rn = nr.readNode(np.get(5), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(5), createInterval(17, 20, kconv, vconv), false, false, np.get(4), np.get(6)), rn);
      rn = nr.readNode(np.get(6), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(6), createInterval(21, 24, kconv, vconv), false, false, np.get(5), np.get(7)), rn);
      rn = nr.readNode(np.get(7), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(7), createInterval(25, 28, kconv, vconv), false, false, np.get(6), np.get(8)), rn);
      rn = nr.readNode(np.get(8), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(8), createInterval(29, 32, kconv, vconv), false, false, np.get(7), np.get(9)), rn);
      rn = nr.readNode(np.get(9), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(9), createInterval(33, 36, kconv, vconv), false, false, np.get(8), np.get(10)), rn);
      rn = nr.readNode(np.get(10), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(10), createInterval(37, 40, kconv, vconv), false, false, np.get(9), np.get(13)), rn);
      rn = nr.readNode(np.get(11), null);
      assertNodesEqual(new BPlusTreeNonLeafNode<K>(np.get(11), null, np.get(1), createLRecordList(createLRecord(5, np.get(2), kconv), createLRecord(9, np.get(3), kconv), createLRecord(13, np.get(4), kconv), createLRecord(17, np.get(5), kconv)), false, false), rn);
      rn = nr.readNode(np.get(12), kconv.convert(21));
      assertNodesEqual(new BPlusTreeNonLeafNode<K>(np.get(12), kconv.convert(21), np.get(6), createLRecordList(createLRecord(25, np.get(7), kconv), createLRecord(29, np.get(8), kconv), createLRecord(33, np.get(9), kconv), createLRecord(37, np.get(10), kconv), createLRecord(41, np.get(13), kconv)), false, false), rn);
      rn = nr.readNode(np.get(13), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(13), createInterval(41, 45, kconv, vconv), false, false, np.get(10), null), rn);
      verifyNextAndPreviousRecords(btr, records);

      // Delete 1
      assertEquals(vconv.convert(1), btr.delete(kconv.convert(1)));
      // 0[(11), 29(12)]
      // 11[(1),9(3),13(4),17(5),21(6),25(7)]
      // 12[(8),33(9),37(10),41(13),]
      // 1[2..8] 3[9..12] 4[13..16] 5[17..20] 6[21..24] 7[25..28]
      // 8[29..32] 9[33..36] 10[37..40] 13[41..45]

      rn = nr.readNode(np.get(0), null);
      assertNodesEqual(new BPlusTreeNonLeafNode<K>(np.get(0), null, np.get(11), createLRecordList(createLRecord(29, np.get(12), kconv)), true, false), rn);
      rn = nr.readNode(np.get(1), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(1), createInterval(2, 8, kconv, vconv), false, false, null, np.get(3)), rn);
      assertNodeDeleted(nr, np.get(2));
      rn = nr.readNode(np.get(3), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(3), createInterval(9, 12, kconv, vconv), false, false, np.get(1), np.get(4)), rn);
      rn = nr.readNode(np.get(11), null);
      assertNodesEqual(new BPlusTreeNonLeafNode<K>(np.get(11), null, np.get(1), createLRecordList(createLRecord(9, np.get(3), kconv), createLRecord(13, np.get(4), kconv), createLRecord(17, np.get(5), kconv), createLRecord(21, np.get(6), kconv), createLRecord(25, np.get(7), kconv)), false, false), rn);
      rn = nr.readNode(np.get(12), kconv.convert(29));
      assertNodesEqual(new BPlusTreeNonLeafNode<K>(np.get(12), kconv.convert(29), np.get(8), createLRecordList(createLRecord(33, np.get(9), kconv), createLRecord(37, np.get(10), kconv), createLRecord(41, np.get(13), kconv)), false, false), rn);
      records.remove(new KeyAndValue<K, V>(kconv.convert(1), vconv.convert(1)));
      verifyNextAndPreviousRecords(btr, records);
    }
    finally
    {
      btr.close();
    }
  }

  private <K extends Comparable<K>, V> void testStealTwoNonLeafRecordsFromNodeToTheRightInternal(Serializer<K> keySerializer, ValueConverter<K> kconv, Serializer<V> valueSerializer, ValueConverter<V> vconv, int cacheSize, int pointerSize, boolean leafNodePointers)
  {
    File tmpFile = FileSupport.createTempFile();
    try
    {
      ReadWritableFile f = new ReadWritableFileAdapter(tmpFile);
      byte[] barr = writeJunk(f);
      NodeRepository<K> nr = new FileBackedNodeRepository<K, V>(f, false, f.getSize(), new NumberOfRecordsNodeSizeStrategy(8), leafNodePointers, keySerializer, valueSerializer, pointerSize, 8192, null, new LogAdapterHolder(new StdOutLogAdapter()));
      if (cacheSize > 0)
      {
        nr = new LruCacheNodeRepository<K, V>(nr, cacheSize);
      }
      testStealTwoNonLeafRecordsFromNodeToTheRightInternal(nr, new NodePositions(getNonLeafNodeSize(keySerializer.getSerializedSize(), pointerSize, 8), getLeafNodeSize(keySerializer.getSerializedSize(), valueSerializer.getSerializedSize(), pointerSize, 8, leafNodePointers), nr.getPositionOfRootNode()), kconv, vconv);
      assertJunkUntouched(f, barr);
    }
    finally
    {
      assertTrue(tmpFile.delete());
    }
  }

  @Test
  public void testStealTwoNonLeafRecordsFromNodeToTheRightAndTooBigNodeSize()
  {
    File tmpFile = FileSupport.createTempFile();
    try
    {
      ReadWritableFile f = new ReadWritableFileAdapter(tmpFile);
      byte[] barr = writeJunk(f);
      // 8 records per node + 5 extra bytes
      int nodeSize = new NumberOfRecordsNodeSizeStrategy(8).getNodeSize(9, 2 * LongSerializer.DATA_SIZE, 0) + 5;
      NodeRepository<Long> nr = new FileBackedNodeRepository<Long, Long>(f, false, f.getSize(), new FixedSizeNodeSizeStrategy(nodeSize), false, LongNullSerializer.INSTANCE, LongSerializer.INSTANCE, 8, 8192, null, new LogAdapterHolder(new StdOutLogAdapter()));
      testStealTwoNonLeafRecordsFromNodeToTheRightInternal(nr, new NodePositions(nodeSize, nodeSize, nr.getPositionOfRootNode()), LONG_CONVERTER, LONG_CONVERTER);
      assertJunkUntouched(f, barr);
    }
    finally
    {
      assertTrue(tmpFile.delete());
    }
  }

  private void testStealTwoNonLeafRecordsFromNodeToTheRightInternal(int cacheSize)
  {
    for (int pointerSize : POINTER_SIZES)
    {
      for (boolean lnp : BOOLEANS)
      {
        testStealTwoNonLeafRecordsFromNodeToTheRightInternal(LongNullSerializer.INSTANCE, LONG_CONVERTER, LongSerializer.INSTANCE, LONG_CONVERTER, cacheSize, pointerSize, lnp);
        testStealTwoNonLeafRecordsFromNodeToTheRightInternal(LongNullSerializer.INSTANCE, LONG_CONVERTER, IntegerSerializer.INSTANCE, INTEGER_CONVERTER, cacheSize, pointerSize, lnp);
        testStealTwoNonLeafRecordsFromNodeToTheRightInternal(LongNullSerializer.INSTANCE, LONG_CONVERTER, ShortSerializer.INSTANCE, SHORT_CONVERTER, cacheSize, pointerSize, lnp);
        testStealTwoNonLeafRecordsFromNodeToTheRightInternal(IntegerNullSerializer.INSTANCE, INTEGER_CONVERTER, LongSerializer.INSTANCE, LONG_CONVERTER, cacheSize, pointerSize, lnp);
        testStealTwoNonLeafRecordsFromNodeToTheRightInternal(IntegerNullSerializer.INSTANCE, INTEGER_CONVERTER, IntegerSerializer.INSTANCE, INTEGER_CONVERTER, cacheSize, pointerSize, lnp);
        testStealTwoNonLeafRecordsFromNodeToTheRightInternal(IntegerNullSerializer.INSTANCE, INTEGER_CONVERTER, ShortSerializer.INSTANCE, SHORT_CONVERTER, cacheSize, pointerSize, lnp);
        testStealTwoNonLeafRecordsFromNodeToTheRightInternal(ShortNullSerializer.INSTANCE, SHORT_CONVERTER, LongSerializer.INSTANCE, LONG_CONVERTER, cacheSize, pointerSize, lnp);
        testStealTwoNonLeafRecordsFromNodeToTheRightInternal(ShortNullSerializer.INSTANCE, SHORT_CONVERTER, IntegerSerializer.INSTANCE, INTEGER_CONVERTER, cacheSize, pointerSize, lnp);
        testStealTwoNonLeafRecordsFromNodeToTheRightInternal(ShortNullSerializer.INSTANCE, SHORT_CONVERTER, ShortSerializer.INSTANCE, SHORT_CONVERTER, cacheSize, pointerSize, lnp);
      }
    }
  }

  @Test
  public void testStealTwoNonLeafRecordsFromNodeToTheRight()
  {
    testStealTwoNonLeafRecordsFromNodeToTheRightInternal(0);
  }

  @Test
  public void testStealTwoNonLeafRecordsFromNodeToTheRightWithLruCache()
  {
    testStealTwoNonLeafRecordsFromNodeToTheRightInternal(10);
  }

  @SuppressWarnings("unchecked")
  private <K extends Comparable<K>, V> void testStealTwoNonLeafRecordsFromNodeToTheLeftInternal(NodeRepository<K> nr, NodePositions np, ValueConverter<K> kconv, ValueConverter<V> vconv)
  {
    BPlusTree<K, V> btr = new BPlusTree<K, V>(nr, new LogAdapterHolder(new StdOutLogAdapter()));
    try
    {
      // 0[(11), 29(12)]
      // 11[(1),5(2),9(3),13(4),17(5),21(14),]
      // 12[(6),33(7),37(8),41(9),45(10),49(13)]
      // 1[1..4] 2[5..8] 3[9..12] 4[13..16] 5[17..20] 14[21..25]
      // 6[29..32] 7[33..36] 8[37..40] 9[41..44] 10[45..48] 13[49..53]
      List<KeyAndValue<K, V>> records = mergeIntervals(createInterval(1, 20, kconv, vconv), createInterval(29, 53, kconv, vconv), createInterval(21, 25, kconv, vconv));
      insertTestRecords(btr, records);
      for (int i = 1; i <= 10; i++)
      {
        np.addLeaf();
      }
      np.addNonLeaf();
      np.addNonLeaf();
      np.addLeaf();

      BPlusTreeNode<K, ?> rn = nr.readNode(np.get(0), null);
      assertNodesEqual(new BPlusTreeNonLeafNode<K>(np.get(0), null, np.get(11), createLRecordList(createLRecord(29, np.get(12), kconv)), true, false), rn);
      rn = nr.readNode(np.get(1), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(1), createInterval(1, 4, kconv, vconv), false, false, null, np.get(2)), rn);
      rn = nr.readNode(np.get(2), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(2), createInterval(5, 8, kconv, vconv), false, false, np.get(1), np.get(3)), rn);
      rn = nr.readNode(np.get(3), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(3), createInterval(9, 12, kconv, vconv), false, false, np.get(2), np.get(4)), rn);
      rn = nr.readNode(np.get(4), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(4), createInterval(13, 16, kconv, vconv), false, false, np.get(3), np.get(5)), rn);
      rn = nr.readNode(np.get(5), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(5), createInterval(17, 20, kconv, vconv), false, false, np.get(4), np.get(14)), rn);
      rn = nr.readNode(np.get(6), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(6), createInterval(29, 32, kconv, vconv), false, false, np.get(14), np.get(7)), rn);
      rn = nr.readNode(np.get(7), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(7), createInterval(33, 36, kconv, vconv), false, false, np.get(6), np.get(8)), rn);
      rn = nr.readNode(np.get(8), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(8), createInterval(37, 40, kconv, vconv), false, false, np.get(7), np.get(9)), rn);
      rn = nr.readNode(np.get(9), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(9), createInterval(41, 44, kconv, vconv), false, false, np.get(8), np.get(10)), rn);
      rn = nr.readNode(np.get(10), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(10), createInterval(45, 48, kconv, vconv), false, false, np.get(9), np.get(13)), rn);
      rn = nr.readNode(np.get(11), null);
      assertNodesEqual(new BPlusTreeNonLeafNode<K>(np.get(11), null, np.get(1), createLRecordList(createLRecord(5, np.get(2), kconv), createLRecord(9, np.get(3), kconv), createLRecord(13, np.get(4), kconv), createLRecord(17, np.get(5), kconv), createLRecord(21, np.get(14), kconv)), false, false), rn);
      rn = nr.readNode(np.get(12), kconv.convert(29));
      assertNodesEqual(new BPlusTreeNonLeafNode<K>(np.get(12), kconv.convert(29), np.get(6), createLRecordList(createLRecord(33, np.get(7), kconv), createLRecord(37, np.get(8), kconv), createLRecord(41, np.get(9), kconv), createLRecord(45, np.get(10), kconv), createLRecord(49, np.get(13), kconv)), false, false), rn);
      rn = nr.readNode(np.get(13), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(13), createInterval(49, 53, kconv, vconv), false, false, np.get(10), null), rn);
      rn = nr.readNode(np.get(14), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(14), createInterval(21, 25, kconv, vconv), false, false, np.get(5), np.get(6)), rn);
      verifyNextAndPreviousRecords(btr, records);

      // Delete 45, 29
      assertEquals(vconv.convert(45), btr.delete(kconv.convert(45)));
      assertEquals(vconv.convert(29), btr.delete(kconv.convert(29)));
      // 0[(11), 17(12)]
      // 11[(1),5(2),9(3),13(4),]
      // 12[(5),21(14),30(6),37(8),41(10),49(13),]
      // 1[1..4] 2[5..8] 3[9..12] 4[13..16] 5[17..20] 14[21..25]
      // 6[30..36] 8[37..40] 10[41..44,46..48] 13[49..53]

      rn = nr.readNode(np.get(0), null);
      assertNodesEqual(new BPlusTreeNonLeafNode<K>(np.get(0), null, np.get(11), createLRecordList(createLRecord(17, np.get(12), kconv)), true, false), rn);
      rn = nr.readNode(np.get(6), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(6), createInterval(30, 36, kconv, vconv), false, false, np.get(14), np.get(8)), rn);
      assertNodeDeleted(nr, np.get(7));
      rn = nr.readNode(np.get(8), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(8), createInterval(37, 40, kconv, vconv), false, false, np.get(6), np.get(10)), rn);
      assertNodeDeleted(nr, np.get(9));
      rn = nr.readNode(np.get(10), null);
      assertNodesEqual(new BPlusTreeLeafNode<K, V>(np.get(10), createLRecordList(createLRecord(kconv, vconv, 41, 41), createLRecord(kconv, vconv, 42, 42), createLRecord(kconv, vconv, 43, 43), createLRecord(kconv, vconv, 44, 44), createLRecord(kconv, vconv, 46, 46), createLRecord(kconv, vconv, 47, 47), createLRecord(kconv, vconv, 48, 48)), false, false, np.get(8), np.get(13)), rn);
      rn = nr.readNode(np.get(11), null);
      assertNodesEqual(new BPlusTreeNonLeafNode<K>(np.get(11), null, np.get(1), createLRecordList(createLRecord(5, np.get(2), kconv), createLRecord(9, np.get(3), kconv), createLRecord(13, np.get(4), kconv)), false, false), rn);
      rn = nr.readNode(np.get(12), kconv.convert(17));
      assertNodesEqual(new BPlusTreeNonLeafNode<K>(np.get(12), kconv.convert(17), np.get(5), createLRecordList(createLRecord(21, np.get(14), kconv), createLRecord(30, np.get(6), kconv), createLRecord(37, np.get(8), kconv), createLRecord(41, np.get(10), kconv), createLRecord(49, np.get(13), kconv)), false, false), rn);
      records.remove(new KeyAndValue<K, V>(kconv.convert(45), vconv.convert(45)));
      records.remove(new KeyAndValue<K, V>(kconv.convert(29), vconv.convert(29)));
      verifyNextAndPreviousRecords(btr, records);
    }
    finally
    {
      btr.close();
    }
  }

  private <K extends Comparable<K>, V> void testStealTwoNonLeafRecordsFromNodeToTheLeftInternal(Serializer<K> keySerializer, ValueConverter<K> kconv, Serializer<V> valueSerializer, ValueConverter<V> vconv, int cacheSize, int pointerSize, boolean leafNodePointers)
  {
    File tmpFile = FileSupport.createTempFile();
    try
    {
      ReadWritableFile f = new ReadWritableFileAdapter(tmpFile);
      byte[] barr = writeJunk(f);
      NodeRepository<K> nr = new FileBackedNodeRepository<K, V>(f, false, f.getSize(), new NumberOfRecordsNodeSizeStrategy(8), leafNodePointers, keySerializer, valueSerializer, pointerSize, 8192, null, new LogAdapterHolder(new StdOutLogAdapter()));
      if (cacheSize > 0)
      {
        nr = new LruCacheNodeRepository<K, V>(nr, cacheSize);
      }
      testStealTwoNonLeafRecordsFromNodeToTheLeftInternal(nr, new NodePositions(getNonLeafNodeSize(keySerializer.getSerializedSize(), pointerSize, 8), getLeafNodeSize(keySerializer.getSerializedSize(), valueSerializer.getSerializedSize(), pointerSize, 8, leafNodePointers), nr.getPositionOfRootNode()), kconv, vconv);
      assertJunkUntouched(f, barr);
    }
    finally
    {
      assertTrue(tmpFile.delete());
    }
  }

  @Test
  public void testStealTwoNonLeafRecordsFromNodeToTheLeftWithTooBigNodeSize()
  {
    File tmpFile = FileSupport.createTempFile();
    try
    {
      ReadWritableFile f = new ReadWritableFileAdapter(tmpFile);
      byte[] barr = writeJunk(f);
      // 8 records per node + 5 extra bytes
      int nodeSize = new NumberOfRecordsNodeSizeStrategy(8).getNodeSize(9, 16, 0) + 5;
      NodeRepository<Long> nr = new FileBackedNodeRepository<Long, Long>(f, false, f.getSize(), new FixedSizeNodeSizeStrategy(nodeSize), false, LongNullSerializer.INSTANCE, LongSerializer.INSTANCE, 8, 8192, null, new LogAdapterHolder(new StdOutLogAdapter()));
      testStealTwoNonLeafRecordsFromNodeToTheLeftInternal(nr, new NodePositions(nodeSize, nodeSize, nr.getPositionOfRootNode()), LONG_CONVERTER, LONG_CONVERTER);
      assertJunkUntouched(f, barr);
    }
    finally
    {
      assertTrue(tmpFile.delete());
    }
  }

  private void testStealTwoNonLeafRecordsFromNodeToTheLeftInternal(int cacheSize)
  {
    for (int pointerSize : POINTER_SIZES)
    {
      for (boolean lnp : BOOLEANS)
      {
        testStealTwoNonLeafRecordsFromNodeToTheLeftInternal(LongNullSerializer.INSTANCE, LONG_CONVERTER, LongSerializer.INSTANCE, LONG_CONVERTER, cacheSize, pointerSize, lnp);
        testStealTwoNonLeafRecordsFromNodeToTheLeftInternal(LongNullSerializer.INSTANCE, LONG_CONVERTER, IntegerSerializer.INSTANCE, INTEGER_CONVERTER, cacheSize, pointerSize, lnp);
        testStealTwoNonLeafRecordsFromNodeToTheLeftInternal(LongNullSerializer.INSTANCE, LONG_CONVERTER, ShortSerializer.INSTANCE, SHORT_CONVERTER, cacheSize, pointerSize, lnp);
        testStealTwoNonLeafRecordsFromNodeToTheLeftInternal(IntegerNullSerializer.INSTANCE, INTEGER_CONVERTER, LongSerializer.INSTANCE, LONG_CONVERTER, cacheSize, pointerSize, lnp);
        testStealTwoNonLeafRecordsFromNodeToTheLeftInternal(IntegerNullSerializer.INSTANCE, INTEGER_CONVERTER, IntegerSerializer.INSTANCE, INTEGER_CONVERTER, cacheSize, pointerSize, lnp);
        testStealTwoNonLeafRecordsFromNodeToTheLeftInternal(IntegerNullSerializer.INSTANCE, INTEGER_CONVERTER, ShortSerializer.INSTANCE, SHORT_CONVERTER, cacheSize, pointerSize, lnp);
        testStealTwoNonLeafRecordsFromNodeToTheLeftInternal(ShortNullSerializer.INSTANCE, SHORT_CONVERTER, LongSerializer.INSTANCE, LONG_CONVERTER, cacheSize, pointerSize, lnp);
        testStealTwoNonLeafRecordsFromNodeToTheLeftInternal(ShortNullSerializer.INSTANCE, SHORT_CONVERTER, IntegerSerializer.INSTANCE, INTEGER_CONVERTER, cacheSize, pointerSize, lnp);
        testStealTwoNonLeafRecordsFromNodeToTheLeftInternal(ShortNullSerializer.INSTANCE, SHORT_CONVERTER, ShortSerializer.INSTANCE, SHORT_CONVERTER, cacheSize, pointerSize, lnp);
      }
    }
  }

  @Test
  public void testStealTwoNonLeafRecordsFromNodeToTheLeft()
  {
    testStealTwoNonLeafRecordsFromNodeToTheLeftInternal(0);
  }

  @Test
  public void testStealTwoNonLeafRecordsFromNodeToTheLeftWithLruCache()
  {
    testStealTwoNonLeafRecordsFromNodeToTheLeftInternal(10);
  }

  @Test
  public void testIteratorsWhenNotSupported()
  {
    File tmpFile = FileSupport.createTempFile();
    try
    {
      ReadWritableFile f = new ReadWritableFileAdapter(tmpFile);
      byte[] barr = writeJunk(f);
      NodeRepository<Long> nr = new FileBackedNodeRepository<Long, Long>(f, false, f.getSize(), new NumberOfRecordsNodeSizeStrategy(2), false, LongNullSerializer.INSTANCE, LongSerializer.INSTANCE, 8, 8192, null, new LogAdapterHolder(new StdOutLogAdapter()));
      BPlusTree<Long, Long> btr = new BPlusTree<Long, Long>(nr, new LogAdapterHolder(new StdOutLogAdapter()));
      try
      {
        assertFalse(btr.isIteratorSupported());

        try
        {
          btr.keyIterator();
          fail();
        }
        catch (UnsupportedOperationException e)
        {
          // ok
        }

        try
        {
          btr.valueIterator();
          fail();
        }
        catch (UnsupportedOperationException e)
        {
          // ok
        }

        try
        {
          btr.iterator();
          fail();
        }
        catch (UnsupportedOperationException e)
        {
          // ok
        }
      }
      finally
      {
        btr.close();
      }
      assertJunkUntouched(f, barr);
    }
    finally
    {
      assertTrue(tmpFile.delete());
    }
  }

  private void assertGivesConcurrentModificationException(Iterator<?> itr)
  {
    try
    {
      itr.hasNext();
      fail();
    }
    catch (ConcurrentModificationException e)
    {
      // ok
    }

    try
    {
      itr.next();
      fail();
    }
    catch (ConcurrentModificationException e)
    {
      // ok
    }
  }

  @Test
  public void testKeyIterator()
  {
    File tmpFile = FileSupport.createTempFile();
    try
    {
      ReadWritableFile f = new ReadWritableFileAdapter(tmpFile);
      byte[] barr = writeJunk(f);
      Iterator<Long> itr = null;
      BPlusTree<Long, Integer> btr = new BPlusTree<Long, Integer>(new FileBackedNodeRepository<Long, Integer>(f, false, f.getSize(), new NumberOfRecordsNodeSizeStrategy(2), true, LongNullSerializer.INSTANCE, IntegerSerializer.INSTANCE, 3, 8192, null, new LogAdapterHolder(new StdOutLogAdapter())), new LogAdapterHolder(new StdOutLogAdapter()));
      try
      {
        itr = btr.keyIterator();
        assertFalse(itr.hasNext());

        btr.insert(Long.valueOf(1L), Integer.valueOf(1));
        btr.insert(Long.valueOf(8L), Integer.valueOf(8));
        btr.insert(Long.valueOf(6L), Integer.valueOf(6));
        btr.insert(Long.valueOf(2L), Integer.valueOf(2));
        btr.insert(Long.valueOf(5L), Integer.valueOf(5));

        itr = btr.keyIterator();
        assertTrue(itr.hasNext());
        assertEquals(1L, itr.next().longValue());
        assertTrue(itr.hasNext());
        assertEquals(2L, itr.next().longValue());
        assertTrue(itr.hasNext());
        assertEquals(5L, itr.next().longValue());
        assertEquals(6L, itr.next().longValue());
        assertTrue(itr.hasNext());
        assertEquals(8L, itr.next().longValue());
        assertFalse(itr.hasNext());
        try
        {
          itr.next();
          fail();
        }
        catch (NoSuchElementException e)
        {
          // ok
        }

        // Test concurrent modifications
        itr = btr.keyIterator();
        assertTrue(itr.hasNext());
        btr.insert(Long.valueOf(10L), Integer.valueOf(10));
        assertGivesConcurrentModificationException(itr);

        itr = btr.keyIterator();
        assertTrue(itr.hasNext());
        btr.delete(10L);
        assertGivesConcurrentModificationException(itr);

        itr = btr.keyIterator();
        assertTrue(itr.hasNext());
        if (btr.compact())
        {
          assertGivesConcurrentModificationException(itr);
        }

        // Replacing values does not affect key iteration
        itr = btr.keyIterator();
        assertTrue(itr.hasNext());
        btr.replace(2L, 20);
        assertTrue(itr.hasNext());

        itr = btr.keyIterator();
        assertTrue(itr.hasNext());
        btr.clear();
        assertGivesConcurrentModificationException(itr);
      }
      finally
      {
        btr.close();

        try
        {
          itr.hasNext();
          fail();
        }
        catch (IllegalStateException e)
        {
          // ok
        }

        try
        {
          itr = btr.keyIterator();
          fail();
        }
        catch (IllegalStateException e)
        {
          // ok
        }
      }
      assertJunkUntouched(f, barr);
    }
    finally
    {
      assertTrue(tmpFile.delete());
    }
  }

  @Test
  public void testKeyIteratorForEmptyTree()
  {
    File tmpFile = FileSupport.createTempFile();
    try
    {
      ReadWritableFile f = new ReadWritableFileAdapter(tmpFile);
      byte[] barr = writeJunk(f);
      Iterator<Long> itr = null;
      BPlusTree<Long, Integer> btr = new BPlusTree<Long, Integer>(new LruCacheNodeRepository<Long, Integer>(new FileBackedNodeRepository<Long, Integer>(f, false, f.getSize(), new NumberOfRecordsNodeSizeStrategy(2), true, LongNullSerializer.INSTANCE, IntegerSerializer.INSTANCE, 3, 8192, null, new LogAdapterHolder(new StdOutLogAdapter())), 10), new LogAdapterHolder(new StdOutLogAdapter()));
      try
      {
        itr = btr.keyIterator();
        assertFalse(itr.hasNext());
        try
        {
          itr.next();
          fail();
        }
        catch (NoSuchElementException e)
        {
          // ok
        }
      }
      finally
      {
        btr.close();
      }
      assertJunkUntouched(f, barr);
    }
    finally
    {
      assertTrue(tmpFile.delete());
    }
  }

  @Test
  public void testValueIterator()
  {
    File tmpFile = FileSupport.createTempFile();
    try
    {
      ReadWritableFile f = new ReadWritableFileAdapter(tmpFile);
      byte[] barr = writeJunk(f);
      Iterator<Integer> itr = null;
      BPlusTree<Long, Integer> btr = new BPlusTree<Long, Integer>(new FileBackedNodeRepository<Long, Integer>(f, false, f.getSize(), new NumberOfRecordsNodeSizeStrategy(2), true, LongNullSerializer.INSTANCE, IntegerSerializer.INSTANCE, 3, 8192, null, new LogAdapterHolder(new StdOutLogAdapter())), new LogAdapterHolder(new StdOutLogAdapter()));
      try
      {
        itr = btr.valueIterator();
        assertFalse(itr.hasNext());

        btr.insert(Long.valueOf(1L), Integer.valueOf(1));
        btr.insert(Long.valueOf(8L), Integer.valueOf(8));
        btr.insert(Long.valueOf(6L), Integer.valueOf(6));
        btr.insert(Long.valueOf(2L), Integer.valueOf(2));
        btr.insert(Long.valueOf(5L), Integer.valueOf(5));

        itr = btr.valueIterator();
        assertTrue(itr.hasNext());
        assertEquals(1, itr.next().intValue());
        assertTrue(itr.hasNext());
        assertEquals(2, itr.next().intValue());
        assertTrue(itr.hasNext());
        assertEquals(5, itr.next().intValue());
        assertEquals(6, itr.next().intValue());
        assertTrue(itr.hasNext());
        assertEquals(8, itr.next().intValue());
        assertFalse(itr.hasNext());
        try
        {
          itr.next();
          fail();
        }
        catch (NoSuchElementException e)
        {
          // ok
        }

        // Test concurrent modifications
        itr = btr.valueIterator();
        assertTrue(itr.hasNext());
        btr.insert(Long.valueOf(10L), Integer.valueOf(10));
        assertGivesConcurrentModificationException(itr);

        itr = btr.valueIterator();
        assertTrue(itr.hasNext());
        btr.delete(10L);
        assertGivesConcurrentModificationException(itr);

        itr = btr.valueIterator();
        assertTrue(itr.hasNext());
        if (btr.compact())
        {
          assertGivesConcurrentModificationException(itr);
        }

        itr = btr.valueIterator();
        assertTrue(itr.hasNext());
        btr.replace(2L, 20);
        assertGivesConcurrentModificationException(itr);

        itr = btr.valueIterator();
        assertTrue(itr.hasNext());
        btr.clear();
        assertGivesConcurrentModificationException(itr);

        itr = btr.valueIterator();
      }
      finally
      {
        btr.close();

        try
        {
          itr.hasNext();
          fail();
        }
        catch (IllegalStateException e)
        {
          // ok
        }

        try
        {
          itr = btr.valueIterator();
          fail();
        }
        catch (IllegalStateException e)
        {
          // ok
        }
      }
      assertJunkUntouched(f, barr);
    }
    finally
    {
      assertTrue(tmpFile.delete());
    }
  }

  @Test
  public void testValueIteratorForEmptyTree()
  {
    File tmpFile = FileSupport.createTempFile();
    try
    {
      ReadWritableFile f = new ReadWritableFileAdapter(tmpFile);
      byte[] barr = writeJunk(f);
      Iterator<Integer> itr = null;
      BPlusTree<Long, Integer> btr = new BPlusTree<Long, Integer>(new FileBackedNodeRepository<Long, Integer>(f, false, f.getSize(), new NumberOfRecordsNodeSizeStrategy(2), true, LongNullSerializer.INSTANCE, IntegerSerializer.INSTANCE, 3, 8192, null, new LogAdapterHolder(new StdOutLogAdapter())), new LogAdapterHolder(new StdOutLogAdapter()));
      try
      {
        itr = btr.valueIterator();
        assertFalse(itr.hasNext());
        try
        {
          itr.next();
          fail();
        }
        catch (NoSuchElementException e)
        {
          // ok
        }
      }
      finally
      {
        btr.close();
      }
      assertJunkUntouched(f, barr);
    }
    finally
    {
      assertTrue(tmpFile.delete());
    }

  }

  @Test
  public void testIterator()
  {
    File tmpFile = FileSupport.createTempFile();
    try
    {
      ReadWritableFile f = new ReadWritableFileAdapter(tmpFile);
      byte[] barr = writeJunk(f);
      Iterator<Record<Long, Integer>> itr = null;
      BPlusTree<Long, Integer> btr = new BPlusTree<Long, Integer>(new FileBackedNodeRepository<Long, Integer>(f, false, f.getSize(), new NumberOfRecordsNodeSizeStrategy(2), true, LongNullSerializer.INSTANCE, IntegerSerializer.INSTANCE, 3, 8192, null, new LogAdapterHolder(new StdOutLogAdapter())), new LogAdapterHolder(new StdOutLogAdapter()));
      try
      {
        itr = btr.iterator();
        assertFalse(itr.hasNext());

        btr.insert(Long.valueOf(1L), Integer.valueOf(1));
        btr.insert(Long.valueOf(8L), Integer.valueOf(8));
        btr.insert(Long.valueOf(6L), Integer.valueOf(6));
        btr.insert(Long.valueOf(2L), Integer.valueOf(2));
        btr.insert(Long.valueOf(5L), Integer.valueOf(5));

        itr = btr.iterator();
        assertTrue(itr.hasNext());
        assertEquals(new Record<Long, Integer>(1L, 1), itr.next());
        assertTrue(itr.hasNext());
        assertEquals(new Record<Long, Integer>(2L, 2), itr.next());
        assertTrue(itr.hasNext());
        assertEquals(new Record<Long, Integer>(5L, 5), itr.next());
        assertEquals(new Record<Long, Integer>(6L, 6), itr.next());
        assertTrue(itr.hasNext());
        assertEquals(new Record<Long, Integer>(8L, 8), itr.next());
        assertFalse(itr.hasNext());
        try
        {
          itr.next();
          fail();
        }
        catch (NoSuchElementException e)
        {
          // ok
        }

        // Test concurrent modifications
        itr = btr.iterator();
        assertTrue(itr.hasNext());
        btr.insert(Long.valueOf(10L), Integer.valueOf(10));
        assertGivesConcurrentModificationException(itr);

        itr = btr.iterator();
        assertTrue(itr.hasNext());
        btr.delete(10L);
        assertGivesConcurrentModificationException(itr);

        itr = btr.iterator();
        assertTrue(itr.hasNext());
        if (btr.compact())
        {
          assertGivesConcurrentModificationException(itr);
        }

        itr = btr.iterator();
        assertTrue(itr.hasNext());
        btr.replace(2L, 20);
        assertGivesConcurrentModificationException(itr);

        itr = btr.iterator();
        assertTrue(itr.hasNext());
        btr.clear();
        assertGivesConcurrentModificationException(itr);

        itr = btr.iterator();
      }
      finally
      {
        btr.close();

        try
        {
          itr.hasNext();
          fail();
        }
        catch (IllegalStateException e)
        {
          // ok
        }

        try
        {
          itr = btr.iterator();
          fail();
        }
        catch (IllegalStateException e)
        {
          // ok
        }
      }
      assertJunkUntouched(f, barr);
    }
    finally
    {
      assertTrue(tmpFile.delete());
    }
  }

  @Test
  public void testIteratorForEmptyTree()
  {
    File tmpFile = FileSupport.createTempFile();
    try
    {
      ReadWritableFile f = new ReadWritableFileAdapter(tmpFile);
      byte[] barr = writeJunk(f);
      Iterator<Record<Long, Integer>> itr = null;
      BPlusTree<Long, Integer> btr = new BPlusTree<Long, Integer>(new FileBackedNodeRepository<Long, Integer>(f, false, f.getSize(), new NumberOfRecordsNodeSizeStrategy(2), true, LongNullSerializer.INSTANCE, IntegerSerializer.INSTANCE, 3, 8192, null, new LogAdapterHolder(new StdOutLogAdapter())), new LogAdapterHolder(new StdOutLogAdapter()));
      try
      {
        itr = btr.iterator();
        assertFalse(itr.hasNext());
        try
        {
          itr.next();
          fail();
        }
        catch (NoSuchElementException e)
        {
          // ok
        }
      }
      finally
      {
        btr.close();
      }
      assertJunkUntouched(f, barr);
    }
    finally
    {
      assertTrue(tmpFile.delete());
    }

  }

  @Test
  public void testClear()
  {
    File tmpFile = FileSupport.createTempFile();
    try
    {
      ReadWritableFile f = new ReadWritableFileAdapter(tmpFile);
      byte[] barr = writeJunk(f);
      BPlusTree<Long, Integer> btr = new BPlusTree<Long, Integer>(new FileBackedNodeRepository<Long, Integer>(f, false, f.getSize(), new NumberOfRecordsNodeSizeStrategy(2), true, LongNullSerializer.INSTANCE, IntegerSerializer.INSTANCE, 3, 8192, null, new LogAdapterHolder(new StdOutLogAdapter())), new LogAdapterHolder(new StdOutLogAdapter()));
      try
      {
        btr.insert(12L, 24);
        btr.insert(24L, 12);
        assertEquals(2, btr.size());

        btr.clear();
        assertEquals(0, btr.size());
        assertFalse(btr.iterator().hasNext());

        btr.insert(13L, 23);
        assertEquals(1, btr.size());
      }
      finally
      {
        btr.close();

        try
        {
          btr.clear();
          fail();
        }
        catch (IllegalStateException e)
        {
          // ok
        }
      }
      assertJunkUntouched(f, barr);
    }
    finally
    {
      assertTrue(tmpFile.delete());
    }
  }

  @Test
  public void testSize()
  {
    File tmpFile = FileSupport.createTempFile();
    try
    {
      ReadWritableFile f = new ReadWritableFileAdapter(tmpFile);
      byte[] barr = writeJunk(f);
      BPlusTree<Long, Integer> btr = new BPlusTree<Long, Integer>(new FileBackedNodeRepository<Long, Integer>(f, false, f.getSize(), new NumberOfRecordsNodeSizeStrategy(2), true, LongNullSerializer.INSTANCE, IntegerSerializer.INSTANCE, 3, 8192, null, new LogAdapterHolder(new StdOutLogAdapter())), new LogAdapterHolder(new StdOutLogAdapter()));
      try
      {
        assertEquals(0, btr.size());
        btr.insert(12L, 24);
        btr.insert(24L, 12);
        assertEquals(2, btr.size());

        // There are other size-tests spread out among the other test
        // methods.
      }
      finally
      {
        btr.close();

        try
        {
          btr.size();
          fail();
        }
        catch (IllegalStateException e)
        {
          // ok
        }
      }
      assertJunkUntouched(f, barr);
    }
    finally
    {
      assertTrue(tmpFile.delete());
    }
  }

  @Test
  public void testDeleteMissingKey()
  {
    File tmpFile = FileSupport.createTempFile();
    try
    {
      ReadWritableFile f = new ReadWritableFileAdapter(tmpFile);
      byte[] barr = writeJunk(f);
      BPlusTree<Long, Long> btr = new BPlusTree<Long, Long>(new FileBackedNodeRepository<Long, Long>(f, false, f.getSize(), new NumberOfRecordsNodeSizeStrategy(2), false, LongNullSerializer.INSTANCE, LongSerializer.INSTANCE, 8, 8192, null, new LogAdapterHolder(new StdOutLogAdapter())), new LogAdapterHolder(new StdOutLogAdapter()));
      try
      {
        assertNull(btr.delete(Long.valueOf(1)));
        btr.insert(Long.valueOf(1), Long.valueOf(2));
        assertEquals(Long.valueOf(2), btr.delete(Long.valueOf(1)));
        assertNull(btr.delete(Long.valueOf(1)));
      }
      finally
      {
        btr.close();
      }
      assertJunkUntouched(f, barr);
    }
    finally
    {
      assertTrue(tmpFile.delete());
    }
  }

  @Test
  public void testInsertNullValue()
  {
    File tmpFile = FileSupport.createTempFile();
    try
    {
      ReadWritableFile f = new ReadWritableFileAdapter(tmpFile);
      byte[] barr = writeJunk(f);
      // A tree that should support null values.
      BPlusTree<Long, Long> btr = new BPlusTree<Long, Long>(new FileBackedNodeRepository<Long, Long>(f, false, f.getSize(), new NumberOfRecordsNodeSizeStrategy(2), false, LongNullSerializer.INSTANCE, LongNullSerializer.INSTANCE, 8, 8192, null, new LogAdapterHolder(new StdOutLogAdapter())), new LogAdapterHolder(new StdOutLogAdapter()));
      try
      {
        btr.insert(12L, null);
        assertNull(btr.find(12L));
        assertTrue(btr.containsKey(12L));
        assertEquals(12L, btr.findRecord(12L, SearchMode.EXACT_MATCH).getKey().longValue());
        btr.delete(12L);
      }
      finally
      {
        btr.close();
      }
      assertJunkUntouched(f, barr);
    }
    finally
    {
      assertTrue(tmpFile.delete());
    }

    tmpFile = FileSupport.createTempFile();
    try
    {
      ReadWritableFile f = new ReadWritableFileAdapter(tmpFile);
      byte[] barr = writeJunk(f);
      // A tree that does not support null values
      BPlusTree<Long, Long> btr = new BPlusTree<Long, Long>(new FileBackedNodeRepository<Long, Long>(f, false, f.getSize(), new NumberOfRecordsNodeSizeStrategy(2), false, LongNullSerializer.INSTANCE, LongSerializer.INSTANCE, 8, 8192, null, new LogAdapterHolder(new StdOutLogAdapter())), new LogAdapterHolder(new StdOutLogAdapter()));
      try
      {
        btr.insert(Long.valueOf(12L), null);
        fail();
      }
      catch (NullPointerException e)
      {
        // ok
      }
      finally
      {
        btr.close();
      }
      assertJunkUntouched(f, barr);
    }
    finally
    {
      assertTrue(tmpFile.delete());
    }
  }

  private <K extends Comparable<K>, V> void testLargeBTreeInternal(NodeRepository<K> nr, boolean compact, ValueConverter<K> kconv, ValueConverter<V> vconv, int noRecords)
  {
    BPlusTree<K, V> btr = new BPlusTree<K, V>(nr, new LogAdapterHolder(new StdOutLogAdapter()));
    try
    {
      List<KeyAndValue<K, V>> records = createInterval(1, noRecords, kconv, vconv);
      insertTestRecords(btr, records);
      assertEquals(noRecords, btr.size());

      btr.verifyTreeIntegrity();
      verifyNextAndPreviousRecords(btr, records);

      int previ1 = 2;
      int previ2 = 1;
      for (int i = 2; i < noRecords / 10; i = previ1 + previ2)
      {
        // Remove every i:th record
        for (int j = 1; j <= noRecords; j += i)
        {
          try
          {
            assertNotNull(btr.delete(kconv.convert(j)));
          }
          catch (RuntimeException e)
          {
            System.out.println("delete " + i + ":" + j);
            throw e;
          }

          assertTrue(records.remove(new KeyAndValue<K, V>(kconv.convert(j), vconv.convert(j))));

          if ((j - 1) % (i * 1000) == 0)
          {
            try
            {
              if (compact)
              {
                btr.verifyTreeIntegrity();
                // System.out.println(btr.printTree());
                btr.compact();
                // System.out.println(btr.printTree());
              }
              btr.verifyTreeIntegrity();
            }
            catch (RuntimeException e)
            {
              System.out.println("delete* " + i + ":" + j);
              throw e;
            }
          }
        }

        if (compact)
        {
          btr.compact();
          btr.verifyTreeIntegrity();
        }

        verifyNextAndPreviousRecords(btr, records);

        // Insert them again
        for (int j = 1; j <= noRecords; j += i)
        {
          try
          {
            btr.insert(kconv.convert(j), vconv.convert(j));
          }
          catch (RuntimeException e)
          {
            System.out.println("insert " + i + ":" + j);
            throw e;
          }

          records.add(new KeyAndValue<K, V>(kconv.convert(j), vconv.convert(j)));

          if ((j - 1) % (i * 1000) == 0)
          {
            try
            {
              btr.verifyTreeIntegrity();
            }
            catch (RuntimeException e)
            {
              System.out.println("insert* " + i + ":" + j);
              throw e;
            }
          }
        }

        btr.verifyTreeIntegrity();
        verifyNextAndPreviousRecords(btr, records);

        previ2 = previ1;
        previ1 = i;
      }
    }
    finally
    {
      btr.close();
    }
  }

  private <K extends Comparable<K>, V> void testLargeBTreeInternal(Serializer<K> keySerializer, ValueConverter<K> kconv, Serializer<V> valueSerializer, ValueConverter<V> vconv, int cacheSize, int pointerSize, boolean compact, boolean leafNodePointers)
  {
    final int noRecords = 10000;
    File tmpFile = FileSupport.createTempFile();
    try
    {
      ReadWritableFile f = new ReadWritableFileAdapter(tmpFile);
      byte[] barr = writeJunk(f);
      NodeRepository<K> nr = new FileBackedNodeRepository<K, V>(f, false, f.getSize(), new NumberOfRecordsNodeSizeStrategy(8), leafNodePointers, keySerializer, valueSerializer, pointerSize, 8192, null, new LogAdapterHolder(new StdOutLogAdapter()));
      if (cacheSize > 0)
      {
        nr = new LruCacheNodeRepository<K, V>(nr, cacheSize);
      }
      testLargeBTreeInternal(nr, compact, kconv, vconv, noRecords);
      assertJunkUntouched(f, barr);
    }
    finally
    {
      assertTrue(tmpFile.delete());
    }

  }

  private void testLargeBTreeInternal(int cacheSize, boolean compact)
  {
    int[] pointerSizes = new int[] { 4, 7 };
    for (int pointerSize : pointerSizes)
    {
      for (boolean lnp : BOOLEANS)
      {
        testLargeBTreeInternal(LongNullSerializer.INSTANCE, LONG_CONVERTER, LongSerializer.INSTANCE, LONG_CONVERTER, cacheSize, pointerSize, compact, lnp);
        testLargeBTreeInternal(LongNullSerializer.INSTANCE, LONG_CONVERTER, IntegerSerializer.INSTANCE, INTEGER_CONVERTER, cacheSize, pointerSize, compact, lnp);
        testLargeBTreeInternal(IntegerNullSerializer.INSTANCE, INTEGER_CONVERTER, LongSerializer.INSTANCE, LONG_CONVERTER, cacheSize, pointerSize, compact, lnp);
        testLargeBTreeInternal(IntegerNullSerializer.INSTANCE, INTEGER_CONVERTER, IntegerSerializer.INSTANCE, INTEGER_CONVERTER, cacheSize, pointerSize, compact, lnp);
      }
    }
  }

  private void testLargeBTreeWithTooBigNodesInternal(boolean compact)
  {
    File tmpFile = FileSupport.createTempFile();
    try
    {
      ReadWritableFile f = new ReadWritableFileAdapter(tmpFile);
      byte[] barr = writeJunk(f);
      // 8 records per node + 1 extra byte
      NodeRepository<Long> nr = new FileBackedNodeRepository<Long, Long>(f, false, f.getSize(), new FixedSizeNodeSizeStrategy(new NumberOfRecordsNodeSizeStrategy(8).getNodeSize(FileBackedNodeRepository.NODE_HEADER_SIZE, 16, 0) + 1), false, LongNullSerializer.INSTANCE, LongSerializer.INSTANCE, 7, 8192, null, new LogAdapterHolder(new StdOutLogAdapter()));
      testLargeBTreeInternal(nr, compact, LONG_CONVERTER, LONG_CONVERTER, 10000);
      assertJunkUntouched(f, barr);
    }
    finally
    {
      assertTrue(tmpFile.delete());
    }
  }

  @Test
  public void testLargeBTreeWithTooBigNodes()
  {
    testLargeBTreeWithTooBigNodesInternal(false);
  }

  @Test
  public void testLargeBTreeWithTooBigNodesAndCompact()
  {
    testLargeBTreeWithTooBigNodesInternal(true);
  }

  @Test
  public void testLargeBTree()
  {
    testLargeBTreeInternal(0, false);
  }

  @Test
  public void testLargeBTreeWithCompact()
  {
    testLargeBTreeInternal(0, true);
  }

  @Test
  public void testLargeBTreeWithSmallLruCache()
  {
    testLargeBTreeInternal(4, false);
  }

  @Test
  public void testLargeBTreeWithLargeLruCache()
  {
    testLargeBTreeInternal(30, false);
  }

  @Test
  public void testLargeBTreeWithCompactAndSmallLruCache()
  {
    testLargeBTreeInternal(4, true);
  }

  @Test
  public void testLargeBTreeWithCompactAndLargeLruCache()
  {
    testLargeBTreeInternal(30, true);
  }

  @Test
  public void testHugeBTreeWithCompactAndLargeLruCache()
  {
    File tmpFile = FileSupport.createTempFile();
    try
    {
      ReadWritableFile f = new ReadWritableFileAdapter(tmpFile);
      byte[] barr = writeJunk(f);
      NodeRepository<Integer> nr = new FileBackedNodeRepository<Integer, Integer>(f, false, f.getSize(), new NumberOfRecordsNodeSizeStrategy(8), true, IntegerNullSerializer.INSTANCE, IntegerSerializer.INSTANCE, 6, 8192, null, new LogAdapterHolder(new StdOutLogAdapter()));
      testLargeBTreeInternal(nr, true, INTEGER_CONVERTER, INTEGER_CONVERTER, 200000);
      assertJunkUntouched(f, barr);
    }
    finally
    {
      assertTrue(tmpFile.delete());
    }
  }
}
TOP

Related Classes of org.helidb.util.bplus.BPlusTreeTest

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.