[ASTERIXDB-2204][STO] Fix implementations and usages of IIndexCursor

- user model changes: no
- storage format changes: no
- interface changes: yes
  - IIndexCursor.close() is now idempotent and can be called on
    a closed cursor.
  - IIndexCursor.destroy() is now idempotent and can be called
    on a destroyed cursor.
  - Add IIndexAccessor.destroy() letting the accessor know it is
    safe to destroy its reusable cursors and operation contexts.
  - Add IIndexOperationContext.destroy() letting the context
    know that the user is done with it and allow it to release
    resources

details:
- Previously, implementations of the IIndexCursor interface
  didn't enforce the interface contract. This change enforces
  the contract for all the implementations.
- With the enforcement of the contract, all the users of the
  cursors are expected to follow and enforce the expected lifecycle.
- Test cases were added.

Change-Id: I98a7a8b931eb24dbe11bf2bdc61b754ca28ebdf9
Reviewed-on: https://asterix-gerrit.ics.uci.edu/2324
Reviewed-by: Michael Blow <mblow@apache.org>
Tested-by: Jenkins <jenkins@fulliautomatix.ics.uci.edu>
Contrib: Jenkins <jenkins@fulliautomatix.ics.uci.edu>
Integration-Tests: Jenkins <jenkins@fulliautomatix.ics.uci.edu>
diff --git a/hyracks-fullstack/hyracks/hyracks-tests/hyracks-storage-am-btree-test/pom.xml b/hyracks-fullstack/hyracks/hyracks-tests/hyracks-storage-am-btree-test/pom.xml
index b618834..45d6989 100644
--- a/hyracks-fullstack/hyracks/hyracks-tests/hyracks-storage-am-btree-test/pom.xml
+++ b/hyracks-fullstack/hyracks/hyracks-tests/hyracks-storage-am-btree-test/pom.xml
@@ -67,6 +67,13 @@
     </dependency>
     <dependency>
       <groupId>org.apache.hyracks</groupId>
+      <artifactId>hyracks-storage-am-common</artifactId>
+      <version>${project.version}</version>
+      <type>test-jar</type>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.hyracks</groupId>
       <artifactId>hyracks-api</artifactId>
       <version>${project.version}</version>
     </dependency>
diff --git a/hyracks-fullstack/hyracks/hyracks-tests/hyracks-storage-am-btree-test/src/test/java/org/apache/hyracks/storage/am/btree/BTreeCountingCursorTest.java b/hyracks-fullstack/hyracks/hyracks-tests/hyracks-storage-am-btree-test/src/test/java/org/apache/hyracks/storage/am/btree/BTreeCountingCursorTest.java
new file mode 100644
index 0000000..291ab8a
--- /dev/null
+++ b/hyracks-fullstack/hyracks/hyracks-tests/hyracks-storage-am-btree-test/src/test/java/org/apache/hyracks/storage/am/btree/BTreeCountingCursorTest.java
@@ -0,0 +1,106 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.hyracks.storage.am.btree;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.TreeSet;
+
+import org.apache.hyracks.api.exceptions.HyracksDataException;
+import org.apache.hyracks.storage.am.btree.impls.BTree;
+import org.apache.hyracks.storage.am.btree.impls.BTree.BTreeAccessor;
+import org.apache.hyracks.storage.am.btree.util.BTreeTestHarness;
+import org.apache.hyracks.storage.am.common.TestOperationCallback;
+import org.apache.hyracks.storage.am.common.api.IMetadataPageManager;
+import org.apache.hyracks.storage.am.common.freepage.LinkedMetaDataPageManager;
+import org.apache.hyracks.storage.am.common.impls.IndexAccessParameters;
+import org.apache.hyracks.storage.am.common.test.IIndexCursorTest;
+import org.apache.hyracks.storage.common.IIndexAccessor;
+import org.apache.hyracks.storage.common.IIndexCursor;
+import org.apache.hyracks.storage.common.ISearchPredicate;
+import org.apache.hyracks.storage.common.buffercache.IBufferCache;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+
+public class BTreeCountingCursorTest extends IIndexCursorTest {
+    private static final BTreeTestHarness harness = new BTreeTestHarness();
+    private static BTree btree;
+
+    @BeforeClass
+    public static void setup() throws HyracksDataException {
+        harness.setUp();
+        IBufferCache bufferCache = harness.getBufferCache();
+        IMetadataPageManager freePageManager =
+                new LinkedMetaDataPageManager(bufferCache, BTreeSearchCursorTest.META_FRAME_FACTORY);
+        btree = new BTree(bufferCache, freePageManager, BTreeSearchCursorTest.INTERIOR_FRAME_FACTORY,
+                BTreeSearchCursorTest.LEAF_FRAME_FACTORY, BTreeSearchCursorTest.CMP_FACTORIES,
+                BTreeSearchCursorTest.FIELD_COUNT, harness.getFileReference());
+        btree.create();
+        btree.activate();
+        // generate keys
+        int numKeys = 50;
+        int maxKey = 1000;
+        TreeSet<Integer> uniqueKeys = new TreeSet<>();
+        ArrayList<Integer> keys = new ArrayList<>();
+        while (uniqueKeys.size() < numKeys) {
+            int key = BTreeSearchCursorTest.RANDOM.nextInt() % maxKey;
+            uniqueKeys.add(key);
+        }
+        for (Integer i : uniqueKeys) {
+            keys.add(i);
+        }
+        BTreeSearchCursorTest.staticInsertBTree(keys, btree);
+    }
+
+    @AfterClass
+    public static void tearDown() throws HyracksDataException {
+        try {
+            btree.deactivate();
+            btree.destroy();
+        } finally {
+            harness.tearDown();
+        }
+    }
+
+    @Override
+    protected List<ISearchPredicate> createSearchPredicates() throws HyracksDataException {
+        List<ISearchPredicate> predicates = new ArrayList<>();
+        int minKey = -10;
+        int maxKey = 10;
+        for (int i = minKey; i < maxKey; i++) {
+            for (int j = minKey; j < maxKey; j++) {
+                int lowKey = i;
+                int highKey = j;
+                predicates.add(BTreeSearchCursorTest.createRangePredicate(lowKey, highKey, true, true));
+            }
+        }
+        return predicates;
+    }
+
+    @Override
+    protected IIndexAccessor createAccessor() throws Exception {
+        return btree.createAccessor(
+                new IndexAccessParameters(TestOperationCallback.INSTANCE, TestOperationCallback.INSTANCE));
+    }
+
+    @Override
+    protected IIndexCursor createCursor(IIndexAccessor accessor) {
+        return ((BTreeAccessor) accessor).createCountingSearchCursor();
+    }
+}
diff --git a/hyracks-fullstack/hyracks/hyracks-tests/hyracks-storage-am-btree-test/src/test/java/org/apache/hyracks/storage/am/btree/BTreeRangeSearchCursorTest.java b/hyracks-fullstack/hyracks/hyracks-tests/hyracks-storage-am-btree-test/src/test/java/org/apache/hyracks/storage/am/btree/BTreeRangeSearchCursorTest.java
new file mode 100644
index 0000000..73a9bb6
--- /dev/null
+++ b/hyracks-fullstack/hyracks/hyracks-tests/hyracks-storage-am-btree-test/src/test/java/org/apache/hyracks/storage/am/btree/BTreeRangeSearchCursorTest.java
@@ -0,0 +1,100 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.hyracks.storage.am.btree;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.TreeSet;
+
+import org.apache.hyracks.api.exceptions.HyracksDataException;
+import org.apache.hyracks.storage.am.btree.impls.BTree;
+import org.apache.hyracks.storage.am.btree.util.BTreeTestHarness;
+import org.apache.hyracks.storage.am.common.TestOperationCallback;
+import org.apache.hyracks.storage.am.common.api.IMetadataPageManager;
+import org.apache.hyracks.storage.am.common.freepage.LinkedMetaDataPageManager;
+import org.apache.hyracks.storage.am.common.impls.IndexAccessParameters;
+import org.apache.hyracks.storage.am.common.test.IIndexCursorTest;
+import org.apache.hyracks.storage.common.IIndexAccessor;
+import org.apache.hyracks.storage.common.ISearchPredicate;
+import org.apache.hyracks.storage.common.buffercache.IBufferCache;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+
+public class BTreeRangeSearchCursorTest extends IIndexCursorTest {
+    private static final BTreeTestHarness harness = new BTreeTestHarness();
+    private static BTree btree;
+
+    @BeforeClass
+    public static void setup() throws HyracksDataException {
+        harness.setUp();
+        IBufferCache bufferCache = harness.getBufferCache();
+        IMetadataPageManager freePageManager =
+                new LinkedMetaDataPageManager(bufferCache, BTreeSearchCursorTest.META_FRAME_FACTORY);
+        btree = new BTree(bufferCache, freePageManager, BTreeSearchCursorTest.INTERIOR_FRAME_FACTORY,
+                BTreeSearchCursorTest.LEAF_FRAME_FACTORY, BTreeSearchCursorTest.CMP_FACTORIES,
+                BTreeSearchCursorTest.FIELD_COUNT, harness.getFileReference());
+        btree.create();
+        btree.activate();
+        // generate keys
+        int numKeys = 50;
+        int maxKey = 1000;
+        TreeSet<Integer> uniqueKeys = new TreeSet<>();
+        ArrayList<Integer> keys = new ArrayList<>();
+        while (uniqueKeys.size() < numKeys) {
+            int key = BTreeSearchCursorTest.RANDOM.nextInt() % maxKey;
+            uniqueKeys.add(key);
+        }
+        for (Integer i : uniqueKeys) {
+            keys.add(i);
+        }
+        BTreeSearchCursorTest.staticInsertBTree(keys, btree);
+    }
+
+    @AfterClass
+    public static void tearDown() throws HyracksDataException {
+        try {
+            btree.deactivate();
+            btree.destroy();
+        } finally {
+            harness.tearDown();
+        }
+    }
+
+    @Override
+    protected List<ISearchPredicate> createSearchPredicates() throws HyracksDataException {
+        List<ISearchPredicate> predicates = new ArrayList<>();
+        int minKey = -10;
+        int maxKey = 10;
+        for (int i = minKey; i < maxKey; i++) {
+            for (int j = minKey; j < maxKey; j++) {
+                int lowKey = i;
+                int highKey = j;
+                predicates.add(BTreeSearchCursorTest.createRangePredicate(lowKey, highKey, true, true));
+            }
+        }
+        return predicates;
+    }
+
+    @Override
+    protected IIndexAccessor createAccessor() throws Exception {
+        IndexAccessParameters actx =
+                new IndexAccessParameters(TestOperationCallback.INSTANCE, TestOperationCallback.INSTANCE);
+        return btree.createAccessor(actx);
+    }
+}
diff --git a/hyracks-fullstack/hyracks/hyracks-tests/hyracks-storage-am-btree-test/src/test/java/org/apache/hyracks/storage/am/btree/BTreeSearchCursorTest.java b/hyracks-fullstack/hyracks/hyracks-tests/hyracks-storage-am-btree-test/src/test/java/org/apache/hyracks/storage/am/btree/BTreeSearchCursorTest.java
index 68a3984..ff28470 100644
--- a/hyracks-fullstack/hyracks/hyracks-tests/hyracks-storage-am-btree-test/src/test/java/org/apache/hyracks/storage/am/btree/BTreeSearchCursorTest.java
+++ b/hyracks-fullstack/hyracks/hyracks-tests/hyracks-storage-am-btree-test/src/test/java/org/apache/hyracks/storage/am/btree/BTreeSearchCursorTest.java
@@ -60,48 +60,34 @@
 import org.apache.hyracks.storage.common.MultiComparator;
 import org.apache.hyracks.storage.common.buffercache.IBufferCache;
 import org.junit.Assert;
