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

package com.sleepycat.je.cleaner;

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

import junit.framework.TestCase;

import com.sleepycat.je.Cursor;
import com.sleepycat.je.Database;
import com.sleepycat.je.DatabaseConfig;
import com.sleepycat.je.DatabaseEntry;
import com.sleepycat.je.DatabaseException;
import com.sleepycat.je.DbInternal;
import com.sleepycat.je.Environment;
import com.sleepycat.je.EnvironmentConfig;
import com.sleepycat.je.OperationStatus;
import com.sleepycat.je.Transaction;
import com.sleepycat.je.cleaner.FileSummary;
import com.sleepycat.je.config.EnvironmentParams;
import com.sleepycat.je.log.FileManager;
import com.sleepycat.je.util.TestUtils;

public class TruncateAndRemoveTest extends TestCase {

    private static final String DB_NAME = "foo";
    private static final int RECORD_COUNT = 100;

    private File envHome;
    private Environment env;
    private Database db;

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

    public void setUp()
        throws IOException, DatabaseException {

        TestUtils.removeLogFiles("Setup", envHome, false);
        TestUtils.removeFiles("Setup", envHome, FileManager.DEL_SUFFIX);
    }

    public void tearDown()
        throws IOException, DatabaseException {

        try {
            if (env != null) {
                env.close();
            }
        } catch (Throwable e) {
            System.out.println("tearDown: " + e);
        }
                
        try {
            TestUtils.removeLogFiles("tearDown", envHome, true);
            TestUtils.removeFiles("tearDown", envHome, FileManager.DEL_SUFFIX);
        } catch (Throwable e) {
            System.out.println("tearDown: " + e);
        }

        db = null;
        env = null;
        envHome = null;
    }

    /**
     * Opens the environment and database.
     */
    private void openEnv()
        throws DatabaseException {

        EnvironmentConfig config = new EnvironmentConfig();
        config.setTransactional(true);
        config.setAllowCreate(true);
        /* Do not run the daemons since they interfere with LN counting. */
        config.setConfigParam
            (EnvironmentParams.ENV_RUN_CLEANER.getName(), "false");
        config.setConfigParam
            (EnvironmentParams.ENV_RUN_EVICTOR.getName(), "false");
        config.setConfigParam
	    (EnvironmentParams.ENV_RUN_CHECKPOINTER.getName(), "false");
        config.setConfigParam
            (EnvironmentParams.ENV_RUN_INCOMPRESSOR.getName(), "false");
        env = new Environment(envHome, config);

        openDb();
    }

    /**
     * Opens that database.
     */
    private void openDb()
        throws DatabaseException {

        DatabaseConfig dbConfig = new DatabaseConfig();
        dbConfig.setTransactional(true);
        dbConfig.setAllowCreate(true);
        db = env.openDatabase(null, DB_NAME, dbConfig);
    }

    /**
     * Closes the environment and database.
     */
    private void closeEnv()
        throws DatabaseException {

        if (db != null) {
            db.close();
            db = null;
        }
        if (env != null) {
            env.close();
            env = null;
        }
    }

    /**
     * Test that truncate generates the right number of obsolete LNs.
     */
    public void testTruncate()
        throws DatabaseException {

        openEnv();

        writeRecords();
        assertEquals(RECORD_COUNT, countRecords());

        int count1 = getObsoleteLNCount();
        int nTruncated = db.truncate(null, true);
        assertEquals(RECORD_COUNT, nTruncated);
        assertEquals(0, countRecords());
        int count2 = getObsoleteLNCount();

        assertTrue("count1=" + count1 + " count2=" + count2,
                    count2 - count1 > RECORD_COUNT);

        closeEnv();
    }

    /**
     * Test that aborting truncate generates the right number of obsolete LNs.
     */
    public void testTruncateAbort()
        throws DatabaseException {

        openEnv();

        writeRecords();
        assertEquals(RECORD_COUNT, countRecords());

        int count1 = getObsoleteLNCount();
        Transaction txn = env.beginTransaction(null, null);
        int nTruncated = db.truncate(txn, true);
        assertEquals(RECORD_COUNT, nTruncated);
        assertEquals(0, countRecords());
        txn.abort();

        /* Abort after truncate closes the db -- reopen it. */
        openDb();

        assertEquals(RECORD_COUNT, countRecords());

        int count2 = getObsoleteLNCount();
        assertTrue("count1=" + count1 + " count2=" + count2,
                    count2 - count1 <= 2);

        closeEnv();
    }

    /**
     * Test that remove generates the right number of obsolete LNs.
     */
    public void testRemove()
        throws DatabaseException {

        openEnv();

        writeRecords();
        assertEquals(RECORD_COUNT, countRecords());

        int count1 = getObsoleteLNCount();
        db.close();
        db = null;
        env.removeDatabase(null, DB_NAME);
        try {
            env.openDatabase(null, DB_NAME, null);
            fail();
        } catch (DatabaseException expected) {}
        int count2 = getObsoleteLNCount();

        openDb();
        assertEquals(0, countRecords());

        assertTrue("count1=" + count1 + " count2=" + count2,
                    count2 - count1 > RECORD_COUNT);

        closeEnv();
    }

    /**
     * Test that aborting remove generates the right number of obsolete LNs.
     */
    public void testRemoveAbort()
        throws DatabaseException {

        openEnv();

        writeRecords();
        assertEquals(RECORD_COUNT, countRecords());

        int count1 = getObsoleteLNCount();
        db.close();
        db = null;
        Transaction txn = env.beginTransaction(null, null);
        env.removeDatabase(txn, DB_NAME);
        txn.abort();
        int count2 = getObsoleteLNCount();

        openDb();
        assertEquals(RECORD_COUNT, countRecords());

        assertTrue("count1=" + count1 + " count2=" + count2,
                    count2 - count1 <= 2);

        closeEnv();
    }

    /**
     * Writes RECORD_COUNT records.
     */
    private void writeRecords()
        throws DatabaseException {

        for (int i = 0; i < RECORD_COUNT; i += 1) {
            DatabaseEntry entry = new DatabaseEntry(TestUtils.getTestArray(i));
            db.put(null, entry, entry);
        }
    }

    /**
     * Returns how many records are in the database.
     */
    private int countRecords()
        throws DatabaseException {

        DatabaseEntry key = new DatabaseEntry();
        DatabaseEntry data = new DatabaseEntry();
        Cursor cursor = db.openCursor(null, null);
        try {
            int count = 0;
            OperationStatus status = cursor.getFirst(key, data, null);
            while (status == OperationStatus.SUCCESS) {
                count += 1;
                status = cursor.getNext(key, data, null);
            }
            return count;
        } finally {
            cursor.close();
        }
    }

    /**
     * Return the total number of obsolete LNs according to the
     * UtilizationProfile and UtilizationTracker.
     */
    private int getObsoleteLNCount()
        throws DatabaseException {

        FileSummary[] files = (FileSummary[])
            DbInternal.envGetEnvironmentImpl(env)
                      .getUtilizationProfile()
                      .getFileSummaryMap(true)
                      .values().toArray(new FileSummary[0]);
        int count = 0;
        for (int i = 0; i < files.length; i += 1) {
            count += files[i].obsoleteLNCount;
        }
        return count;
    }
}
