/*-
 * See the file LICENSE for redistribution information.
 *
 * Copyright (c) 2000-2002
 *	Sleepycat Software.  All rights reserved.
 *
 * $Id: TestConstruct02.java,v 1.6 2002/08/16 19:35:54 dda Exp $
 */

/*
 * Do some regression tests for constructors.
 * Run normally (without arguments) it is a simple regression test.
 * Run with a numeric argument, it repeats the regression a number
 * of times, to try to determine if there are memory leaks.
 */

package com.sleepycat.test;
import com.sleepycat.db.*;
import java.io.File;
import java.io.IOException;
import java.io.FileNotFoundException;

public class TestConstruct02
{
    public static final String CONSTRUCT02_DBNAME  =       "construct02.db";
    public static final String CONSTRUCT02_DBDIR   =       "./";
    public static final String CONSTRUCT02_DBFULLPATH   =
	CONSTRUCT02_DBDIR + "/" + CONSTRUCT02_DBNAME;

    private int itemcount;	// count the number of items in the database
    public static boolean verbose_flag = false;

    private DbEnv dbenv = new DbEnv(0);

    public TestConstruct02()
        throws DbException, FileNotFoundException
    {
        dbenv.open(CONSTRUCT02_DBDIR, Db.DB_CREATE | Db.DB_INIT_MPOOL, 0666);
    }

    public void close()
    {
        try {
            dbenv.close(0);
            removeall(true, true);
        }
        catch (DbException dbe) {
            ERR("DbException: " + dbe);
        }
    }

    public static void ERR(String a)
    {
	System.out.println("FAIL: " + a);
	sysexit(1);
    }

    public static void DEBUGOUT(String s)
    {
	System.out.println(s);
    }

    public static void VERBOSEOUT(String s)
    {
        if (verbose_flag)
            System.out.println(s);
    }

    public static void sysexit(int code)
    {
	System.exit(code);
    }

    private static void check_file_removed(String name, boolean fatal,
					   boolean force_remove_first)
    {
	File f = new File(name);
	if (force_remove_first) {
	    f.delete();
	}
	if (f.exists()) {
	    if (fatal)
		System.out.print("FAIL: ");
	    System.out.print("File \"" + name + "\" still exists after run\n");
	    if (fatal)
		sysexit(1);
	}
    }


    // Check that key/data for 0 - count-1 are already present,
    // and write a key/data for count.  The key and data are
    // both "0123...N" where N == count-1.
    //
    void rundb(Db db, int count)
	throws DbException, FileNotFoundException
    {
        if (count >= 64)
	    throw new IllegalArgumentException("rundb count arg >= 64");

	// The bit map of keys we've seen
	long bitmap = 0;

	// The bit map of keys we expect to see
	long expected = (1 << (count+1)) - 1;

	byte outbuf[] = new byte[count+1];
	int i;
	for (i=0; i<count; i++) {
	    outbuf[i] = (byte)('0' + i);
	}
	outbuf[i++] = (byte)'x';

	Dbt key = new Dbt(outbuf, 0, i);
	Dbt data = new Dbt(outbuf, 0, i);

	db.put(null, key, data, Db.DB_NOOVERWRITE);

	// Acquire a cursor for the table.
	Dbc dbcp = db.cursor(null, 0);

	// Walk through the table, checking
	Dbt readkey = new Dbt();
	Dbt readdata = new Dbt();
	Dbt whoknows = new Dbt();

	readkey.set_flags(Db.DB_DBT_MALLOC);
	readdata.set_flags(Db.DB_DBT_MALLOC);

	while (dbcp.get(readkey, readdata, Db.DB_NEXT) == 0) {
            byte[] key_bytes = readkey.get_data();
            byte[] data_bytes = readdata.get_data();

	    int len = key_bytes.length;
	    if (len != data_bytes.length) {
		ERR("key and data are different");
	    }
	    for (i=0; i<len-1; i++) {
		byte want = (byte)('0' + i);
		if (key_bytes[i] != want || data_bytes[i] != want) {
		    System.out.println(" got " + new String(key_bytes) +
				       "/" + new String(data_bytes));
		    ERR("key or data is corrupt");
		}
            }
	    if (len <= 0 ||
                key_bytes[len-1] != (byte)'x' ||
                data_bytes[len-1] != (byte)'x') {
		ERR("reread terminator is bad");
	    }
	    len--;
	    long bit = (1 << len);
	    if (len > count) {
		ERR("reread length is bad: expect " + count + " got "+ len);
	    }
	    else if ((bitmap & bit) != 0) {
		ERR("key already seen");
	    }
	    else if ((expected & bit) == 0) {
		ERR("key was not expected");
	    }
	    bitmap |= bit;
	    expected &= ~(bit);
	}
	if (expected != 0) {
	    System.out.print(" expected more keys, bitmap is: " +
			     expected + "\n");
	    ERR("missing keys in database");
	}
	dbcp.close();
    }