-import org.junit.Before;
 import org.junit.Test;
 
 public class BTreeSearchCursorTest extends AbstractBTreeTest {
-    protected final int fieldCount = 2;
-    protected final ITypeTraits[] typeTraits = new ITypeTraits[fieldCount];
-    protected final BTreeTypeAwareTupleWriterFactory tupleWriterFactory =
-            new BTreeTypeAwareTupleWriterFactory(typeTraits, false);
-    protected final ITreeIndexMetadataFrameFactory metaFrameFactory = new LIFOMetaDataFrameFactory();
-    protected final Random rnd = new Random(50);
-
-    @Override
-    @Before
-    public void setUp() throws HyracksDataException {
-        super.setUp();
-        typeTraits[0] = IntegerPointable.TYPE_TRAITS;
-        typeTraits[1] = IntegerPointable.TYPE_TRAITS;
-    }
+    public static final int FIELD_COUNT = 2;
+    public static final ITypeTraits[] TYPE_TRAITS = { IntegerPointable.TYPE_TRAITS, IntegerPointable.TYPE_TRAITS };
+    public static final BTreeTypeAwareTupleWriterFactory TUPLE_WRITER_FACTORY =
+            new BTreeTypeAwareTupleWriterFactory(TYPE_TRAITS, false);
+    public static final ITreeIndexMetadataFrameFactory META_FRAME_FACTORY = new LIFOMetaDataFrameFactory();
+    public static final int KEY_FIELDS_COUNT = 1;
+    public static final IBinaryComparatorFactory[] CMP_FACTORIES =
+            { PointableBinaryComparatorFactory.of(IntegerPointable.FACTORY) };
+    public static final ITreeIndexFrameFactory LEAF_FRAME_FACTORY = new BTreeNSMLeafFrameFactory(TUPLE_WRITER_FACTORY);
+    public static final ITreeIndexFrameFactory INTERIOR_FRAME_FACTORY =
+            new BTreeNSMInteriorFrameFactory(TUPLE_WRITER_FACTORY);
+    public static final Random RANDOM = new Random(50);
 
     @Test
     public void uniqueIndexTest() throws Exception {
         if (LOGGER.isInfoEnabled()) {
             LOGGER.info("TESTING RANGE SEARCH CURSOR ON UNIQUE INDEX");
         }
-
         IBufferCache bufferCache = harness.getBufferCache();
-
         // declare keys
-        int keyFieldCount = 1;
-        IBinaryComparatorFactory[] cmpFactories = new IBinaryComparatorFactory[keyFieldCount];
-        cmpFactories[0] = PointableBinaryComparatorFactory.of(IntegerPointable.FACTORY);
-
-        ITreeIndexFrameFactory leafFrameFactory = new BTreeNSMLeafFrameFactory(tupleWriterFactory);
-        ITreeIndexFrameFactory interiorFrameFactory = new BTreeNSMInteriorFrameFactory(tupleWriterFactory);
-
-        IBTreeLeafFrame leafFrame = (IBTreeLeafFrame) leafFrameFactory.createFrame();
-        IBTreeInteriorFrame interiorFrame = (IBTreeInteriorFrame) interiorFrameFactory.createFrame();
-
-        IMetadataPageManager freePageManager = new LinkedMetaDataPageManager(bufferCache, metaFrameFactory);
-
-        BTree btree = new BTree(bufferCache, freePageManager, interiorFrameFactory, leafFrameFactory, cmpFactories,
-                fieldCount, harness.getFileReference());
+        IBTreeLeafFrame leafFrame = (IBTreeLeafFrame) LEAF_FRAME_FACTORY.createFrame();
+        IBTreeInteriorFrame interiorFrame = (IBTreeInteriorFrame) INTERIOR_FRAME_FACTORY.createFrame();
+        IMetadataPageManager freePageManager = new LinkedMetaDataPageManager(bufferCache, META_FRAME_FACTORY);
+        BTree btree = new BTree(bufferCache, freePageManager, INTERIOR_FRAME_FACTORY, LEAF_FRAME_FACTORY, CMP_FACTORIES,
+                FIELD_COUNT, harness.getFileReference());
         btree.create();
         btree.activate();
 
@@ -111,7 +97,7 @@
         TreeSet<Integer> uniqueKeys = new TreeSet<>();
         ArrayList<Integer> keys = new ArrayList<>();
         while (uniqueKeys.size() < numKeys) {
-            int key = rnd.nextInt() % maxKey;
+            int key = RANDOM.nextInt() % maxKey;
             uniqueKeys.add(key);
         }
         for (Integer i : uniqueKeys) {
@@ -151,16 +137,16 @@
         cmpFactories[0] = PointableBinaryComparatorFactory.of(IntegerPointable.FACTORY);
         cmpFactories[1] = PointableBinaryComparatorFactory.of(IntegerPointable.FACTORY);
 
-        ITreeIndexFrameFactory leafFrameFactory = new BTreeNSMLeafFrameFactory(tupleWriterFactory);
-        ITreeIndexFrameFactory interiorFrameFactory = new BTreeNSMInteriorFrameFactory(tupleWriterFactory);
+        ITreeIndexFrameFactory leafFrameFactory = new BTreeNSMLeafFrameFactory(TUPLE_WRITER_FACTORY);
+        ITreeIndexFrameFactory interiorFrameFactory = new BTreeNSMInteriorFrameFactory(TUPLE_WRITER_FACTORY);
 
         IBTreeLeafFrame leafFrame = (IBTreeLeafFrame) leafFrameFactory.createFrame();
         IBTreeInteriorFrame interiorFrame = (IBTreeInteriorFrame) interiorFrameFactory.createFrame();
 
-        IMetadataPageManager freePageManager = new LinkedMetaDataPageManager(bufferCache, metaFrameFactory);
+        IMetadataPageManager freePageManager = new LinkedMetaDataPageManager(bufferCache, META_FRAME_FACTORY);
 
         BTree btree = new BTree(bufferCache, freePageManager, interiorFrameFactory, leafFrameFactory, cmpFactories,
-                fieldCount, harness.getFileReference());
+                FIELD_COUNT, harness.getFileReference());
         btree.create();
         btree.activate();
 
@@ -169,7 +155,7 @@
         int maxKey = 10;
         ArrayList<Integer> keys = new ArrayList<>();
         for (int i = 0; i < numKeys; i++) {
-            int k = rnd.nextInt() % maxKey;
+            int k = RANDOM.nextInt() % maxKey;
             keys.add(k);
         }
         Collections.sort(keys);
@@ -207,16 +193,16 @@
         cmpFactories[0] = PointableBinaryComparatorFactory.of(IntegerPointable.FACTORY);
         cmpFactories[1] = PointableBinaryComparatorFactory.of(IntegerPointable.FACTORY);
 
-        ITreeIndexFrameFactory leafFrameFactory = new BTreeNSMLeafFrameFactory(tupleWriterFactory);
-        ITreeIndexFrameFactory interiorFrameFactory = new BTreeNSMInteriorFrameFactory(tupleWriterFactory);
+        ITreeIndexFrameFactory leafFrameFactory = new BTreeNSMLeafFrameFactory(TUPLE_WRITER_FACTORY);
+        ITreeIndexFrameFactory interiorFrameFactory = new BTreeNSMInteriorFrameFactory(TUPLE_WRITER_FACTORY);
 
         IBTreeLeafFrame leafFrame = (IBTreeLeafFrame) leafFrameFactory.createFrame();
         IBTreeInteriorFrame interiorFrame = (IBTreeInteriorFrame) interiorFrameFactory.createFrame();
 
-        IMetadataPageManager freePageManager = new LinkedMetaDataPageManager(bufferCache, metaFrameFactory);
+        IMetadataPageManager freePageManager = new LinkedMetaDataPageManager(bufferCache, META_FRAME_FACTORY);
 
         BTree btree = new BTree(bufferCache, freePageManager, interiorFrameFactory, leafFrameFactory, cmpFactories,
-                fieldCount, harness.getFileReference());
+                FIELD_COUNT, harness.getFileReference());
         btree.create();
         btree.activate();
         // generate keys
@@ -224,7 +210,7 @@
         int maxKey = 10;
         ArrayList<Integer> keys = new ArrayList<>();
         for (int i = 0; i < numKeys; i++) {
-            int k = rnd.nextInt() % maxKey;
+            int k = RANDOM.nextInt() % maxKey;
             keys.add(k);
         }
         Collections.sort(keys);
@@ -248,7 +234,7 @@
         btree.destroy();
     }
 
