/*-
* See the file LICENSE for redistribution information.
*
* Copyright (c) 2002-2005
*      Sleepycat Software.  All rights reserved.
*
* $Id: DatabaseTest.java,v 1.82 2004/12/22 14:11:42 linda Exp $
*/

package com.sleepycat.je;

import java.io.File;
import java.io.IOException;

import junit.framework.TestCase;

import com.sleepycat.je.config.EnvironmentParams;
import com.sleepycat.je.dbi.INList;
import com.sleepycat.je.junit.JUnitThread;
import com.sleepycat.je.tree.Key;
import com.sleepycat.je.util.TestUtils;

/**
 * Basic database operations, excluding configuration testing.
 */
public class DatabaseTest extends TestCase {
    private static final boolean DEBUG = false;
    private static final int NUM_RECS = 257;

    private File envHome;
    private Environment env;

    public DatabaseTest() {
        envHome = new File(System.getProperty(TestUtils.DEST_DIR));
    }

    public void setUp()
        throws IOException {

        TestUtils.removeLogFiles("Setup", envHome, false);
    }
    
    public void tearDown()
        throws Exception {

        try {
            /* Close in case we hit an exception and didn't close */
            if (env != null) {
		env.close();
            }
        } catch (DatabaseException e) {
            /* Ok if already closed */
        }
        env = null; // for JUNIT, to reduce memory usage when run in a suite.
        TestUtils.removeLogFiles("TearDown", envHome, false);
    }

    /**
     * Make sure we can't create a transactional cursor on a non-transactional 
     * database.
     */
    public void testCursor() 
        throws Exception {
        
        Environment txnalEnv = null;
        Database nonTxnalDb = null;
        Cursor txnalCursor = null;
        Transaction txn = null;

        try {

            EnvironmentConfig envConfig = new EnvironmentConfig();
            envConfig.setTransactional(true);
            envConfig.setAllowCreate(true);
            txnalEnv = new Environment(envHome, envConfig);

            // Make a db and open it
            DatabaseConfig dbConfig = new DatabaseConfig();
            dbConfig.setAllowCreate(true);
            dbConfig.setTransactional(false);
            nonTxnalDb = txnalEnv.openDatabase(null, "testDB", dbConfig);

            // We should not be able to open a txnal cursor.
            txn = txnalEnv.beginTransaction(null, null);
            try {
                txnalCursor = nonTxnalDb.openCursor(txn, null);
                fail("Openin a txnal cursor on a nontxnal db is invalid.");
            } catch (DatabaseException e) {
                // expected
            }
        } finally {
            if (txn != null) {
                txn.abort();
            }
            if (txnalCursor != null) {
                txnalCursor.close();
            }
            if (nonTxnalDb != null) {
                nonTxnalDb.close();
            }
            if (txnalEnv != null) {
                txnalEnv.close();
            }

        }
    }

    public void testPutExisting()
        throws Throwable {

        try {
            Database myDb = initEnvAndDb(true, false, true, null);
            DatabaseEntry key = new DatabaseEntry();
            DatabaseEntry data = new DatabaseEntry();
            DatabaseEntry getData = new DatabaseEntry();
	    Transaction txn = env.beginTransaction(null, null);
            for (int i = NUM_RECS; i > 0; i--) {
                key.setData(TestUtils.getTestArray(i));
                data.setData(TestUtils.getTestArray(i));
                assertEquals(OperationStatus.SUCCESS,
			     myDb.put(txn, key, data));
                assertEquals(OperationStatus.SUCCESS,
			     myDb.get(txn, key, getData, LockMode.DEFAULT));
                assertEquals(0, Key.compareByteArray(data.getData(),
                                                     getData.getData()));
                assertEquals(OperationStatus.SUCCESS,
			     myDb.put(txn, key, data));
                assertEquals(OperationStatus.SUCCESS, myDb.getSearchBoth
                             (txn, key, getData, LockMode.DEFAULT));
                assertEquals(0, Key.compareByteArray(data.getData(),
                                                     getData.getData()));
            }
	    txn.commit();
            myDb.close();
            env.close();
        } catch (Throwable t) {
            t.printStackTrace();
            throw t;
        }
    }