    void t1()
	throws DbException, FileNotFoundException
    {
	Db db = new Db(dbenv, 0);
	db.set_error_stream(System.err);
	db.set_pagesize(1024);
	db.open(null, CONSTRUCT02_DBNAME, null, Db.DB_BTREE,
		Db.DB_CREATE, 0664);

	rundb(db, itemcount++);
	rundb(db, itemcount++);
	rundb(db, itemcount++);
	rundb(db, itemcount++);
	rundb(db, itemcount++);
	rundb(db, itemcount++);
        db.close(0);

        // Reopen no longer allowed, so we create a new db.
	db = new Db(dbenv, 0);
	db.set_error_stream(System.err);
	db.set_pagesize(1024);
	db.open(null, CONSTRUCT02_DBNAME, null, Db.DB_BTREE,
		Db.DB_CREATE, 0664);
	rundb(db, itemcount++);
	rundb(db, itemcount++);
	rundb(db, itemcount++);
	rundb(db, itemcount++);
        db.close(0);
    }

    // remove any existing environment or database
    void removeall(boolean use_db, boolean remove_env)
    {
	{
            try {
                if (remove_env) {
                    DbEnv tmpenv = new DbEnv(0);
                    tmpenv.remove(CONSTRUCT02_DBDIR, Db.DB_FORCE);
                }
                else if (use_db) {
		    /**/
		    //memory leak for this:
		    Db tmpdb = new Db(null, 0);
		    tmpdb.remove(CONSTRUCT02_DBFULLPATH, null, 0);
		    /**/
		}
            }
            catch (DbException dbe) {
                System.err.println("error during remove: " + dbe);
            }
            catch (FileNotFoundException dbe) {
                System.err.println("error during remove: " + dbe);
            }
	}
	check_file_removed(CONSTRUCT02_DBFULLPATH, true, !use_db);
        if (remove_env) {
            for (int i=0; i<8; i++) {
                String fname = "__db.00" + i;
                check_file_removed(fname, true, !use_db);
            }
	}
    }

    boolean doall()
    {
	itemcount = 0;
	try {
	    VERBOSEOUT("  Running test 1:\n");
	    t1();
	    VERBOSEOUT("  finished.\n");
	    removeall(true, false);
	    return true;
	}
	catch (DbException dbe) {
	    ERR("EXCEPTION RECEIVED: " + dbe);
	}
	catch (FileNotFoundException fnfe) {
	    ERR("EXCEPTION RECEIVED: " + fnfe);
	}
	return false;
    }

    public static void main(String args[])
    {
	int iterations = 200;

	for (int argcnt=0; argcnt<args.length; argcnt++) {
	    String arg = args[argcnt];
	    try {
		iterations = Integer.parseInt(arg);
		if (iterations < 0) {
		    ERR("Usage:  construct02 [-testdigits] count");
		}
	    }
	    catch (NumberFormatException nfe) {
		ERR("EXCEPTION RECEIVED: " + nfe);
	    }
	}

        System.gc();
        System.runFinalization();
        VERBOSEOUT("gc complete");
        long starttotal = Runtime.getRuntime().totalMemory();
        long startfree = Runtime.getRuntime().freeMemory();
        TestConstruct02 con = null;

        try {
            con = new TestConstruct02();
        }
        catch (DbException dbe) {
            System.err.println("Exception: " + dbe);
            System.exit(1);
        }
        catch (java.io.FileNotFoundException fnfe) {
            System.err.println("Exception: " + fnfe);
            System.exit(1);
        }

	for (int i=0; i<iterations; i++) {
	    if (iterations != 0) {
		VERBOSEOUT("(" + i + "/" + iterations + ") ");
	    }
	    VERBOSEOUT("construct02 running:\n");
	    if (!con.doall()) {
		ERR("SOME TEST FAILED");
	    }
	    System.gc();
            System.runFinalization();
	    VERBOSEOUT("gc complete");

        }
        con.close();

        System.out.print("ALL TESTS SUCCESSFUL\n");

        long endtotal = Runtime.getRuntime().totalMemory();
        long endfree = Runtime.getRuntime().freeMemory();

        System.out.println("delta for total mem: " + magnitude(endtotal - starttotal));
        System.out.println("delta for free mem: " + magnitude(endfree - startfree));

	return;
    }

    static String magnitude(long value)
    {
        final long max = 10000000;
        for (long scale = 10; scale <= max; scale *= 10) {
            if (value < scale && value > -scale)
                return "<" + scale;
        }
        return ">" + max;
    }
}