-    public RangePredicate createRangePredicate(int lk, int hk, boolean lowKeyInclusive, boolean highKeyInclusive)
+    public static RangePredicate createRangePredicate(int lk, int hk, boolean lowKeyInclusive, boolean highKeyInclusive)
             throws HyracksDataException {
         // create tuplereferences for search keys
         ITupleReference lowKey = TupleUtils.createIntegerTuple(false, lk);
@@ -290,43 +276,38 @@
     public boolean performSearches(ArrayList<Integer> keys, BTree btree, IBTreeLeafFrame leafFrame,
             IBTreeInteriorFrame interiorFrame, int minKey, int maxKey, boolean lowKeyInclusive,
             boolean highKeyInclusive, boolean printExpectedResults) throws Exception {
-
         ArrayList<Integer> results = new ArrayList<>();
         ArrayList<Integer> expectedResults = new ArrayList<>();
-
         for (int i = minKey; i < maxKey; i++) {
             for (int j = minKey; j < maxKey; j++) {
                 results.clear();
                 expectedResults.clear();
-
                 int lowKey = i;
                 int highKey = j;
-
                 RangePredicate rangePred = createRangePredicate(lowKey, highKey, lowKeyInclusive, highKeyInclusive);
                 IndexAccessParameters actx =
                         new IndexAccessParameters(TestOperationCallback.INSTANCE, TestOperationCallback.INSTANCE);
                 ITreeIndexAccessor indexAccessor = btree.createAccessor(actx);
                 IIndexCursor rangeCursor = indexAccessor.createSearchCursor(false);
-                indexAccessor.search(rangeCursor, rangePred);
-
                 try {
-                    while (rangeCursor.hasNext()) {
-                        rangeCursor.next();
-                        ITupleReference frameTuple = rangeCursor.getTuple();
-                        ByteArrayInputStream inStream = new ByteArrayInputStream(frameTuple.getFieldData(0),
-                                frameTuple.getFieldStart(0), frameTuple.getFieldLength(0));
-                        DataInput dataIn = new DataInputStream(inStream);
-                        Integer res = IntegerSerializerDeserializer.INSTANCE.deserialize(dataIn);
-                        results.add(res);
+                    indexAccessor.search(rangeCursor, rangePred);
+                    try {
+                        while (rangeCursor.hasNext()) {
+                            rangeCursor.next();
+                            ITupleReference frameTuple = rangeCursor.getTuple();
+                            ByteArrayInputStream inStream = new ByteArrayInputStream(frameTuple.getFieldData(0),
+                                    frameTuple.getFieldStart(0), frameTuple.getFieldLength(0));
+                            DataInput dataIn = new DataInputStream(inStream);
+                            Integer res = IntegerSerializerDeserializer.INSTANCE.deserialize(dataIn);
+                            results.add(res);
+                        }
+                    } finally {
+                        rangeCursor.close();
                     }
-                } catch (Exception e) {
-                    e.printStackTrace();
                 } finally {
                     rangeCursor.destroy();
                 }
-
                 getExpectedResults(expectedResults, keys, lowKey, highKey, lowKeyInclusive, highKeyInclusive);
-
                 if (printExpectedResults) {
                     if (expectedResults.size() > 0) {
                         char l, u;
@@ -381,10 +362,14 @@
     }
 
     protected void insertBTree(List<Integer> keys, BTree btree) throws HyracksDataException {
+        staticInsertBTree(keys, btree);
+    }
+
+    public static void staticInsertBTree(List<Integer> keys, BTree btree) throws HyracksDataException {
         IndexAccessParameters actx =
                 new IndexAccessParameters(TestOperationCallback.INSTANCE, TestOperationCallback.INSTANCE);
         BTreeAccessor accessor = btree.createAccessor(actx);
-        ArrayTupleBuilder tupleBuilder = new ArrayTupleBuilder(fieldCount);
+        ArrayTupleBuilder tupleBuilder = new ArrayTupleBuilder(FIELD_COUNT);
         ArrayTupleReference tuple = new ArrayTupleReference();
 
         // insert keys into btree
diff --git a/hyracks-fullstack/hyracks/hyracks-tests/hyracks-storage-am-btree-test/src/test/java/org/apache/hyracks/storage/am/btree/BTreeUpdateSearchTest.java b/hyracks-fullstack/hyracks/hyracks-tests/hyracks-storage-am-btree-test/src/test/java/org/apache/hyracks/storage/am/btree/BTreeUpdateSearchTest.java
index f32bda3..9a1b6bb 100644
--- a/hyracks-fullstack/hyracks/hyracks-tests/hyracks-storage-am-btree-test/src/test/java/org/apache/hyracks/storage/am/btree/BTreeUpdateSearchTest.java
+++ b/hyracks-fullstack/hyracks/hyracks-tests/hyracks-storage-am-btree-test/src/test/java/org/apache/hyracks/storage/am/btree/BTreeUpdateSearchTest.java
@@ -101,75 +101,81 @@
         IndexAccessParameters actx =
                 new IndexAccessParameters(TestOperationCallback.INSTANCE, TestOperationCallback.INSTANCE);
         ITreeIndexAccessor indexAccessor = btree.createAccessor(actx);
-
-        int numInserts = 10000;
-        for (int i = 0; i < numInserts; i++) {
-            int f0 = rnd.nextInt() % 10000;
-            int f1 = 5;
-            TupleUtils.createIntegerTuple(tb, insertTuple, f0, f1);
-            if (LOGGER.isInfoEnabled()) {
-                if (i % 10000 == 0) {
-                    long end = System.currentTimeMillis();
-                    LOGGER.info("INSERTING " + i + " : " + f0 + " " + f1 + " " + (end - start));
-                }
-            }
-
-            try {
-                indexAccessor.insert(insertTuple);
-            } catch (HyracksDataException hde) {
-                if (hde.getErrorCode() != ErrorCode.DUPLICATE_KEY) {
-                    hde.printStackTrace();
-                    throw hde;
-                }
-            }
-        }
-        long end = System.currentTimeMillis();
-        long duration = end - start;
-        if (LOGGER.isInfoEnabled()) {
-            LOGGER.info("DURATION: " + duration);
-        }
-
-        // Update scan.
-        if (LOGGER.isInfoEnabled()) {
-            LOGGER.info("UPDATE SCAN:");
-        }
-        // Set the cursor to X latch nodes.
-        ITreeIndexCursor updateScanCursor = new BTreeRangeSearchCursor(leafFrame, true);
-        RangePredicate nullPred = new RangePredicate(null, null, true, true, null, null);
-        indexAccessor.search(updateScanCursor, nullPred);
         try {
-            while (updateScanCursor.hasNext()) {
-                updateScanCursor.next();
-                ITupleReference tuple = updateScanCursor.getTuple();
-                // Change the value field.
-                IntegerPointable.setInteger(tuple.getFieldData(1), tuple.getFieldStart(1), 10);
-            }
-        } catch (Exception e) {
-            e.printStackTrace();
-        } finally {
-            updateScanCursor.destroy();
-        }
-
-        // Ordered scan to verify the values.
-        if (LOGGER.isInfoEnabled()) {
-            LOGGER.info("ORDERED SCAN:");
-        }
-        // Set the cursor to X latch nodes.
-        ITreeIndexCursor scanCursor = new BTreeRangeSearchCursor(leafFrame, true);
-        indexAccessor.search(scanCursor, nullPred);
-        try {
-            while (scanCursor.hasNext()) {
-                scanCursor.next();
-                ITupleReference tuple = scanCursor.getTuple();
-                String rec = TupleUtils.printTuple(tuple, recDescSers);
+            int numInserts = 10000;
+            for (int i = 0; i < numInserts; i++) {
+                int f0 = rnd.nextInt() % 10000;
+                int f1 = 5;
+                TupleUtils.createIntegerTuple(tb, insertTuple, f0, f1);
                 if (LOGGER.isInfoEnabled()) {
-                    LOGGER.info(rec);
+                    if (i % 10000 == 0) {
+                        long end = System.currentTimeMillis();
+                        LOGGER.info("INSERTING " + i + " : " + f0 + " " + f1 + " " + (end - start));
+                    }
+                }
+
+                try {
+                    indexAccessor.insert(insertTuple);
+                } catch (HyracksDataException hde) {
+                    if (hde.getErrorCode() != ErrorCode.DUPLICATE_KEY) {
+                        hde.printStackTrace();
+                        throw hde;
+                    }
                 }
             }
-        } catch (Exception e) {
-            e.printStackTrace();
+            long end = System.currentTimeMillis();
+            long duration = end - start;
+            if (LOGGER.isInfoEnabled()) {
+                LOGGER.info("DURATION: " + duration);
+            }
+
+            // Update scan.
+            if (LOGGER.isInfoEnabled()) {
+                LOGGER.info("UPDATE SCAN:");
+            }
+            // Set the cursor to X latch nodes.
+            RangePredicate nullPred = new RangePredicate(null, null, true, true, null, null);
+            ITreeIndexCursor updateScanCursor = new BTreeRangeSearchCursor(leafFrame, true);
+            try {
+                indexAccessor.search(updateScanCursor, nullPred);
+                try {
+                    while (updateScanCursor.hasNext()) {
+                        updateScanCursor.next();
+                        ITupleReference tuple = updateScanCursor.getTuple();
+                        // Change the value field.
+                        IntegerPointable.setInteger(tuple.getFieldData(1), tuple.getFieldStart(1), 10);
+                    }
+                } finally {
+                    updateScanCursor.close();
+                }
+            } finally {
+                updateScanCursor.destroy();
+            }
+            // Ordered scan to verify the values.
+            if (LOGGER.isInfoEnabled()) {
+                LOGGER.info("ORDERED SCAN:");
+            }
+            // Set the cursor to X latch nodes.
+            ITreeIndexCursor scanCursor = new BTreeRangeSearchCursor(leafFrame, true);
+            try {
+                indexAccessor.search(scanCursor, nullPred);
+                try {
+                    while (scanCursor.hasNext()) {
+                        scanCursor.next();
+                        ITupleReference tuple = scanCursor.getTuple();
+                        String rec = TupleUtils.printTuple(tuple, recDescSers);
+                        if (LOGGER.isInfoEnabled()) {
+                            LOGGER.info(rec);
+                        }
+                    }
+                } finally {
+                    scanCursor.close();
+                }
+            } finally {
+                scanCursor.destroy();
+            }
         } finally {
-            scanCursor.destroy();
+            indexAccessor.destroy();
         }
         btree.deactivate();
         btree.destroy();
diff --git a/hyracks-fullstack/hyracks/hyracks-tests/hyracks-storage-am-btree-test/src/test/java/org/apache/hyracks/storage/am/btree/DiskBTreeDiskScanCursorTest.java b/hyracks-fullstack/hyracks/hyracks-tests/hyracks-storage-am-btree-test/src/test/java/org/apache/hyracks/storage/am/btree/DiskBTreeDiskScanCursorTest.java
new file mode 100644
index 0000000..8a27198
--- /dev/null
+++ b/hyracks-fullstack/hyracks/hyracks-tests/hyracks-storage-am-btree-test/src/test/java/org/apache/hyracks/storage/am/btree/DiskBTreeDiskScanCursorTest.java
@@ -0,0 +1,115 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.hyracks.storage.am.btree;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.TreeSet;
+
+import org.apache.hyracks.api.exceptions.HyracksDataException;
+import org.apache.hyracks.storage.am.btree.impls.BTree.BTreeAccessor;
+import org.apache.hyracks.storage.am.btree.impls.DiskBTree;
+import org.apache.hyracks.storage.am.btree.util.BTreeTestHarness;
+import org.apache.hyracks.storage.am.common.TestOperationCallback;
+import org.apache.hyracks.storage.am.common.api.IMetadataPageManager;
+import org.apache.hyracks.storage.am.common.api.ITreeIndexCursor;
+import org.apache.hyracks.storage.am.common.freepage.LinkedMetaDataPageManager;
+import org.apache.hyracks.storage.am.common.impls.IndexAccessParameters;
+import org.apache.hyracks.storage.am.common.test.IIndexCursorTest;
+import org.apache.hyracks.storage.common.IIndexAccessor;
+import org.apache.hyracks.storage.common.IIndexCursor;
+import org.apache.hyracks.storage.common.ISearchPredicate;
+import org.apache.hyracks.storage.common.buffercache.IBufferCache;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+
+public class DiskBTreeDiskScanCursorTest extends IIndexCursorTest {
+    private static final BTreeTestHarness harness = new BTreeTestHarness();
+    private static DiskBTree btree;
+
+    @BeforeClass
+    public static void setup() throws HyracksDataException {
+        harness.setUp();
+        IBufferCache bufferCache = harness.getBufferCache();
+        IMetadataPageManager freePageManager =
+                new LinkedMetaDataPageManager(bufferCache, BTreeSearchCursorTest.META_FRAME_FACTORY);
+        btree = new DiskBTree(bufferCache, freePageManager, BTreeSearchCursorTest.INTERIOR_FRAME_FACTORY,
+                BTreeSearchCursorTest.LEAF_FRAME_FACTORY, BTreeSearchCursorTest.CMP_FACTORIES,
+                BTreeSearchCursorTest.FIELD_COUNT, harness.getFileReference());
+        btree.create();
+        btree.activate();
+
+        TreeSet<Integer> uniqueKeys = new TreeSet<>();
+        ArrayList<Integer> keys = new ArrayList<>();
+        // generate keys
+        int numKeys = 50;
+        int maxKey = 1000;
+        while (uniqueKeys.size() < numKeys) {
+            int key = BTreeSearchCursorTest.RANDOM.nextInt() % maxKey;
+            uniqueKeys.add(key);
+        }
+        for (Integer i : uniqueKeys) {
+            keys.add(i);
+        }
+        DiskBTreeSearchCursorTest.bulkLoadBTree(keys, btree);
+    }
+
+    @AfterClass
+    public static void tearDown() throws HyracksDataException {
+        try {
+            btree.deactivate();
+            btree.destroy();
+        } finally {
+            harness.tearDown();
+        }
+    }
+
+    @Override
+    protected List<ISearchPredicate> createSearchPredicates() throws HyracksDataException {
+        List<ISearchPredicate> predicates = new ArrayList<>();
+        int minKey = -10;
+        int maxKey = 10;
+        for (int i = minKey; i < maxKey; i++) {
+            for (int j = minKey; j < maxKey; j++) {
+                int lowKey = i;
+                int highKey = j;
+                predicates.add(BTreeSearchCursorTest.createRangePredicate(lowKey, highKey, true, true));
+            }
+        }
+        return predicates;
+    }
+
+    @Override
+    protected IIndexAccessor createAccessor() throws Exception {
+        IndexAccessParameters actx =
+                new IndexAccessParameters(TestOperationCallback.INSTANCE, TestOperationCallback.INSTANCE);
+        return btree.createAccessor(actx);
+    }
+
+    @Override
+    protected void open(IIndexAccessor accessor, IIndexCursor cursor, ISearchPredicate predicate)
+            throws HyracksDataException {
+        ((BTreeAccessor) accessor).diskOrderScan((ITreeIndexCursor) cursor);
+    }
+
+    @Override
+    protected IIndexCursor createCursor(IIndexAccessor accessor) {
+        return ((BTreeAccessor) accessor).createDiskOrderScanCursor();
+    }
+}
diff --git a/hyracks-fullstack/hyracks/hyracks-tests/hyracks-storage-am-btree-test/src/test/java/org/apache/hyracks/storage/am/btree/DiskBTreePointSearchCursorTest.java b/hyracks-fullstack/hyracks/hyracks-tests/hyracks-storage-am-btree-test/src/test/java/org/apache/hyracks/storage/am/btree/DiskBTreePointSearchCursorTest.java
new file mode 100644
index 0000000..c2a69e1
--- /dev/null
+++ b/hyracks-fullstack/hyracks/hyracks-tests/hyracks-storage-am-btree-test/src/test/java/org/apache/hyracks/storage/am/btree/DiskBTreePointSearchCursorTest.java
@@ -0,0 +1,106 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.hyracks.storage.am.btree;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.TreeSet;
+
+import org.apache.hyracks.api.exceptions.HyracksDataException;
+import org.apache.hyracks.storage.am.btree.impls.BTree.BTreeAccessor;
+import org.apache.hyracks.storage.am.btree.impls.DiskBTree;
+import org.apache.hyracks.storage.am.btree.util.BTreeTestHarness;
+import org.apache.hyracks.storage.am.common.TestOperationCallback;
+import org.apache.hyracks.storage.am.common.api.IMetadataPageManager;
+import org.apache.hyracks.storage.am.common.freepage.LinkedMetaDataPageManager;
+import org.apache.hyracks.storage.am.common.impls.IndexAccessParameters;
+import org.apache.hyracks.storage.am.common.test.IIndexCursorTest;
+import org.apache.hyracks.storage.common.IIndexAccessor;
+import org.apache.hyracks.storage.common.IIndexCursor;
+import org.apache.hyracks.storage.common.ISearchPredicate;
+import org.apache.hyracks.storage.common.buffercache.IBufferCache;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+
+public class DiskBTreePointSearchCursorTest extends IIndexCursorTest {
+    private static final BTreeTestHarness harness = new BTreeTestHarness();
+    private static DiskBTree btree;
+
+    @BeforeClass
+    public static void setup() throws HyracksDataException {
+        harness.setUp();
+        IBufferCache bufferCache = harness.getBufferCache();
+        IMetadataPageManager freePageManager =
+                new LinkedMetaDataPageManager(bufferCache, BTreeSearchCursorTest.META_FRAME_FACTORY);
+        btree = new DiskBTree(bufferCache, freePageManager, BTreeSearchCursorTest.INTERIOR_FRAME_FACTORY,
+                BTreeSearchCursorTest.LEAF_FRAME_FACTORY, BTreeSearchCursorTest.CMP_FACTORIES,
+                BTreeSearchCursorTest.FIELD_COUNT, harness.getFileReference());
+        btree.create();
+        btree.activate();
+
+        TreeSet<Integer> uniqueKeys = new TreeSet<>();
+        ArrayList<Integer> keys = new ArrayList<>();
+        // generate keys
+        int numKeys = 50;
+        int maxKey = 1000;
+        while (uniqueKeys.size() < numKeys) {
+            int key = BTreeSearchCursorTest.RANDOM.nextInt() % maxKey;
+            uniqueKeys.add(key);
+        }
+        for (Integer i : uniqueKeys) {
+            keys.add(i);
+        }
+        DiskBTreeSearchCursorTest.bulkLoadBTree(keys, btree);
+    }
+
+    @AfterClass
+    public static void tearDown() throws HyracksDataException {
+        try {
+            btree.deactivate();
+            btree.destroy();
+        } finally {
+            harness.tearDown();
+        }
+    }
+
+    @Override
+    protected List<ISearchPredicate> createSearchPredicates() throws HyracksDataException {
+        List<ISearchPredicate> predicates = new ArrayList<>();
+        int minKey = -10;
+        int maxKey = 10;
+        for (int i = minKey; i < maxKey; i++) {
+            int lowKey = i;
+            int highKey = i;
+            predicates.add(BTreeSearchCursorTest.createRangePredicate(lowKey, highKey, true, true));
+        }
+        return predicates;
+    }
+
+    @Override
+    protected IIndexAccessor createAccessor() throws Exception {
+        IndexAccessParameters actx =
+                new IndexAccessParameters(TestOperationCallback.INSTANCE, TestOperationCallback.INSTANCE);
+        return btree.createAccessor(actx);
+    }
+
+    @Override
+    protected IIndexCursor createCursor(IIndexAccessor accessor) {
+        return ((BTreeAccessor) accessor).createPointCursor(false);
+    }
+}
diff --git a/hyracks-fullstack/hyracks/hyracks-tests/hyracks-storage-am-btree-test/src/test/java/org/apache/hyracks/storage/am/btree/DiskBTreeRangeSearchCursorTest.java b/hyracks-fullstack/hyracks/hyracks-tests/hyracks-storage-am-btree-test/src/test/java/org/apache/hyracks/storage/am/btree/DiskBTreeRangeSearchCursorTest.java
new file mode 100644
index 0000000..6c1c1bf
--- /dev/null
+++ b/hyracks-fullstack/hyracks/hyracks-tests/hyracks-storage-am-btree-test/src/test/java/org/apache/hyracks/storage/am/btree/DiskBTreeRangeSearchCursorTest.java
@@ -0,0 +1,101 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.hyracks.storage.am.btree;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.TreeSet;
+
+import org.apache.hyracks.api.exceptions.HyracksDataException;
+import org.apache.hyracks.storage.am.btree.impls.DiskBTree;
+import org.apache.hyracks.storage.am.btree.util.BTreeTestHarness;
+import org.apache.hyracks.storage.am.common.TestOperationCallback;
+import org.apache.hyracks.storage.am.common.api.IMetadataPageManager;
+import org.apache.hyracks.storage.am.common.freepage.LinkedMetaDataPageManager;
+import org.apache.hyracks.storage.am.common.impls.IndexAccessParameters;
+import org.apache.hyracks.storage.am.common.test.IIndexCursorTest;
+import org.apache.hyracks.storage.common.IIndexAccessor;
+import org.apache.hyracks.storage.common.ISearchPredicate;
+import org.apache.hyracks.storage.common.buffercache.IBufferCache;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+
+public class DiskBTreeRangeSearchCursorTest extends IIndexCursorTest {
+    private static final BTreeTestHarness harness = new BTreeTestHarness();
+    private static DiskBTree btree;
+
+    @BeforeClass
+    public static void setup() throws HyracksDataException {
+        harness.setUp();
+        IBufferCache bufferCache = harness.getBufferCache();
+        IMetadataPageManager freePageManager =
+                new LinkedMetaDataPageManager(bufferCache, BTreeSearchCursorTest.META_FRAME_FACTORY);
+        btree = new DiskBTree(bufferCache, freePageManager, BTreeSearchCursorTest.INTERIOR_FRAME_FACTORY,
+                BTreeSearchCursorTest.LEAF_FRAME_FACTORY, BTreeSearchCursorTest.CMP_FACTORIES,
+                BTreeSearchCursorTest.FIELD_COUNT, harness.getFileReference());
+        btree.create();
+        btree.activate();
+
+        TreeSet<Integer> uniqueKeys = new TreeSet<>();
+        ArrayList<Integer> keys = new ArrayList<>();
+        // generate keys
+        int numKeys = 50;
+        int maxKey = 1000;
+        while (uniqueKeys.size() < numKeys) {
+            int key = BTreeSearchCursorTest.RANDOM.nextInt() % maxKey;
+            uniqueKeys.add(key);
+        }
+        for (Integer i : uniqueKeys) {
+            keys.add(i);
+        }
+        DiskBTreeSearchCursorTest.bulkLoadBTree(keys, btree);
+    }
+
+    @AfterClass
+    public static void tearDown() throws HyracksDataException {
+        try {
+            btree.deactivate();
+            btree.destroy();
+        } finally {
+            harness.tearDown();
+        }
+    }
+
+    @Override
+    protected List<ISearchPredicate> createSearchPredicates() throws HyracksDataException {
+        List<ISearchPredicate> predicates = new ArrayList<>();
+        int minKey = -10;
+        int maxKey = 10;
+        for (int i = minKey; i < maxKey; i++) {
+            for (int j = minKey; j < maxKey; j++) {
+                int lowKey = i;
+                int highKey = j;
+                predicates.add(BTreeSearchCursorTest.createRangePredicate(lowKey, highKey, true, true));
+            }
+        }
+        return predicates;
+    }
+
+    @Override
+    protected IIndexAccessor createAccessor() throws Exception {
+        IndexAccessParameters actx =
+                new IndexAccessParameters(TestOperationCallback.INSTANCE, TestOperationCallback.INSTANCE);
+        return btree.createAccessor(actx);
+    }
+}
diff --git a/hyracks-fullstack/hyracks/hyracks-tests/hyracks-storage-am-btree-test/src/test/java/org/apache/hyracks/storage/am/btree/DiskBTreeSearchCursorTest.java b/hyracks-fullstack/hyracks/hyracks-tests/hyracks-storage-am-btree-test/src/test/java/org/apache/hyracks/storage/am/btree/DiskBTreeSearchCursorTest.java
index c7f0425..c77bea0 100644
--- a/hyracks-fullstack/hyracks/hyracks-tests/hyracks-storage-am-btree-test/src/test/java/org/apache/hyracks/storage/am/btree/DiskBTreeSearchCursorTest.java
+++ b/hyracks-fullstack/hyracks/hyracks-tests/hyracks-storage-am-btree-test/src/test/java/org/apache/hyracks/storage/am/btree/DiskBTreeSearchCursorTest.java
@@ -26,10 +26,7 @@
 import java.util.List;
 import java.util.TreeSet;
 
-import org.apache.hyracks.api.dataflow.value.IBinaryComparatorFactory;
 import org.apache.hyracks.api.exceptions.HyracksDataException;
-import org.apache.hyracks.data.std.accessors.PointableBinaryComparatorFactory;
-import org.apache.hyracks.data.std.primitive.IntegerPointable;
 import org.apache.hyracks.dataflow.common.comm.io.ArrayTupleBuilder;
 import org.apache.hyracks.dataflow.common.comm.io.ArrayTupleReference;
 import org.apache.hyracks.dataflow.common.data.accessors.ITupleReference;
@@ -37,15 +34,12 @@
 import org.apache.hyracks.dataflow.common.utils.TupleUtils;
 import org.apache.hyracks.storage.am.btree.api.IBTreeInteriorFrame;
 import org.apache.hyracks.storage.am.btree.api.IBTreeLeafFrame;
-import org.apache.hyracks.storage.am.btree.frames.BTreeNSMInteriorFrameFactory;
-import org.apache.hyracks.storage.am.btree.frames.BTreeNSMLeafFrameFactory;
 import org.apache.hyracks.storage.am.btree.impls.BTree;
 import org.apache.hyracks.storage.am.btree.impls.BTree.BTreeAccessor;
 import org.apache.hyracks.storage.am.btree.impls.DiskBTree;
 import org.apache.hyracks.storage.am.btree.impls.RangePredicate;
 import org.apache.hyracks.storage.am.common.TestOperationCallback;
 import org.apache.hyracks.storage.am.common.api.IMetadataPageManager;
-import org.apache.hyracks.storage.am.common.api.ITreeIndexFrameFactory;
 import org.apache.hyracks.storage.am.common.freepage.LinkedMetaDataPageManager;
 import org.apache.hyracks.storage.am.common.impls.IndexAccessParameters;
 import org.apache.hyracks.storage.common.IIndexBulkLoader;
@@ -89,29 +83,18 @@
     private void batchPointLookupTest(int numKeys, int maxKey, int minSearchKey, int maxSearchKey) throws Exception {
 
         IBufferCache bufferCache = harness.getBufferCache();
-
-        // declare keys
-        int keyFieldCount = 1;
-        IBinaryComparatorFactory[] cmpFactories = new IBinaryComparatorFactory[keyFieldCount];
-        cmpFactories[0] = PointableBinaryComparatorFactory.of(IntegerPointable.FACTORY);
-
-        ITreeIndexFrameFactory leafFrameFactory = new BTreeNSMLeafFrameFactory(tupleWriterFactory);
-        ITreeIndexFrameFactory interiorFrameFactory = new BTreeNSMInteriorFrameFactory(tupleWriterFactory);
-
-        IBTreeLeafFrame leafFrame = (IBTreeLeafFrame) leafFrameFactory.createFrame();
-        IBTreeInteriorFrame interiorFrame = (IBTreeInteriorFrame) interiorFrameFactory.createFrame();
-
-        IMetadataPageManager freePageManager = new LinkedMetaDataPageManager(bufferCache, metaFrameFactory);
-
-        DiskBTree btree = new DiskBTree(bufferCache, freePageManager, interiorFrameFactory, leafFrameFactory,
-                cmpFactories, fieldCount, harness.getFileReference());
+        IBTreeLeafFrame leafFrame = (IBTreeLeafFrame) LEAF_FRAME_FACTORY.createFrame();
+        IBTreeInteriorFrame interiorFrame = (IBTreeInteriorFrame) INTERIOR_FRAME_FACTORY.createFrame();
+        IMetadataPageManager freePageManager = new LinkedMetaDataPageManager(bufferCache, META_FRAME_FACTORY);
+        DiskBTree btree = new DiskBTree(bufferCache, freePageManager, INTERIOR_FRAME_FACTORY, LEAF_FRAME_FACTORY,
+                CMP_FACTORIES, FIELD_COUNT, harness.getFileReference());
         btree.create();
         btree.activate();
 
         TreeSet<Integer> uniqueKeys = new TreeSet<>();
         ArrayList<Integer> keys = new ArrayList<>();
         while (uniqueKeys.size() < numKeys) {
-            int key = rnd.nextInt() % maxKey;
+            int key = RANDOM.nextInt() % maxKey;
             uniqueKeys.add(key);
         }
         for (Integer i : uniqueKeys) {
@@ -135,58 +118,60 @@
         BTreeAccessor indexAccessor = btree.createAccessor(
                 new IndexAccessParameters(TestOperationCallback.INSTANCE, TestOperationCallback.INSTANCE));
         IIndexCursor pointCursor = indexAccessor.createPointCursor(false);
-        for (int i = minKey; i < maxKey; i++) {
-            results.clear();
-            expectedResults.clear();
-
-            int lowKey = i;
-            int highKey = i;
-            RangePredicate rangePred = createRangePredicate(lowKey, highKey, true, true);
-            indexAccessor.search(pointCursor, rangePred);
-            try {
-                while (pointCursor.hasNext()) {
-                    pointCursor.next();
-                    ITupleReference frameTuple = pointCursor.getTuple();
-                    ByteArrayInputStream inStream = new ByteArrayInputStream(frameTuple.getFieldData(0),
-                            frameTuple.getFieldStart(0), frameTuple.getFieldLength(0));
-                    DataInput dataIn = new DataInputStream(inStream);
-                    Integer res = IntegerSerializerDeserializer.INSTANCE.deserialize(dataIn);
-                    results.add(res);
-                }
-            } catch (Exception e) {
-                e.printStackTrace();
-            }
-
-            getExpectedResults(expectedResults, keys, lowKey, highKey, true, true);
-
-            if (results.size() == expectedResults.size()) {
-                for (int k = 0; k < results.size(); k++) {
-                    if (!results.get(k).equals(expectedResults.get(k))) {
-                        if (LOGGER.isInfoEnabled()) {
-                            LOGGER.info("DIFFERENT RESULTS AT: i=" + i + " k=" + k);
-                            LOGGER.info(results.get(k) + " " + expectedResults.get(k));
-                        }
-                        return false;
+        try {
+            for (int i = minKey; i < maxKey; i++) {
+                results.clear();
+                expectedResults.clear();
+                int lowKey = i;
+                int highKey = i;
+                RangePredicate rangePred = createRangePredicate(lowKey, highKey, true, true);
+                indexAccessor.search(pointCursor, rangePred);
+                try {
+                    while (pointCursor.hasNext()) {
+                        pointCursor.next();
+                        ITupleReference frameTuple = pointCursor.getTuple();
+                        ByteArrayInputStream inStream = new ByteArrayInputStream(frameTuple.getFieldData(0),
+                                frameTuple.getFieldStart(0), frameTuple.getFieldLength(0));
+                        DataInput dataIn = new DataInputStream(inStream);
+                        Integer res = IntegerSerializerDeserializer.INSTANCE.deserialize(dataIn);
+                        results.add(res);
                     }
+                } finally {
+                    pointCursor.close();
                 }
-            } else {
-                if (LOGGER.isInfoEnabled()) {
-                    LOGGER.info("UNEQUAL NUMBER OF RESULTS AT: i=" + i);
-                    LOGGER.info("RESULTS: " + results.size());
-                    LOGGER.info("EXPECTED RESULTS: " + expectedResults.size());
+                getExpectedResults(expectedResults, keys, lowKey, highKey, true, true);
+                if (results.size() == expectedResults.size()) {
+                    for (int k = 0; k < results.size(); k++) {
+                        if (!results.get(k).equals(expectedResults.get(k))) {
+                            if (LOGGER.isInfoEnabled()) {
+                                LOGGER.info("DIFFERENT RESULTS AT: i=" + i + " k=" + k);
+                                LOGGER.info(results.get(k) + " " + expectedResults.get(k));
+                            }
+                            return false;
+                        }
+                    }
+                } else {
+                    if (LOGGER.isInfoEnabled()) {
+                        LOGGER.info("UNEQUAL NUMBER OF RESULTS AT: i=" + i);
+                        LOGGER.info("RESULTS: " + results.size());
+                        LOGGER.info("EXPECTED RESULTS: " + expectedResults.size());
+                    }
+                    return false;
                 }
-                return false;
             }
+        } finally {
+            pointCursor.destroy();;
         }
-
-        pointCursor.close();
-
         return true;
     }
 
     @Override
     protected void insertBTree(List<Integer> keys, BTree btree) throws HyracksDataException {
-        ArrayTupleBuilder tupleBuilder = new ArrayTupleBuilder(fieldCount);
+        bulkLoadBTree(keys, btree);
+    }
+
+    public static void bulkLoadBTree(List<Integer> keys, BTree btree) throws HyracksDataException {
+        ArrayTupleBuilder tupleBuilder = new ArrayTupleBuilder(FIELD_COUNT);
         ArrayTupleReference tuple = new ArrayTupleReference();
 
         IIndexBulkLoader bulkloader = btree.createBulkLoader(1, true, 0, true);
diff --git a/hyracks-fullstack/hyracks/hyracks-tests/hyracks-storage-am-btree-test/src/test/java/org/apache/hyracks/storage/am/btree/DiskOrderScanCursorTest.java b/hyracks-fullstack/hyracks/hyracks-tests/hyracks-storage-am-btree-test/src/test/java/org/apache/hyracks/storage/am/btree/DiskOrderScanCursorTest.java
new file mode 100644
index 0000000..7cfd0fe
--- /dev/null
+++ b/hyracks-fullstack/hyracks/hyracks-tests/hyracks-storage-am-btree-test/src/test/java/org/apache/hyracks/storage/am/btree/DiskOrderScanCursorTest.java
@@ -0,0 +1,113 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.hyracks.storage.am.btree;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.TreeSet;
+
+import org.apache.hyracks.api.exceptions.HyracksDataException;
+import org.apache.hyracks.storage.am.btree.impls.BTree;
+import org.apache.hyracks.storage.am.btree.impls.BTree.BTreeAccessor;
+import org.apache.hyracks.storage.am.btree.util.BTreeTestHarness;
+import org.apache.hyracks.storage.am.common.TestOperationCallback;
+import org.apache.hyracks.storage.am.common.api.IMetadataPageManager;
+import org.apache.hyracks.storage.am.common.api.ITreeIndexCursor;
+import org.apache.hyracks.storage.am.common.freepage.LinkedMetaDataPageManager;
+import org.apache.hyracks.storage.am.common.impls.IndexAccessParameters;
+import org.apache.hyracks.storage.am.common.test.IIndexCursorTest;
+import org.apache.hyracks.storage.common.IIndexAccessor;
+import org.apache.hyracks.storage.common.IIndexCursor;
+import org.apache.hyracks.storage.common.ISearchPredicate;
+import org.apache.hyracks.storage.common.buffercache.IBufferCache;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+
+public class DiskOrderScanCursorTest extends IIndexCursorTest {
+    private static final BTreeTestHarness harness = new BTreeTestHarness();
+    private static BTree btree;
+
+    @BeforeClass
+    public static void setup() throws HyracksDataException {
+        harness.setUp();
+        IBufferCache bufferCache = harness.getBufferCache();
+        IMetadataPageManager freePageManager =
+                new LinkedMetaDataPageManager(bufferCache, BTreeSearchCursorTest.META_FRAME_FACTORY);
+        btree = new BTree(bufferCache, freePageManager, BTreeSearchCursorTest.INTERIOR_FRAME_FACTORY,
+                BTreeSearchCursorTest.LEAF_FRAME_FACTORY, BTreeSearchCursorTest.CMP_FACTORIES,
+                BTreeSearchCursorTest.FIELD_COUNT, harness.getFileReference());
+        btree.create();
+        btree.activate();
+        // generate keys
+        int numKeys = 50;
+        int maxKey = 1000;
+        TreeSet<Integer> uniqueKeys = new TreeSet<>();
+        ArrayList<Integer> keys = new ArrayList<>();
+        while (uniqueKeys.size() < numKeys) {
+            int key = BTreeSearchCursorTest.RANDOM.nextInt() % maxKey;
+            uniqueKeys.add(key);
+        }
+        for (Integer i : uniqueKeys) {
+            keys.add(i);
+        }
+        BTreeSearchCursorTest.staticInsertBTree(keys, btree);
+    }
+
+    @AfterClass
+    public static void tearDown() throws HyracksDataException {
+        try {
+            btree.deactivate();
+            btree.destroy();
+        } finally {
+            harness.tearDown();
+        }
+    }
+
+    @Override
+    protected List<ISearchPredicate> createSearchPredicates() throws HyracksDataException {
+        List<ISearchPredicate> predicates = new ArrayList<>();
+        int minKey = -10;
+        int maxKey = 10;
+        for (int i = minKey; i < maxKey; i++) {
+            for (int j = minKey; j < maxKey; j++) {
+                int lowKey = i;
+                int highKey = j;
+                predicates.add(BTreeSearchCursorTest.createRangePredicate(lowKey, highKey, true, true));
+            }
+        }
+        return predicates;
+    }
+
+    @Override
+    protected IIndexAccessor createAccessor() throws Exception {
+        return btree.createAccessor(
+                new IndexAccessParameters(TestOperationCallback.INSTANCE, TestOperationCallback.INSTANCE));
+    }
+
+    @Override
+    protected void open(IIndexAccessor accessor, IIndexCursor cursor, ISearchPredicate predicate)
+            throws HyracksDataException {
+        ((BTreeAccessor) accessor).diskOrderScan((ITreeIndexCursor) cursor);
+    }
+
+    @Override
+    protected IIndexCursor createCursor(IIndexAccessor accessor) {
+        return ((BTreeAccessor) accessor).createDiskOrderScanCursor();
+    }
+}
diff --git a/hyracks-fullstack/hyracks/hyracks-tests/hyracks-storage-am-btree-test/src/test/java/org/apache/hyracks/storage/am/btree/multithread/BTreeTestWorker.java b/hyracks-fullstack/hyracks/hyracks-tests/hyracks-storage-am-btree-test/src/test/java/org/apache/hyracks/storage/am/btree/multithread/BTreeTestWorker.java
index 06a00e0..a67450b 100644
--- a/hyracks-fullstack/hyracks/hyracks-tests/hyracks-storage-am-btree-test/src/test/java/org/apache/hyracks/storage/am/btree/multithread/BTreeTestWorker.java
+++ b/hyracks-fullstack/hyracks/hyracks-tests/hyracks-storage-am-btree-test/src/test/java/org/apache/hyracks/storage/am/btree/multithread/BTreeTestWorker.java
@@ -56,90 +56,100 @@
         ITreeIndexCursor diskOrderScanCursor = accessor.createDiskOrderScanCursor();
         MultiComparator cmp = accessor.getOpContext().getCmp();
         RangePredicate rangePred = new RangePredicate(tuple, tuple, true, true, cmp, cmp);
-
-        switch (op) {
-            case INSERT:
-                try {
-                    accessor.insert(tuple);
-                } catch (HyracksDataException e) {
-                    if (e.getErrorCode() != ErrorCode.DUPLICATE_KEY) {
-                        // Ignore duplicate keys, since we get random tuples.
-                        throw e;
+        try {
+            switch (op) {
+                case INSERT:
+                    try {
+                        accessor.insert(tuple);
+                    } catch (HyracksDataException e) {
+                        if (e.getErrorCode() != ErrorCode.DUPLICATE_KEY) {
+                            // Ignore duplicate keys, since we get random tuples.
+                            throw e;
+                        }
                     }
-                }
-                break;
+                    break;
 
-            case DELETE:
-                // Create a tuple reference with only key fields.
-                deleteTb.reset();
-                for (int i = 0; i < numKeyFields; i++) {
-                    deleteTb.addField(tuple.getFieldData(i), tuple.getFieldStart(i), tuple.getFieldLength(i));
-                }
-                deleteTuple.reset(deleteTb.getFieldEndOffsets(), deleteTb.getByteArray());
-                try {
-                    accessor.delete(deleteTuple);
-                } catch (HyracksDataException e) {
-                    if (e.getErrorCode() != ErrorCode.UPDATE_OR_DELETE_NON_EXISTENT_KEY) {
+                case DELETE:
+                    // Create a tuple reference with only key fields.
+                    deleteTb.reset();
+                    for (int i = 0; i < numKeyFields; i++) {
+                        deleteTb.addField(tuple.getFieldData(i), tuple.getFieldStart(i), tuple.getFieldLength(i));
+                    }
+                    deleteTuple.reset(deleteTb.getFieldEndOffsets(), deleteTb.getByteArray());
+                    try {
+                        accessor.delete(deleteTuple);
+                    } catch (HyracksDataException e) {
+                        if (e.getErrorCode() != ErrorCode.UPDATE_OR_DELETE_NON_EXISTENT_KEY) {
+                            // Ignore non-existant keys, since we get random tuples.
+                            throw e;
+                        }
+                    }
+                    break;
+
+                case UPDATE:
+                    try {
+                        accessor.update(tuple);
+                    } catch (HyracksDataException e) {
                         // Ignore non-existant keys, since we get random tuples.
-                        throw e;
+                        if (e.getErrorCode() != ErrorCode.UPDATE_OR_DELETE_NON_EXISTENT_KEY
+                                && e.getErrorCode() != ErrorCode.INDEX_NOT_UPDATABLE) {
+                            // Ignore non-existant keys, since we get random tuples.
+                            // Ignore not updateable exception due to numKeys == numFields.
+                            throw e;
+                        }
                     }
-                }
-                break;
+                    break;
 
-            case UPDATE:
-                try {
-                    accessor.update(tuple);
-                } catch (HyracksDataException e) {
-                    // Ignore non-existant keys, since we get random tuples.
-                    if (e.getErrorCode() != ErrorCode.UPDATE_OR_DELETE_NON_EXISTENT_KEY
-                            && e.getErrorCode() != ErrorCode.INDEX_NOT_UPDATABLE) {
-                        // Ignore non-existant keys, since we get random tuples.
-                        // Ignore not updateable exception due to numKeys == numFields.
-                        throw e;
+                case UPSERT:
+                    accessor.upsert(tuple);
+                    // Upsert should not throw. If it does, there's
+                    // a bigger problem and the test should fail.
+                    break;
+
+                case POINT_SEARCH:
+                    searchCursor.close();
+                    rangePred.setLowKey(tuple, true);
+                    rangePred.setHighKey(tuple, true);
+                    accessor.search(searchCursor, rangePred);
+                    try {
+                        consumeCursorTuples(searchCursor);
+                    } finally {
+                        searchCursor.close();
                     }
-                }
-                break;
+                    break;
 
-            case UPSERT:
-                accessor.upsert(tuple);
-                // Upsert should not throw. If it does, there's
-                // a bigger problem and the test should fail.
-                break;
+                case SCAN:
+                    searchCursor.close();
+                    rangePred.setLowKey(null, true);
+                    rangePred.setHighKey(null, true);
+                    accessor.search(searchCursor, rangePred);
+                    try {
+                        consumeCursorTuples(searchCursor);
+                    } finally {
+                        searchCursor.close();
+                    }
+                    break;
 
-            case POINT_SEARCH:
-                searchCursor.close();
-                rangePred.setLowKey(tuple, true);
-                rangePred.setHighKey(tuple, true);
-                accessor.search(searchCursor, rangePred);
-                consumeCursorTuples(searchCursor);
-                break;
-
-            case SCAN:
-                searchCursor.close();
-                rangePred.setLowKey(null, true);
-                rangePred.setHighKey(null, true);
-                accessor.search(searchCursor, rangePred);
-                consumeCursorTuples(searchCursor);
-                break;
-
-            case DISKORDER_SCAN:
-                diskOrderScanCursor.close();
-                accessor.diskOrderScan(diskOrderScanCursor);
-                consumeCursorTuples(diskOrderScanCursor);
-                break;
-
-            default:
-                throw new HyracksDataException("Op " + op.toString() + " not supported.");
+                case DISKORDER_SCAN:
+                    accessor.diskOrderScan(diskOrderScanCursor);
+                    try {
+                        consumeCursorTuples(diskOrderScanCursor);
+                    } finally {
+                        diskOrderScanCursor.close();
+                    }
+                    break;
+                default:
+                    throw new HyracksDataException("Op " + op.toString() + " not supported.");
+            }
+        } finally {
+            searchCursor.destroy();
+            diskOrderScanCursor.destroy();
         }
     }
 
     private void consumeCursorTuples(ITreeIndexCursor cursor) throws HyracksDataException {
-        try {
-            while (cursor.hasNext()) {
-                cursor.next();
-            }
-        } finally {
-            cursor.destroy();
+        while (cursor.hasNext()) {
+            cursor.next();
         }
     }
 }