    public void testDeleteNonDup()
        throws Throwable {

        try {
            Database myDb = initEnvAndDb(true, false, true, null);
            DatabaseEntry key = new DatabaseEntry();
            DatabaseEntry data = new DatabaseEntry();
            DatabaseEntry getData = new DatabaseEntry();
	    Transaction txn = env.beginTransaction(null, null);
            for (int i = NUM_RECS; i > 0; i--) {
                key.setData(TestUtils.getTestArray(i));
                data.setData(TestUtils.getTestArray(i));
                assertEquals(OperationStatus.SUCCESS,
			     myDb.put(txn, key, data));
            }

            for (int i = NUM_RECS; i > 0; i--) {
                key.setData(TestUtils.getTestArray(i));
                data.setData(TestUtils.getTestArray(i));
                assertEquals(OperationStatus.SUCCESS,
			     myDb.delete(txn, key));
                OperationStatus status =
		    myDb.get(txn, key, getData, LockMode.DEFAULT);
                if (status != OperationStatus.KEYEMPTY &&
                    status != OperationStatus.NOTFOUND) {
                    fail("invalid Database.get return: " + status);
                }
                assertEquals(OperationStatus.NOTFOUND,
			     myDb.delete(txn, key));
            }
	    txn.commit();
            myDb.close();
            env.close();
        } catch (Throwable t) {
            t.printStackTrace();
            throw t;
        }
    }

    public void testDeleteDup()
        throws Throwable {

        try {
            Database myDb = initEnvAndDb(true, false, true, null);
            DatabaseEntry key = new DatabaseEntry();
            DatabaseEntry data = new DatabaseEntry();
            DatabaseEntry getData = new DatabaseEntry();
	    Transaction txn = env.beginTransaction(null, null);
            for (int i = NUM_RECS; i > 0; i--) {
                key.setData(TestUtils.getTestArray(i));
                data.setData(TestUtils.getTestArray(i));
                assertEquals(OperationStatus.SUCCESS,
			     myDb.put(txn, key, data));
                for (int j = 0; j < NUM_RECS; j++) {
                    data.setData(TestUtils.getTestArray(i + j));
                    assertEquals(OperationStatus.SUCCESS,
				 myDb.put(txn, key, data));
                }
            }
	    txn.commit();

	    txn = env.beginTransaction(null, null);
            for (int i = NUM_RECS; i > 0; i--) {
                key.setData(TestUtils.getTestArray(i));
                data.setData(TestUtils.getTestArray(i));
                assertEquals(OperationStatus.SUCCESS,
			     myDb.delete(txn, key));
                OperationStatus status =
		    myDb.get(txn, key, getData, LockMode.DEFAULT);
                if (status != OperationStatus.KEYEMPTY &&
                    status != OperationStatus.NOTFOUND) {
                    fail("invalid Database.get return");
                }
                assertEquals(OperationStatus.NOTFOUND,
			     myDb.delete(txn, key));
            }
	    txn.commit();
            myDb.close();
            env.close();

        } catch (Throwable t) {
            t.printStackTrace();
            throw t;
        }
    }

    public void testPutDuplicate()
        throws Throwable {
        
        try {
            Database myDb = initEnvAndDb(true, true, true, null);
            DatabaseEntry key = new DatabaseEntry();
            DatabaseEntry data = new DatabaseEntry();
	    Transaction txn = env.beginTransaction(null, null);
            for (int i = NUM_RECS; i > 0; i--) {
                key.setData(TestUtils.getTestArray(i));
                data.setData(TestUtils.getTestArray(i));
                assertEquals(OperationStatus.SUCCESS,
			     myDb.put(txn, key, data));
                data.setData(TestUtils.getTestArray(i * 2));
                assertEquals(OperationStatus.SUCCESS,
			     myDb.put(txn, key, data));
            }
	    txn.commit();
            myDb.close();
            env.close();
        } catch (Throwable t) {
            t.printStackTrace();
            throw t;
        }
    }

    public void testPutNoDupData()
        throws Throwable {
        try {
            Database myDb = initEnvAndDb(true, true, true, null);
            DatabaseEntry key = new DatabaseEntry();
            DatabaseEntry data = new DatabaseEntry();
	    Transaction txn = env.beginTransaction(null, null);
            for (int i = NUM_RECS; i > 0; i--) {
                key.setData(TestUtils.getTestArray(i));
                data.setData(TestUtils.getTestArray(i));
                assertEquals(OperationStatus.SUCCESS,
			     myDb.putNoDupData(txn, key, data));
                assertEquals(OperationStatus.KEYEXIST,
                             myDb.putNoDupData(txn, key, data));
                data.setData(TestUtils.getTestArray(i+1));
                assertEquals(OperationStatus.SUCCESS,
			     myDb.putNoDupData(txn, key, data));
            }
	    txn.commit();
            myDb.close();
            env.close();
        } catch (Throwable t) {
            t.printStackTrace();
            throw t;
        }
    }

    public void testPutNoOverwriteInANoDupDb()
        throws Throwable {

        try {
            Database myDb = initEnvAndDb(true, false, true, null);
            DatabaseEntry key = new DatabaseEntry();
            DatabaseEntry data = new DatabaseEntry();
	    Transaction txn = env.beginTransaction(null, null);
            for (int i = NUM_RECS; i > 0; i--) {
                key.setData(TestUtils.getTestArray(i));
                data.setData(TestUtils.getTestArray(i));
                assertEquals(OperationStatus.SUCCESS,
			     myDb.putNoOverwrite(txn, key, data));
                assertEquals(OperationStatus.KEYEXIST,
			     myDb.putNoOverwrite(txn, key, data));
            }
	    txn.commit();
            myDb.close();
            env.close();
        } catch (Throwable t) {
            t.printStackTrace();
            throw t;
        }
    }

    public void testPutNoOverwriteInADupDbTxn()
        throws Throwable {

        try {
            Database myDb = initEnvAndDb(true, true, true, null);
            DatabaseEntry key = new DatabaseEntry();
            DatabaseEntry data = new DatabaseEntry();
            for (int i = NUM_RECS; i > 0; i--) {
		Transaction txn1 = env.beginTransaction(null, null);
                key.setData(TestUtils.getTestArray(i));
                data.setData(TestUtils.getTestArray(i));
                assertEquals(OperationStatus.SUCCESS,
			     myDb.putNoOverwrite(txn1, key, data));
                assertEquals(OperationStatus.KEYEXIST,
			     myDb.putNoOverwrite(txn1, key, data));
                data.setData(TestUtils.getTestArray(i << 1));
                assertEquals(OperationStatus.SUCCESS,
			     myDb.put(txn1, key, data));
                data.setData(TestUtils.getTestArray(i << 2));
                assertEquals(OperationStatus.KEYEXIST,
			     myDb.putNoOverwrite(txn1, key, data));
		assertEquals(OperationStatus.SUCCESS,
			     myDb.delete(txn1, key));
                assertEquals(OperationStatus.SUCCESS,
			     myDb.putNoOverwrite(txn1, key, data));
		txn1.commit();
            }
            myDb.close();
            env.close();
        } catch (Throwable t) {
            t.printStackTrace();
            throw t;
        }
    }

    public void testPutNoOverwriteInADupDbNoTxn()
        throws Throwable {

        try {
            Database myDb = initEnvAndDb(true, true, false, null);
            DatabaseEntry key = new DatabaseEntry();
            DatabaseEntry data = new DatabaseEntry();
            for (int i = NUM_RECS; i > 0; i--) {
                key.setData(TestUtils.getTestArray(i));
                data.setData(TestUtils.getTestArray(i));
                assertEquals(OperationStatus.SUCCESS,
			     myDb.putNoOverwrite(null, key, data));
                assertEquals(OperationStatus.KEYEXIST,
			     myDb.putNoOverwrite(null, key, data));
                data.setData(TestUtils.getTestArray(i << 1));
                assertEquals(OperationStatus.SUCCESS,
			     myDb.put(null, key, data));
                data.setData(TestUtils.getTestArray(i << 2));
                assertEquals(OperationStatus.KEYEXIST,
			     myDb.putNoOverwrite(null, key, data));
		assertEquals(OperationStatus.SUCCESS,
			     myDb.delete(null, key));
                assertEquals(OperationStatus.SUCCESS,
			     myDb.putNoOverwrite(null, key, data));
            }
            myDb.close();
            env.close();
        } catch (Throwable t) {
            t.printStackTrace();
            throw t;
        }
    }

    public void testStat()
        throws Throwable {

        try {
            Database myDb = initEnvAndDb(true, false, true, null);
            DatabaseEntry key = new DatabaseEntry();
            DatabaseEntry data = new DatabaseEntry();
	    Transaction txn = env.beginTransaction(null, null);
            for (int i = NUM_RECS; i > 0; i--) {
                key.setData(TestUtils.getTestArray(i));
                data.setData(TestUtils.getTestArray(i));
                assertEquals(OperationStatus.SUCCESS,
			     myDb.put(txn, key, data));
            }

            BtreeStats stat = (BtreeStats)
		myDb.getStats(TestUtils.FAST_STATS);

            assertEquals(0, stat.getInternalNodeCount());
            assertEquals(0, stat.getDuplicateInternalNodeCount());
            assertEquals(0, stat.getBottomInternalNodeCount());
            assertEquals(0, stat.getDuplicateBottomInternalNodeCount());
            assertEquals(0, stat.getLeafNodeCount());
            assertEquals(0, stat.getDeletedLeafNodeCount());
            assertEquals(0, stat.getDupCountLeafNodeCount());
            assertEquals(0, stat.getMainTreeMaxDepth());
            assertEquals(0, stat.getDuplicateTreeMaxDepth());

            stat = (BtreeStats) myDb.getStats(null);

            assertEquals(15, stat.getInternalNodeCount());
            assertEquals(0, stat.getDuplicateInternalNodeCount());
            assertEquals(52, stat.getBottomInternalNodeCount());
            assertEquals(0, stat.getDuplicateBottomInternalNodeCount());
            assertEquals(257, stat.getLeafNodeCount());
            assertEquals(0, stat.getDeletedLeafNodeCount());
            assertEquals(0, stat.getDupCountLeafNodeCount());
            assertEquals(4, stat.getMainTreeMaxDepth());
            assertEquals(0, stat.getDuplicateTreeMaxDepth());

            stat = (BtreeStats) myDb.getStats(TestUtils.FAST_STATS);

            assertEquals(15, stat.getInternalNodeCount());
            assertEquals(52, stat.getBottomInternalNodeCount());
            assertEquals(257, stat.getLeafNodeCount());
            assertEquals(0, stat.getDeletedLeafNodeCount());
            assertEquals(0, stat.getDupCountLeafNodeCount());
            assertEquals(4, stat.getMainTreeMaxDepth());
            assertEquals(0, stat.getDuplicateTreeMaxDepth());

	    txn.commit();
            myDb.close();
            env.close();
        } catch (Throwable t) {
            t.printStackTrace();
            throw t;
        }
    }

    public void testStatDupes()
        throws Throwable {

        try {
            Database myDb = initEnvAndDb(true, true, true, null);
            DatabaseEntry key = new DatabaseEntry();
            DatabaseEntry data = new DatabaseEntry();
	    Transaction txn = env.beginTransaction(null, null);
            for (int i = NUM_RECS; i > 0; i--) {
                key.setData(TestUtils.getTestArray(i));
                data.setData(TestUtils.getTestArray(i));
                assertEquals(OperationStatus.SUCCESS,
			     myDb.put(txn, key, data));
		for (int j = 0; j < 10; j++) {
		    data.setData(TestUtils.getTestArray(i + j));
		    assertEquals(OperationStatus.SUCCESS,
				 myDb.put(txn, key, data));
		}
            }

            BtreeStats stat = (BtreeStats)
		myDb.getStats(TestUtils.FAST_STATS);

            assertEquals(0, stat.getInternalNodeCount());
            assertEquals(0, stat.getDuplicateInternalNodeCount());
            assertEquals(0, stat.getBottomInternalNodeCount());
            assertEquals(0, stat.getDuplicateBottomInternalNodeCount());
            assertEquals(0, stat.getLeafNodeCount());
            assertEquals(0, stat.getDeletedLeafNodeCount());
            assertEquals(0, stat.getDupCountLeafNodeCount());
            assertEquals(0, stat.getMainTreeMaxDepth());
            assertEquals(0, stat.getDuplicateTreeMaxDepth());

            stat = (BtreeStats) myDb.getStats(null);

            assertEquals(23, stat.getInternalNodeCount());
            assertEquals(257, stat.getDuplicateInternalNodeCount());
            assertEquals(85, stat.getBottomInternalNodeCount());
            assertEquals(771, stat.getDuplicateBottomInternalNodeCount());
            assertEquals(2570, stat.getLeafNodeCount());
            assertEquals(0, stat.getDeletedLeafNodeCount());
            assertEquals(257, stat.getDupCountLeafNodeCount());
            assertEquals(4, stat.getMainTreeMaxDepth());
            assertEquals(2, stat.getDuplicateTreeMaxDepth());

            stat = (BtreeStats) myDb.getStats(TestUtils.FAST_STATS);

            assertEquals(23, stat.getInternalNodeCount());
            assertEquals(257, stat.getDuplicateInternalNodeCount());
            assertEquals(85, stat.getBottomInternalNodeCount());
            assertEquals(771, stat.getDuplicateBottomInternalNodeCount());
            assertEquals(2570, stat.getLeafNodeCount());
            assertEquals(0, stat.getDeletedLeafNodeCount());
            assertEquals(257, stat.getDupCountLeafNodeCount());
            assertEquals(4, stat.getMainTreeMaxDepth());
            assertEquals(2, stat.getDuplicateTreeMaxDepth());

	    txn.commit();
            myDb.close();
            env.close();
        } catch (Throwable t) {
            t.printStackTrace();
            throw t;
        }
    }

    public void testStatDeletes()
        throws Throwable {

	deleteTestInternal(1, 2, 0, 2);
	deleteTestInternal(2, 2, 2, 2);
	deleteTestInternal(10, 2, 10, 10);
	deleteTestInternal(11, 2, 10, 12);
    }

    private void deleteTestInternal(int numRecs,
				    int numDupRecs,
				    int expectedLNs,
				    int expectedDeletedLNs)
	throws Throwable {

        try {
	    TestUtils.removeLogFiles("Setup", envHome, false);
            Database myDb = initEnvAndDb(true, true, true, null);
            DatabaseEntry key = new DatabaseEntry();
            DatabaseEntry data = new DatabaseEntry();
	    Transaction txn = env.beginTransaction(null, null);
            for (int i = numRecs; i > 0; i--) {
                key.setData(TestUtils.getTestArray(i));
		for (int j = 0; j < numDupRecs; j++) {
		    data.setData(TestUtils.getTestArray(i + j));
		    assertEquals(OperationStatus.SUCCESS,
				 myDb.put(txn, key, data));
		}
            }

            for (int i = numRecs; i > 0; i -= 2) {
                key.setData(TestUtils.getTestArray(i));
                assertEquals(OperationStatus.SUCCESS,
			     myDb.delete(txn, key));
            }

            BtreeStats stat = (BtreeStats) myDb.getStats(null);

            assertEquals(expectedLNs, stat.getLeafNodeCount());
            assertEquals(expectedDeletedLNs, stat.getDeletedLeafNodeCount());
            assertEquals(numRecs, stat.getDupCountLeafNodeCount());

	    txn.commit();
            myDb.close();
            env.close();
        } catch (Throwable t) {
            t.printStackTrace();
            throw t;
        }
    }

    /**
     * Exercise the preload method, which warms up the cache.
     */
    public void testPreload()
        throws Throwable {

        /* Set up a test db */
        Database myDb = initEnvAndDb(false, false, true, null);
        DatabaseEntry key = new DatabaseEntry();
        DatabaseEntry data = new DatabaseEntry();
	Transaction txn = env.beginTransaction(null, null);
        for (int i = 2500; i > 0; i--) {
            key.setData(TestUtils.getTestArray(i));
            data.setData(TestUtils.getTestArray(i));
            assertEquals(OperationStatus.SUCCESS,
			 myDb.put(txn, key, data));
        }

        /* Recover the database, restart w/no evictor. */
        long postCreateMemUsage = env.getMemoryUsage();
        INList inlist = env.getEnvironmentImpl().getInMemoryINs();
        long postCreateResidentNodes = inlist.getSize();
	txn.commit();
        myDb.close();
        env.close();
        myDb = initEnvAndDb(true, false, true, "40000");

        /* 
         * Do two evictions, because the first eviction will only strip
         * LNs. We need to actually evict BINS because preload only pulls in
         * IN/BINs
         */
        env.evictMemory(); // first eviction strips LNS.
        env.evictMemory(); // second one will evict BINS

        long postEvictMemUsage = env.getMemoryUsage();
        inlist = env.getEnvironmentImpl().getInMemoryINs(); // re-get inList
        long postEvictResidentNodes = inlist.getSize();

        /* Now preload, but not up to the full size of the db */
        myDb.preload(100000);

        long postPreloadMemUsage = env.getMemoryUsage();
        long postPreloadResidentNodes = inlist.getSize();

        /* Now iterate to get everything back into memory */
        Cursor cursor = myDb.openCursor(null, null);
        int count = 0;
        OperationStatus status = cursor.getFirst(key, data, LockMode.DEFAULT);
        while (status == OperationStatus.SUCCESS) {
            count++;
            status = cursor.getNext(key, data, LockMode.DEFAULT);
        }
	cursor.close();

        long postIterationMemUsage = env.getMemoryUsage();
        long postIterationResidentNodes = inlist.getSize();

        if (DEBUG) {
            System.out.println("postCreateMemUsage: " + postCreateMemUsage);
            System.out.println("postEvictMemUsage: " + postEvictMemUsage);
            System.out.println("postPreloadMemUsage: " + postPreloadMemUsage);
            System.out.println("postIterationMemUsage: " +
                               postIterationMemUsage);
            System.out.println("postEvictResidentNodes: " +
                               postEvictResidentNodes);
            System.out.println("postPreloadResidentNodes: " +
                               postPreloadResidentNodes);
            System.out.println("postIterationResidentNodes: " +
                               postIterationResidentNodes);
            System.out.println("postCreateResidentNodes: " +
                               postCreateResidentNodes);
        }

        assertTrue(postEvictMemUsage < postCreateMemUsage);
        assertTrue(postEvictMemUsage < postPreloadMemUsage);
        assertTrue("postPreloadMemUsage=" + postPreloadMemUsage +
                   " postIterationMemUsage=" + postIterationMemUsage,
                   postPreloadMemUsage < postIterationMemUsage);
        assertTrue(postIterationMemUsage <= postCreateMemUsage);
        assertTrue(postEvictResidentNodes < postPreloadResidentNodes);
        //assertEquals(postCreateResidentNodes, postIterationResidentNodes);
        assertTrue(postCreateResidentNodes >= postIterationResidentNodes);

        myDb.close();
        env.close();
    }

    public void testDbClose()
        throws Throwable {

        /* Set up a test db */
        Database myDb = initEnvAndDb(false, false, true, null);
        DatabaseEntry key = new DatabaseEntry();
        DatabaseEntry data = new DatabaseEntry();
	Transaction txn = env.beginTransaction(null, null);
        for (int i = 2500; i > 0; i--) {
            key.setData(TestUtils.getTestArray(i));
            data.setData(TestUtils.getTestArray(i));
            assertEquals(OperationStatus.SUCCESS,
			 myDb.put(txn, key, data));
        }

	/* Create a cursor, use it, then close db without closing cursor. */
        Cursor cursor = myDb.openCursor(txn, null);
        OperationStatus status = cursor.getFirst(key, data, LockMode.DEFAULT);

	try {
	    myDb.close();
	    fail("didn't throw DatabaseException for unclosed cursor");
	} catch (DatabaseException DBE) {
	}
	txn.commit();
        env.close();
    }

    public void testDbCloseUnopenedDb()
	throws DatabaseException {

        EnvironmentConfig envConfig = new EnvironmentConfig();
        envConfig.setTransactional(true);
        envConfig.setAllowCreate(true);
        env = new Environment(envHome, envConfig);
	Database myDb = new Database(env);
	try {
	    myDb.close();
	} catch (DatabaseException DBE) {
	    fail("shouldn't catch DatabaseException for closing unopened db");
	}
	env.close();
    }

    /**
     * Test that open cursor isn't possible on a closed database.
     */
    public void testOpenCursor() 
        throws DatabaseException {
        Database db = initEnvAndDb(true, false, true, null);
        Cursor cursor = db.openCursor(null, null);
        cursor.close();
        db.close();
        try {
            db.openCursor(null, null);
            fail("Should throw exception because databse is closed");
        } catch (DatabaseException e) {
        }
    }

    /**
     * Set up the environment and db.
     */
    private Database initEnvAndDb(boolean dontRunEvictor,
                                  boolean allowDuplicates,
				  boolean transactional,
                                  String memSize)
        throws DatabaseException {

        EnvironmentConfig envConfig = new EnvironmentConfig();
        envConfig.setTransactional(transactional);
        envConfig.setConfigParam
            (EnvironmentParams.ENV_CHECK_LEAKS.getName(), "false");
        envConfig.setConfigParam(EnvironmentParams.NODE_MAX.getName(), "6");
        envConfig.setTxnNoSync(Boolean.getBoolean(TestUtils.NO_SYNC));
        if (dontRunEvictor) {
            envConfig.setConfigParam(EnvironmentParams.
                                     ENV_RUN_EVICTOR.getName(),
                                     "false");
            envConfig.setConfigParam
		(EnvironmentParams.EVICTOR_EVICTION_BATCH_PERCENTAGE.getName(),
		 "100");
            envConfig.setConfigParam(EnvironmentParams.
                                     EVICTOR_NODE_SCAN_PERCENTAGE.getName(),
                                     "100");
            /* 
             * Don't let critical eviction run or it will interfer with the
             * preload test.
             */
            envConfig.setConfigParam(EnvironmentParams.
                                     EVICTOR_CRITICAL_PERCENTAGE.getName(),
                                     "500");
        }

        if (memSize != null) {
            envConfig.setConfigParam(EnvironmentParams.
                                     MAX_MEMORY.getName(),
                                     memSize);
        }

        envConfig.setAllowCreate(true);
        env = new Environment(envHome, envConfig);

        // Make a db and open it
        DatabaseConfig dbConfig = new DatabaseConfig();
        dbConfig.setSortedDuplicates(allowDuplicates);
        dbConfig.setAllowCreate(true);
        dbConfig.setTransactional(transactional);
        Database myDb = env.openDatabase(null, "testDB", dbConfig);
        return myDb;
    }

    /**
     * X'd out because this is expected to be used in the debugger to set
     * specific breakpoints and step through in a synchronous manner.
     */
    private Database pNOCDb;

    public void xxtestPutNoOverwriteConcurrently()
	throws Throwable {

	pNOCDb = initEnvAndDb(true, true, true, null);
	JUnitThread tester1 =
	    new JUnitThread("testNonBlocking1") {
		public void testBody() {
		    try {
			Transaction txn1 = env.beginTransaction(null, null);
			DatabaseEntry key = new DatabaseEntry();
			DatabaseEntry data = new DatabaseEntry();
			key.setData(TestUtils.getTestArray(1));
			data.setData(TestUtils.getTestArray(1));
			OperationStatus status =
			    pNOCDb.putNoOverwrite(txn1, key, data);
			txn1.commit();
			System.out.println("thread1: " + status);
		    } catch (DatabaseException DBE) {
			DBE.printStackTrace();
			fail("caught DatabaseException " + DBE);
		    }
		}
	    };

	JUnitThread tester2 =
	    new JUnitThread("testNonBlocking2") {
		public void testBody() {
		    try {
			Transaction txn2 = env.beginTransaction(null, null);
			DatabaseEntry key = new DatabaseEntry();
			DatabaseEntry data = new DatabaseEntry();
			key.setData(TestUtils.getTestArray(1));
			data.setData(TestUtils.getTestArray(2));
			OperationStatus status =
			    pNOCDb.putNoOverwrite(txn2, key, data);
			txn2.commit();
			System.out.println("thread2: " + status);
		    } catch (DatabaseException DBE) {
                        DBE.printStackTrace();
			fail("caught DatabaseException " + DBE);
		    }
		}
	    };

	tester1.start();
	tester2.start();
	tester1.finishTest();
	tester2.finishTest();
    }
}
