/*
 * Copyright (C) 2008 The Android Open Source Project
 *
 * Licensed 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 tests.SQLite;

import dalvik.annotation.AndroidOnly;
import dalvik.annotation.KnownFailure;
import dalvik.annotation.TestLevel;
import dalvik.annotation.TestTargetClass;
import dalvik.annotation.TestTargetNew;
import dalvik.annotation.TestTargets;
import tests.support.DatabaseCreator;
import tests.support.MockFunction;
import tests.support.ThreadPool;
import tests.support.resource.Support_Resources;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.net.URL;
import java.sql.DatabaseMetaData;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;

import SQLite.Authorizer;
import SQLite.Blob;
import SQLite.BusyHandler;
import SQLite.Callback;
import SQLite.Constants;
import SQLite.Database;
import SQLite.Exception;
import SQLite.Function;
import SQLite.FunctionContext;
import SQLite.ProgressHandler;
import SQLite.Stmt;
import SQLite.TableResult;
import SQLite.Trace;
import SQLite.Vm;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.logging.Logger;

@TestTargetClass(Database.class)
public class DatabaseTest extends SQLiteTest {

    /**
     * The SQLite db file.
     */
//    protected final File dbFile = new File("sqliteTest.db");
//
//    private final String connectionURL = "jdbc:sqlite:/" + dbFile.getPath();
//
//    private final String classname = "SQLite.JDBCDriver";
//
//    private static Connection conn = null;

    private static ErrorTracker tracker = null;

    private Statement statement;

    private Database db = null;

    private static final int numThreads = 10;

    private static final int numOfRecords = 30;

    public void setUp() throws java.lang.Exception {
        try {
            super.setUp();
            assertNotNull("Could not establish DB connection",conn);
            tracker = new ErrorTracker();

            statement = conn.createStatement();

          //Cleanup tables if necessary

            DatabaseMetaData meta = conn.getMetaData();
            assertNotNull(meta);
            if (meta != null) {
            ResultSet userTab = meta.getTables(null, null, null, null);
            while (userTab.next()) {
            String tableName = userTab.getString("TABLE_NAME");
               this.statement.execute("drop table "+tableName);
            }
            }

            // Create default test table
//            statement = conn.createStatement();
            statement.execute(DatabaseCreator.CREATE_TABLE_SIMPLE1);
            statement.close();

            try {
            db = new Database();
            db.open(dbFile.getPath(), 0);
            db.busy_handler(null);
            } catch (Exception e) {
                System.out.println("2: Error opening File: Dir "+dbFile.getPath()+" Name: "+dbFile.getPath());
            } catch (java.lang.Exception e) {
                System.err.println("Non SQLException "+e.getMessage());
            }
        } catch (Exception e) {
            System.out.println("Database setup fails: "+e.getMessage());
            e.printStackTrace();
        }

    }

    public void tearDown() {

        try {
            db.close();
        }catch (Exception e) {
            if (! (e.getMessage().equals("database already closed"))) {
                System.err.println("Error closing DB "+dbFile.getPath());
            }
        }
//        conn.close();
//        dbFile.delete();
        tracker.reset();
        super.tearDown();
    }

    /**
     * @tests Database#Database()
     */
    @TestTargetNew(
        level = TestLevel.COMPLETE,
        notes = "constructor test",
        method = "Database",
        args = {}
    )
    public void testDatabase() {
        // db closed
        Database db2 = new Database();
        try {
            db.close();
            db2 = new Database();
            db2.open(dbFile.getPath(), 0);
            db2.close();
            db.open(dbFile.getPath(), 0);
        } catch (Exception e) {
            fail("Database object could not be created "+e.getMessage());
            e.printStackTrace();
        }
        //db is open
        try {
            db2.open(dbFile.getPath(), 0);
            db2.close();
        } catch (Exception e) {
            fail("Second Database object could not be created "+e.getMessage());
            e.printStackTrace();
        }
    }

    /**
     * @tests Database#finalize()
     */
    @TestTargetNew(
        level = TestLevel.NOT_FEASIBLE,
        notes = "method test",
        method = "finalize",
        args = {}
    )
    public void testFinalize() {
    }

    /**
     * @tests {@link Database#open(String, int)}.
     */
    @TestTargetNew(
        level = TestLevel.COMPLETE,
        notes = "method test. Test fails.",
        method = "open",
        args = {java.lang.String.class, int.class}
    )
    public void testOpen() {
        try {
            db.close();
            db.open(dbFile.getPath(), 0);
        } catch (Exception e) {
            fail("Database object could not be opened: " + e.getMessage());
            e.printStackTrace();
        }
        // open second db while db1 still open
        Database db2 = new Database();
        try {
            db2.open(dbFile.getPath(), 0);
            db2.open(dbFile.getPath(), 0);
            db2.close();
        } catch (Exception e) {
            fail("Database object could not be opened: " + e.getMessage());
            e.printStackTrace();
        }
        // open non db file
        File tempDir = Support_Resources.createTempFolder();
        final String resourceName = "blob.c";
        try {
            URL file = Class.forName(this.getClass().getName())
            .getResource("/blob.c");
            db2.open(file.getPath(), 0);
           fail("Should not be able to open non db file");
        } catch (Exception e) {
            assertEquals("unknown error in open", e.getMessage());
        } catch (java.lang.Exception e) {
            fail("Error in setup " + e.getMessage());
            e.printStackTrace();
        }

    }

    /**
     * @tests Database#open_aux_file(String)
     */
    @TestTargetNew(
        level = TestLevel.SUFFICIENT,
        notes = "not supported",
        method = "open_aux_file",
        args = {java.lang.String.class}
    )
    public void testOpen_aux_file() {
        File temp = null;
        try {
            db.open_aux_file("");
            fail("open should fail");
        } catch (Exception e) {
            assertEquals("unsupported", e.getMessage());
        }

     /*
        try {
            temp = File.createTempFile("openAuxMethod", ".db");
            db.open_aux_file("");
            db.exec("create table AUX_TABLE", null);
            db.close();
        } catch (Exception e) {
            temp.delete();
            fail("Error handling temporary file "+e.getMessage());
            e.printStackTrace();
        } catch (IOException e) {
            temp.delete();
            fail("Could not create temporary File");
            e.printStackTrace();
        }
        try {
            db.open(dbFile.getPath(),0);
            db.exec("select * from AUX_TABLE", null);
            fail("Statement should fail");
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

        temp.delete();
        */
    }

    /**
     * @tests Database#close()
     */
    @TestTargetNew(
        level = TestLevel.COMPLETE,
        notes = "method test",
        method = "close",
        args = {}
    )
    public void testClose() {
        try {
            db.close();
            db.get_table("test");
        } catch (Exception e) {
            assertTrue(e.getMessage().equals("database already closed"));
            try {
                db.open(dbFile.getPath(), 0);
            } catch (Exception e1) {
                fail("Database object could not be reopened after 'close': "
                        + e.getMessage());
                e1.printStackTrace();
            }
        }

        try {
            db.close();
            db.close();
        } catch (Exception e) {
            assertTrue(e.getMessage().equals("database already closed"));
            try {
                db.open(dbFile.getPath(), 0);
            } catch (Exception e1) {
                fail("Database object could not be reopened after 'close': "
                        + e.getMessage());
                e1.printStackTrace();
            }
        }
    }

    /**
     * @tests Database#exec(String, Callback)
     */
    @TestTargetNew(
        level = TestLevel.COMPLETE,
        notes = "method test",
        method = "exec",
        args = {java.lang.String.class, Callback.class}
    )
    public void testExecStringCallback() {
        TableResult res = new TableResult();
        try {
            db.exec("insert into " + DatabaseCreator.SIMPLE_TABLE1
                    + " VALUES(1, 10, 20)", null);
            db.exec("select * from " + DatabaseCreator.SIMPLE_TABLE1, res);
            db
                    .exec("delete from " + DatabaseCreator.SIMPLE_TABLE1
                            + " where 1", null);
        } catch (Exception e) {
            fail("Database error");
            e.printStackTrace();
        }
        String row[] = (String[]) res.rows.elementAt(0);
        assertEquals(Integer.parseInt(row[0]), 1);
        assertEquals(Integer.parseInt(row[1]), 10);
        assertEquals(Integer.parseInt(row[2]), 20);
    }

    /**
     * @tests Database#exec(String, Callback, String[])
     */
    @TestTargetNew(
        level = TestLevel.COMPLETE,
        notes = "method test",
        method = "exec",
        args = {java.lang.String.class, Callback.class, java.lang.String[].class}
    )
    public void testExecStringCallbackStringArray() {
        TableResult res = new TableResult();
        String args[] = new String[1];
        args[0] = "table";
        try {
            db.exec("select name from sqlite_master where type = '%q';", res,
                    args);
            String[] s = (String[]) res.rows.elementAt(0);
            assertEquals(s[0], DatabaseCreator.SIMPLE_TABLE1);
        } catch (Exception e) {
            fail("DB Error occurred");
            e.printStackTrace();
        }

        try {
            db.exec("select name from sqlite_master where type = ", res, args);
            fail("Testmethod should fail");
        } catch (Exception e) {
            // Ok
        }
    }

    /**
     * @tests {@link Database#last_insert_rowid()}
     */
    @TestTargetNew(
        level = TestLevel.COMPLETE,
        notes = "method test",
        method = "last_insert_rowid",
        args = {}
    )
    public void testLast_insert_rowid() {
        assertEquals(0, db.last_insert_rowid());
        try {
            db
                    .exec(
                            "create table TEST5(id integer, firstname text, lastname text);",
                            null);
            db.exec("insert into TEST5 values (1,'James','Bond');", null);
            db.exec("insert into TEST5 values (2,'Fiona','Apple');", null);
        } catch (Exception e) {
            fail("Error in test setup: " + e.getMessage());
            e.printStackTrace();
        }
        assertEquals(2, db.last_insert_rowid());
        assertEquals(db.last_insert_rowid(), db.last_insert_rowid());

        try {
            db.exec("drop table TEST5;", null);
        } catch (Exception e) {
            fail("Error in test setup: " + e.getMessage());
            e.printStackTrace();
        }
        assertEquals(2, db.last_insert_rowid());
    }

    /**
     * @throws Exception
     * @tests {@link Database#interrupt()}
     */
    @TestTargetNew(
        level = TestLevel.COMPLETE,
        notes = "",
        method = "interrupt",
        args = {}
    )
    @KnownFailure("Reason for failure unknown: Database should be locked. " +
                   "Specification of interrupt is scarce.")
    public void testInterrupt() throws Exception {
        ThreadPool threadPool = new ThreadPool(numThreads);

        // initialization
        ResultSet userTabs;
        try {
            userTabs = conn.getMetaData().getTables(null, null, null, null);
            while (userTabs.next()) {
                String tableName = userTabs.getString("TABLE_NAME");
                if (tableName.equals(DatabaseCreator.TEST_TABLE1)) {
                    statement.execute(DatabaseCreator.DROP_TABLE1);
                }
            }
            db.exec(DatabaseCreator.CREATE_TABLE3, null);
            db.exec(DatabaseCreator.CREATE_TABLE1, null);
        } catch (SQLException e1) {
            fail("Error initializing test " + e1.toString());
            e1.printStackTrace();
        } catch (Exception e) {
            fail("Error initializing test " + e.getMessage());
            e.printStackTrace();
        }

        int id1 = numOfRecords - 3;
        threadPool.runTask(createTask1(id1, dbFile.getPath(), tracker));
        // should not be able to do any other insertions since task 1 holds lock
        int id2 = numOfRecords + 3;
        threadPool
                .runTask(createTask2Interrupt(id2, dbFile.getPath(), tracker));

        threadPool.join();

        List<String> errors = tracker.getErrors();
        System.out.println("Last error: " + db.error_message());
        if (errors.size() > 0) {
            assertEquals(errors.get(0), db
                    .error_string(Constants.SQLITE_LOCKED));
            for (String s : errors) {
                Logger.global.info("INTERRUPT Error: " + s);
            }

        } else {
            fail("Should have one exception: database should be locked.");
        }

        // reset

        db
                .exec(
                        "delete from " + DatabaseCreator.TEST_TABLE1
                                + " where 1", null);
        db
                .exec(
                        "delete from " + DatabaseCreator.TEST_TABLE3
                                + " where 1", null);

    }

    /**
     * @tests {@link Database#changes()}
     */
    @TestTargetNew(
        level = TestLevel.COMPLETE,
        notes = "test fails",
        method = "changes",
        args = {}
    )
    @KnownFailure("Returns wrong number for updates: returns value > 1 for select.")
    public void testChanges() {
        TableResult res = new TableResult();
        try {
            assertTrue(db.changes() == 0);
            db.exec("INSERT INTO " + DatabaseCreator.SIMPLE_TABLE1
                    + " VALUES(2, 5, 7);", null);
            int rows = (int) db.changes();
            assertEquals(1,db.changes());
            db.exec("update " + DatabaseCreator.SIMPLE_TABLE1
                    + " set speed = 7, size= 5 where id = 2;", null);
            assertEquals(1,db.changes());
            db.exec("select * from " + DatabaseCreator.SIMPLE_TABLE1, res);
            assertEquals(0,db.changes());
            db.exec("INSERT INTO " + DatabaseCreator.SIMPLE_TABLE1
                    + " VALUES(8, 5, 7);", null);
            db.exec("Update "+DatabaseCreator.SIMPLE_TABLE1+" set speed = 10;",null);
           assertTrue(db.changes() > 2);
        } catch (Exception e) {
            fail("Could not get changes: " + e.getMessage());
            e.printStackTrace();
        }
    }

    /**
     * @throws SQLException
     * @throws Exception
     * @tests {@link Database#busy_handler(BusyHandler)}
     */
    @TestTargets({
    @TestTargetNew(
        level = TestLevel.NOT_FEASIBLE,
        notes = "method test fails once in a while. Cannot be sure that exception is thrown every time.",
        method = "busy_handler",
        args = {BusyHandler.class}
    ),
    @TestTargetNew(
            level = TestLevel.NOT_FEASIBLE,
            notes = "method test fails once in a while. Cannot be sure that exception is thrown every time.",
            method = "busy",
            clazz = BusyHandler.class,
            args = {java.lang.String.class, int.class}
        )
    })
    @KnownFailure("method test fails once in a while. "+
            "Cannot be sure that exception is thrown in every test execution.")
    public void testBusy_handler() throws SQLException, Exception {
        TestBusyHandler bh = new TestBusyHandler();
        db.busy_handler(bh);
        int counter = 0;
        ThreadPool threadPool = new ThreadPool(numThreads);

        // initialization
        ResultSet userTabs;
        try {
            userTabs = conn.getMetaData().getTables(null, null, null, null);
            while (userTabs.next()) {
                String tableName = userTabs.getString("TABLE_NAME");
                if (tableName.equals(DatabaseCreator.TEST_TABLE1)) {
                    statement.execute(DatabaseCreator.DROP_TABLE1);
                }
            }
            db.exec(DatabaseCreator.CREATE_TABLE3, null);
            db.exec(DatabaseCreator.CREATE_TABLE1, null);
        } catch (SQLException e1) {
            fail("Error initializing test " + e1.toString());
            e1.printStackTrace();
        } catch (Exception e) {
            fail("Error initializing test " + e.getMessage());
            e.printStackTrace();
        }


//        try {
//            DatabaseCreator.fillTestTable1(conn, numOfRecords);
            // set to fail immediately if table is locked.
//            db.busy_handler(bh);
//            db.busy_timeout(0);
        try {
            conn.setAutoCommit(false);
            int id1 = numOfRecords - 3;
            threadPool.runTask(createTask1(id1, dbFile.getPath(), tracker));
            int id2 = numOfRecords + 3;
            threadPool.runTask(createTask2(id2, dbFile.getPath(), tracker));
            int oldID = 5;
            int newID = 100;
            threadPool.runTask(createTask3(oldID, dbFile.getPath(), newID,
                    tracker));

            threadPool.join();

            List<String> errors = tracker.getErrors();
            if (errors.size() > 0) {
//                 assertEquals(errors.get(0),
//                 db.error_string(Constants.SQLITE_LOCKED));
                for (String s: errors) {
                  System.out.println("Round 2 Error: "+s);
              }
            } else {
                fail("No error happened");
            }

            // reset


            db.exec("delete from " + DatabaseCreator.TEST_TABLE1 + " where 1",
                    null);
            db.exec("delete from " + DatabaseCreator.TEST_TABLE3 + " where 1",
                            null);
//
//            // increase timeout for retry
//            db.busy_timeout(1000);
//            db.busy_handler(bh);
//            tracker.reset();

//            threadPool = new ThreadPool(numThreads);
//
//            threadPool.runTask(createTask1(id1, dbFile.getPath(), tracker));
//            threadPool.runTask(createTask2(id2, dbFile.getPath(), tracker));
//
//            threadPool.join();
//
//            errors = tracker.getErrors();
//            if (errors.size() > 0) {
//                // assertEquals(errors.get(0),
//                // db.error_string(Constants.SQLITE_LOCKED));
//                for (String s: errors) {
//                    System.out.println("Round 2 Error"+s);
//                }
//            } else {
//                // ok
//                System.out.println("BUSY: No Error!");
//            }
//
//
        } catch (Exception e) {
            fail("Error in test setup " + e.getMessage());
            try {
                db.get_table("select * from " + DatabaseCreator.TEST_TABLE1,
                        null).toString();
            } catch (Exception e1) {

                e1.printStackTrace();
            }
            e.printStackTrace();
//             } catch (SQLException e2) {
//             System.out.println("Error in test setup "+e2.toString());
//             try {
//             db.get_table("select * from "+DatabaseCreator.TEST_TABLE1,null).
//             toString();
//             } catch (Exception e1) {
//             e2.printStackTrace();
//             }
        } finally {
            conn.setAutoCommit(true);
            db.exec(DatabaseCreator.DROP_TABLE1, null);
            db.exec(DatabaseCreator.DROP_TABLE3, null);
        }
    }

    /**
     * @throws Exception
     * @throws SQLException
     * @tests {@link Database#busy_timeout(int)}
     */
    @TestTargetNew(
        level = TestLevel.NOT_FEASIBLE,
        notes = "test fails. Cannot be sure that exception is thrown every time.",
        method = "busy_timeout",
        args = {int.class}
    )
    @KnownFailure("Database does not lock values")
    public void testBusy_timeout() throws Exception, SQLException {
        int counter = 0;
        ThreadPool threadPool = new ThreadPool(numThreads);

        // initialization
        ResultSet userTabs;
        try {
            userTabs = conn.getMetaData().getTables(null, null, null, null);
            while (userTabs.next()) {
                String tableName = userTabs.getString("TABLE_NAME");
                if (tableName.equals(DatabaseCreator.TEST_TABLE1)) {
                    statement.execute(DatabaseCreator.DROP_TABLE1);
                }
            }
            db.exec(DatabaseCreator.CREATE_TABLE3, null);
            db.exec(DatabaseCreator.CREATE_TABLE1, null);
        } catch (SQLException e1) {
            fail("Error initializing test " + e1.toString());
            e1.printStackTrace();
        } catch (Exception e) {
            fail("Error initializing test " + e.getMessage());
            e.printStackTrace();
        }


        // test run
        try {
            conn.setAutoCommit(false);

//            DatabaseCreator.fillTestTable1(conn, numOfRecords);
            // set to fail immediately if table is locked.
            db.busy_handler(null);
            db.busy_timeout(0);
            int id1 = numOfRecords - 3;

            threadPool.runTask(createTask2(id1, dbFile.getPath(), tracker));
            int id2 = numOfRecords + 3;
            threadPool.runTask(createTask1(id2, dbFile.getPath(), tracker));
            int oldID = 5;
            int newID = 100;
            threadPool.runTask(createTask3(oldID, dbFile.getPath(), newID,
                    tracker));

            threadPool.join();

            List<String> errors = tracker.getErrors();
            assertTrue("No error occurred on DB but should have",errors.size() > 0);

            assertEquals(errors.get(0),
            db.error_string(Constants.SQLITE_LOCKED));
            assertEquals(errors.get(0), "database is locked");

            // reset

            db.exec("delete from " + DatabaseCreator.TEST_TABLE1 + " where 1",
                    null);
            db.exec("delete from " + DatabaseCreator.TEST_TABLE3 + " where 1",
                            null);

            // increase timeout for retry
            db.busy_timeout(10000);
            db.busy_handler(null);
            tracker.reset();
            threadPool = new ThreadPool(numThreads);

            threadPool.runTask(createTask1(id1, dbFile.getPath(), tracker));
            threadPool.runTask(createTask2(id2, dbFile.getPath(), tracker));

            threadPool.join();

            errors = tracker.getErrors();
            if (errors.size() > 0) {
                fail("busy timeout should prevent from lock exception!");
                for (String s: errors) {
                    System.out.println("Round 2 Error"+s);
                }
            } else {
                // ok
            }


        } catch (Exception e) {
            fail("Error in test setup " + e.getMessage());
            try {
                db.get_table("select * from " + DatabaseCreator.TEST_TABLE1,
                        null).toString();
            } catch (Exception e1) {

                e1.printStackTrace();
            }
            e.printStackTrace();
        } finally {
            conn.setAutoCommit(true);
            // cleanup
            db.exec(DatabaseCreator.DROP_TABLE1, null);
            db.exec(DatabaseCreator.DROP_TABLE3, null);
        }
    }

    /**
     * @tests {@link Database#get_table(String)}
     */
    @TestTargetNew(
        level = TestLevel.COMPLETE,
        notes = "method test",
        method = "get_table",
        args = {java.lang.String.class}
    )
    public void testGet_tableString() {
        TableResult emptyTable = new TableResult();
        try {
            //select from empty table
            TableResult res = db.get_table("select * from "
                    + DatabaseCreator.SIMPLE_TABLE1);
            assertEquals(res.toString(), emptyTable.toString());
            //fill table-> t
//            DatabaseCreator.fillSimpleTable1(conn);
//            res = db.get_table("select * from "
//                    + DatabaseCreator.SIMPLE_TABLE1);
//            assertFalse(emptyTable.toString().equals(res.toString()));

            try {
                db.exec("insert into " + DatabaseCreator.SIMPLE_TABLE1
                        + " VALUES(1, 10, 20)", null);
                res = db.get_table("select * from " + DatabaseCreator.SIMPLE_TABLE1);
                db
                        .exec("delete from " + DatabaseCreator.SIMPLE_TABLE1
                                + " where 1", null);
            } catch (Exception e) {
                fail("Database error");
                e.printStackTrace();
            }
            String row[] = (String[]) res.rows.elementAt(0);
            assertEquals(Integer.parseInt(row[0]), 1);
            assertEquals(Integer.parseInt(row[1]), 10);
            assertEquals(Integer.parseInt(row[2]), 20);
        } catch (Exception e) {
            fail("Error getting table " + e.getMessage());
            e.printStackTrace();
//        } catch (SQLException e) {
//            fail("Error initialising table " + e.getMessage());
//            e.printStackTrace();
        }
    }

    /**
     * @tests {@link Database#get_table(String, String[])}
     */
    @TestTargetNew(
        level = TestLevel.COMPLETE,
        notes = "method test",
        method = "get_table",
        args = {java.lang.String.class, java.lang.String[].class}
    )
    public void testGet_tableStringStringArray() {
        String args[] = new String[1];
        args[0] = "table";
        String argsFail[] = new String[1];
        try {
            TableResult res = db.get_table(
                    "select name from sqlite_master where type = ", argsFail);
            fail("Testmethod should fail");
        } catch (Exception e) {
            try {
                TableResult res = db.get_table(
                        "select name from sqlite_master where type = '%q'",
                        args);
                String[] s = (String[]) res.rows.elementAt(0);
                assertEquals(s[0], DatabaseCreator.SIMPLE_TABLE1);
            } catch (Exception e2) {
                fail("Testmethod failed: " + e2.getMessage());
                e.printStackTrace();
            }
        }

    }

    /**
     * @tests {@link Database#get_table(String, String[], TableResult)}
     */
    @TestTargets({
        @TestTargetNew(
            level = TestLevel.COMPLETE,
            notes = "method test",
            method = "get_table",
            args = {java.lang.String.class, java.lang.String[].class, TableResult.class}
        ),
        @TestTargetNew(
                level = TestLevel.COMPLETE,
                notes = "method test",
                method = "toString",
                clazz = TableResult.class,
                args = {}
            ),
       @TestTargetNew(
                    level = TestLevel.COMPLETE,
                    notes = "method test",
                    method = "types",
                    clazz = TableResult.class,
                    args = {String[].class}
            ),
       @TestTargetNew(
                level = TestLevel.COMPLETE,
                notes = "method test",
                method = "TableResult",
                clazz = TableResult.class,
                args = {}
           ),
       @TestTargetNew(
               level = TestLevel.NOT_NECESSARY,
               notes = "method test",
               method = "columns",
               clazz = TableResult.class,
               args = {String[].class}
          ),
      @TestTargetNew(
              level = TestLevel.NOT_NECESSARY,
              notes = "method test",
              method = "newrow",
              clazz = TableResult.class,
              args = {String[].class}
         ),
     @TestTargetNew(
         level = TestLevel.NOT_NECESSARY,
         notes = "method test",
         method = "clear",
         clazz = TableResult.class,
         args = {}
        )

    })
    public void testGet_tableStringStringArrayTableResult() {
        String args[] = new String[1];
        String argsFail[] = new String[1];
        TableResult res = new TableResult();
        TableResult defaultTableRes = new TableResult();
        args[0] = "table";

        try {
            db.get_table("select name from sqlite_master where type = '%q'",
                    argsFail, res);
            assertEquals(defaultTableRes.toString(), res.toString());
        } catch (Exception e) {
            try {
                db.get_table(
                        "select name from sqlite_master where type = '%q'",
                        args, res);
                String[] s = (String[]) res.rows.elementAt(0);
                assertEquals(s[0], DatabaseCreator.SIMPLE_TABLE1);
                String[] types = res.types;
                System.out
                        .println("DatabaseTest.testGet_tableStringStringArrayTableResult() "+types.toString());
            } catch (Exception e2) {
                fail("Testmethod failed: " + e2.getMessage());
                e.printStackTrace();
            }
        }
    }


    /**
     * @tests {@link Database#complete(String)}
     */
    @TestTargetNew(
        level = TestLevel.COMPLETE,
        notes = "method test",
        method = "complete",
        args = {java.lang.String.class}
    )
    public void testComplete() {
        assertFalse(db.complete("create"));
        assertTrue(db.complete("create table TEST (res double);"));
    }

    /**
     * @tests {@link Database#version()}
     */
    @TestTargetNew(
        level = TestLevel.COMPLETE,
        notes = "method test",
        method = "version",
        args = {}
    )
    public void testVersion() {
        String version = db.version();
        if (version != null) {
        assertTrue(Integer.parseInt(db.version().substring(0,1)) > 0);
        assertEquals(db.version(), db.version());
        } else {
            fail("DB version info missing");
        }

    }

    /**
     * @tests {@link Database#dbversion()}
     */
    @TestTargetNew(
        level = TestLevel.COMPLETE,
        notes = "method test",
        method = "dbversion",
        args = {}
    )
    public void testDbversion() {
        String verNo = "";
        try {
            verNo = db.dbversion();
            db.close();
            assertEquals(db.dbversion(),"unknown");
            db.open(dbFile.getPath(), 0);
            assertEquals(verNo,db.dbversion());
        } catch (Exception e) {
            try {
                db.open(dbFile.getPath(), 0);
            } catch (Exception e1) {
                fail("error in db setup "+e.getMessage());
                e.printStackTrace();
            }
            fail("error in db setup "+e.getMessage());
            e.printStackTrace();
        }

        assertTrue(Integer.parseInt(verNo.substring(0, 1))>= 3 );

    }

    /**
     * @tests {@link Database#create_function(String, int, Function)}
     */
    @TestTargets({
    @TestTargetNew(
        level = TestLevel.COMPLETE,
        notes = "method test",
        method = "create_function",
        args = {java.lang.String.class, int.class, Function.class}
    ),
    @TestTargetNew(
        level = TestLevel.COMPLETE,
        notes = "method test",
        method = "create_function",
        args = {java.lang.String.class, int.class, Function.class}
        )
    })
    public void testCreate_function() {
        try {
            double input = 1.0;
            db.exec("create table TEST (res double)", null);
            db.exec("insert into TEST values (" + Double.toString(input) + ")",
                    null);
            TableResult res = new TableResult();
            Function sinFunc = (Function) new SinFunc();
            db.create_function("sin", 1, sinFunc);
            db.exec("select sin(res) from TEST WHERE res = "
                    + Double.toString(input), res);
            String row[] = (String[]) res.rows.elementAt(0);
            String val = row[0];
            double sinusVal = Double.parseDouble(val);
            double funcVal = Math.sin(input);

            assertTrue(Math.round(funcVal) == Math.round(sinusVal));
        } catch (Exception e) {
            fail("Error happened creating function:" + e.getMessage());
            e.printStackTrace();
        }
    }

    /**
     * @tests {@link Database#create_aggregate(String, int, Function)}
     */
    @TestTargets({

    @TestTargetNew(
        level = TestLevel.COMPLETE,
        notes = "method test",
        method = "create_aggregate",
        args = {java.lang.String.class, int.class, Function.class}
    ),
    @TestTargetNew(
        level = TestLevel.COMPLETE,
        notes = "method test",
        method = "step",
        clazz = Function.class,
        args = {FunctionContext.class, String[].class}
            ),
    @TestTargetNew(
        level = TestLevel.COMPLETE,
        notes = "method test",
        method = "last_step",
        clazz = Function.class,
        args = {FunctionContext.class}
                ),
    @TestTargetNew(
        level = TestLevel.COMPLETE,
        notes = "method test",
        method = "function",
        clazz = Function.class,
        args = {FunctionContext.class, String[].class}
                    )
    })
    @KnownFailure("Aggregation function not called")
    public void testCreate_aggregate() {
        TestTrace t = new TestTrace();

        MockFunction aggFunction = new MockFunction();

        try {
            db
                    .exec(
                            "create table TEST(id integer, firstname text, lastname text)",
                            null);
            db.exec("insert into TEST values(3, 'James', 'Bond'); ", null);
            db.exec("insert into TEST values(4, 'Fiona', 'Apple'); ", null);
            db.trace((Trace) t);
            db.create_aggregate("myaggfunc", 1, aggFunction);
            db.function_type("myaggfunc", Constants.SQLITE3_TEXT);
            db.exec("PRAGMA show_datatypes = on", null);

            assertFalse(aggFunction.functionCalled);
            assertFalse(aggFunction.stepCalled);
            assertFalse(aggFunction.lastStepCalled);
            db.exec("select myaggfunc(TEST.firstname) from TEST", t);
            assertTrue(aggFunction.stepCalled);
            assertTrue(aggFunction.lastStepCalled);
            assertTrue(aggFunction.functionCalled);

            assertEquals("James Fiona ",aggFunction.getAggValue());
            db.exec("drop table TEST", null);
        } catch (Exception e) {
            System.out.println(t.getTrace());
            fail("Error in test setup: " + e.getMessage());
            e.printStackTrace();
        }

        try {
            db.create_aggregate("myaggfunc", 0, null);
        } catch (Throwable e) {
            assertEquals("null SQLite.Function not allowed",e.getMessage());
        }

          try {
            db.create_aggregate("myaggfunc", 0, aggFunction);
        } catch (Throwable e) {
            assertEquals("wrong number of arguments to function myaggfunc()",e.getMessage());
        }

    }

    /**
     * @throws Exception
     * @tests {@link Database#function_type(String, int)}
     * This method does not make sense
     */
    @TestTargetNew(
        level = TestLevel.COMPLETE,
        notes = "Method does not make sense: for functions, return type is already set.",
        method = "function_type",
        args = {java.lang.String.class, int.class}
    )
    public void testFunction_type() throws Exception {

        double input = 1.0;
        TableResult res = new TableResult();
        Function sinFunc = (Function) new SinFunc();

        db.exec("PRAGMA show_datatypes = on", null);
        db.exec("create table TEST (res double)", null);
        db.exec("insert into TEST values (" + Double.toString(input) + ")",
                null);

        db.create_function("sin", 1, sinFunc);
        db.function_type("sin", Constants.SQLITE_FLOAT);
        res = db.get_table("select sin(res) from TEST WHERE res = "
                + Double.toString(input));

        String row[] = (String[]) res.rows.elementAt(0);
        String val = row[0];
        assertTrue("double".equalsIgnoreCase(res.types[0]));
        assertSame(Math.round(Math.sin(input)), Math.round(Double.parseDouble(val)));

        // function determines return type: test that Double type is returned.
        db.function_type("sin", Constants.SQLITE_BLOB);
        Stmt s = db.prepare("select sin(res) from TEST WHERE res = ?");
        s.bind(1,input);
        s.step();

        res = db.get_table("select sin(res) from TEST WHERE res = "
                + Double.toString(input));
        assertTrue("double".equalsIgnoreCase(res.types[0]));
        row = (String[]) res.rows.elementAt(0);
        val = row[0];
        assertSame(Math.round(Math.sin(input)), Math.round(Double.parseDouble(val)));




    }

    /**
     * @tests {@link Database#last_error()}
     */
    @TestTargetNew(
        level = TestLevel.COMPLETE,
        notes = "method test",
        method = "last_error",
        args = {}
    )
    public void testLast_error() {
        assertEquals(db.last_error(), Constants.SQLITE_OK);
        try {
            db.exec("create table TEST (res double)",null);
            db.exec("create table TEST (res double)",null);
            fail("Error should have happened");
        } catch (Exception e) {
            assertEquals(db.last_error(),db.last_error());
            assertEquals(db.last_error(),Constants.SQLITE_ERROR);
        }

    }

    /**
     * @tests {@link Database#set_last_error(int)}
     */
    @TestTargetNew(
        level = TestLevel.SUFFICIENT,
        notes = "don't now which other errors may occur from black-box approach.",
        method = "set_last_error",
        args = {int.class}
    )
    public void testSet_last_error() {
       assertEquals(db.last_error(), Constants.SQLITE_OK);

       try {
           db.exec("sel from test;", null);
       } catch (Exception e) {
           assertEquals(Constants.SQLITE_ERROR,db.last_error());
       }
    }

    /**
     * @tests {@link Database#error_message()}
     */
    @TestTargetNew(
        level = TestLevel.COMPLETE,
        notes = "method test",
        method = "error_message",
        args = {}
    )
    public void testError_message() {
        String statement = "create table TEST (res double)";
        try {
            db.exec(statement,null);
            db.exec(statement,null);
            fail("DB Error expected");
        } catch (Exception e) {
            String dbError = db.error_message();
            assertTrue(e.getMessage().equals(dbError));

        }
    }

    /**
     * @tests {@link Database#error_string(int)}
     */

    @TestTargetNew(
        level = TestLevel.COMPLETE,
        notes = "not supported",
        method = "error_string",
        args = {int.class}
    )
    public void testError_string() {
        TestTrace t = new TestTrace();
        assertEquals(db.last_error(), Constants.SQLITE_OK);
        String errorString = db.error_string(Constants.SQLITE_ERROR);
        try {
            db.trace((Trace) t);
            db.exec("create table TEST (res double)", t);
            db.exec("create table TEST (res double)", t);
        } catch (Exception e) {
            assertEquals(db.last_error(), Constants.SQLITE_ERROR);
            if (db.is3()) {
                assertEquals("Unsupported Method (sqlite 3): error_string", db
                        .error_string(db.last_error()), errorString);
            }
        }
    }

    /**
     * @throws UnsupportedEncodingException
     * @tests {@link Database#set_encoding(String)}
     * Method unsupported? -> tests fail
     */
    @TestTargetNew(
        level = TestLevel.COMPLETE,
        notes = "method test fails.",
        method = "set_encoding",
        args = {java.lang.String.class}
    )
    @KnownFailure("ASCII encoding does not work: a UTF encoded val is returned. Spec is not sufficient. "
            + "Might be that test impl is wrong or String constructor for the ASCII encoding.")
    public void testSet_encoding() throws UnsupportedEncodingException {
        String input = "\u00bfMa\u00f1ana\u003f"; // ?Manana?
        TableResult res = new TableResult();
        String refOutput = null;
        Stmt stat = null;

        // DB setup
        try {
            db.exec("create table encodingTest (encoded text DEFAULT NULL);",
                    null);
            stat = db
                    .prepare("insert into encodingTest(encoded) values(:one);");
            stat.bind(1, input);
            stat.step();
            // stat.close();
            db.exec("select * from encodingTest;", res);
            String[] encInput = (String[]) res.rows.elementAt(0);
            String output = encInput[0];
            assertEquals(input, output);
            // db.exec("delete from encodingTest where 1", null);
        } catch (Exception e1) {
            fail("Error in test setup: " + e1.getMessage());
            e1.printStackTrace();
        }

     // tests for different encoding schemes
        String[] charsetNames = {"UTF-8", "UTF-16", "UTF-16BE", "UTF-16LE"};
        for (int i = 0; i < charsetNames.length; i++) {
            try {
                byte[] encInput = input.getBytes(charsetNames[i]);
                db.set_encoding(charsetNames[i]);
                db.exec("select * from encodingTest;", res);
                String[] encOutput = (String[]) res.rows.elementAt(0);
                String inputAsString = new String(encInput,charsetNames[i]);
                assertEquals(inputAsString, encOutput[0]);
            } catch (Exception e4) {
                fail("Error setting the encoding." + e4.getMessage());
                e4.printStackTrace();
            } catch (UnsupportedEncodingException e2) {
                fail(e2.getMessage());
                e2.printStackTrace();
            }
        }

        // Default tests
        try {
            db.set_encoding("UTF-16");
            db.exec("select * from encodingTest;", res);
            String[] encOutput1 = (String[]) res.rows.elementAt(0);
            assertEquals("Got "+encOutput1[0]+" as UTF-16",input,encOutput1[0]);

            db.set_encoding("US-ASCII");
            db.exec("select * from encodingTest;", res);
            String[] encOutput2 = (String[]) res.rows.elementAt(0);
            assertEquals(new String(input.getBytes(),"US-ASCII"),encOutput2[0]);
        } catch (Exception e) {
            fail("Error setting the encoding." + e.getMessage());
            e.printStackTrace();
        }


        // DB teardown
        try {
            stat.close();
            db.exec("delete from encodingTest", null);
            // reset encoding
        } catch (Exception e3) {
            fail("Error in teardown of encoding environment");
            e3.printStackTrace();
        }

        // Default tests
        try {
            db.set_encoding("");
            fail("invalid input should fail");
        } catch (Exception e) {
            //ok
        }

    }

    /**
     * Test fails -> implemented correctly?
     * @tests {@link Database#set_authorizer(Authorizer)}
     */
    @TestTargets({
    @TestTargetNew(
        level = TestLevel.COMPLETE,
        notes = "method test fails.",
        method = "set_authorizer",
        args = {Authorizer.class}
    ),
    @TestTargetNew(
            level = TestLevel.COMPLETE,
            notes = "method test fails.",
            method = "authorize",
            clazz = Authorizer.class,
            args = {int.class, java.lang.String.class, java.lang.String.class, java.lang.String.class, java.lang.String.class}
        )
    })
    @KnownFailure("Callback never made for authorization. "+
            "Results of private table are returned withouth furhter checks.")
    public void testSet_authorizer() {

        TableResult resPriv = null;
        TableResult resPub = null;
        TableResult emptyTable = new TableResult();
        String insertPublic = "insert into public_table values(1,2)";
        String insertPrivate = "insert into private_table values(1,2)";
        try {
            // prepare, authorizer is not activated yet
            db.exec("create table public_table(c1 integer, c2 integer);", null);
            db.exec("create table private_table(c1 integer, c2 integer);", null);
            // inserts
            db.exec(insertPublic, null);
            db.exec(insertPrivate, null);
            // selects
            resPriv = db.get_table("select * from private_table");
            resPub = db.get_table("select * from public_table");

//            db.exec("delete from public_table where 1", null);
//            TableResult emptyPubTable = db.exec("select * from public");

            // set Authorizer (positive case): denies private table
            AuthorizerCallback cb = new AuthorizerCallback();
            db.set_authorizer(cb);
            //select

            db.exec("select * from private_table", cb);
            assertTrue(cb.wasCalled());

           /*
            TableResult res = db.get_table("select * from private_table");
            assertEquals(emptyTable.toString(),res.toString());
            assertFalse(emptyTable.equals(resPriv));

            res = db.get_table("select * from public_table");
            assertEquals(resPub,res);
            */
        } catch (Exception e) {
            fail("Error testing authorization: "+e.getMessage());
        }

        // Try insert
        try {
            db.exec(insertPublic, null);
            fail("authorization failed");
        } catch (Exception e) {
            try {
                db.exec(insertPrivate, null);
                fail("authorization failed");
            } catch (Exception e1) {
                // ok
            }
        }

    }

    /**
     * @tests {@link Database#trace(Trace)}
     */

    @TestTargetNew(
        level = TestLevel.COMPLETE,
        notes = "method test",
        method = "trace",
        args = {Trace.class}
    )
    public void testTrace() {
        String stmt = "create table TEST (res double);";
        TestTrace t = new TestTrace();
        assertFalse(t.traceCalled);
        assertEquals(db.last_error(),Constants.SQLITE_OK);
        try {
            db.trace((Trace) t);
            db.exec(stmt,t);
            assertTrue(t.traceCalled);
            assertEquals(t.getTrace(),stmt);
        } catch (Exception e) {
            fail("Error testing traces: "+e.getMessage());
            e.printStackTrace();
        }

        try {
            db.close();
            db.exec(stmt,t);
            fail("Exception Expected");
        } catch (Exception e) {
            //ok
        }


    }

    /**
     * @tests {@link Database#compile(String)}
     */
    @TestTargetNew(
        level = TestLevel.COMPLETE,
        notes = "method test",
        method = "compile",
        args = {java.lang.String.class}
    )
    public void testCompileString() {
        try {
            db.compile("select name from sqlite_master;");
        } catch (Exception e) {
            fail("Error compiling sql statement " + e.getMessage());
            e.printStackTrace();
        }
        try {
            db.compile("test");
            fail("Compiling of inaccurate statement does not fail.");
        } catch (Exception e) {

        }
    }

    /**
     * @tests {@link Database#compile(String, String[])}
     */
    @TestTargetNew(
        level = TestLevel.COMPLETE,
        notes = "method test",
        method = "compile",
        args = {java.lang.String.class, java.lang.String[].class}
    )
    public void testCompileStringStringArray() {
        String args[] = new String[1];
        args[0] = "table";
        try {
            db.compile("select name from sqlite_master where type = '%q';",args);
        } catch (Exception e) {
            fail("Error compiling sql statement " + e.getMessage());
            e.printStackTrace();
        }
        try {
            db.compile("test",null);
            fail("Compiling of inaccurate statement does not fail.");
        } catch (Exception e) {

        }
    }

    /**
     * @tests {@link Database#prepare(String)}
     */
    @TestTargetNew(
        level = TestLevel.COMPLETE,
        notes = "method test",
        method = "prepare",
        args = {java.lang.String.class}
    )
    public void testPrepare() {
        Stmt st = null;
        Stmt st2 = null;
        // test empty statement
        try {
            st = db.prepare("");
            assertEquals(0, st.bind_parameter_count());
            st.step();
            fail("stmt should not be prepared");
        } catch (Exception e) {
            assertEquals("stmt already closed", e.getMessage());
        }

        // test statement with unbound arguments
        try {
            st2 = db.prepare("insert into " + DatabaseCreator.SIMPLE_TABLE1
                    + " values (:one,:two,:three)");
            assertEquals(3, st2.bind_parameter_count());
            assertEquals(3, st2.bind_parameter_index(":three"));
            assertEquals(":two", st2.bind_parameter_name(2));
        } catch (Exception e) {
            fail("error in prepare method: " + e.getMessage());
            e.printStackTrace();
        } finally {
            try {
                st2.close();
            } catch (Exception e) {
                fail("error in prepare method cleanup: " + e.getMessage());
                e.printStackTrace();
            }
        }

        try {
            db.prepare("insert into " + DatabaseCreator.SIMPLE_TABLE1
                    + " values(:one,:two,:three,:four);");
        } catch (Exception e) {
            assertEquals("table " + DatabaseCreator.SIMPLE_TABLE1
                    + " has 3 columns but 4 values were supplied", e
                    .getMessage());
        }

        try {
            db.prepare("insert into " + DatabaseCreator.SIMPLE_TABLE1
                    + " values(5, '10, 20);");
        } catch (Exception e) {
            assertEquals("unrecognized token: \"'10, 20);\"", e.getMessage());
        }

        try {
            db.prepare("insert into " + DatabaseCreator.SIMPLE_TABLE1
                    + " values(5, 10 20);");
        } catch (Exception e) {
            assertEquals("near \"20\": syntax error", e.getMessage());
        }

    }

    /**
     * @throws Exception
     * @throws java.lang.Exception
     * @tests {@link Database#open_blob(String, String, String, long, boolean)}
     * unsupported
     */
    @TestTargetNew(
        level = TestLevel.COMPLETE,
        notes = "not supported",
        method = "open_blob",
        args = {java.lang.String.class, java.lang.String.class, java.lang.String.class, long.class, boolean.class}
    )
    @KnownFailure("not supported")
    public void testOpen_blob() throws Exception, java.lang.Exception {
        Stmt statement2;
        Blob blobInput = new Blob();


        // Create test input Blob
        InputStream inStream = null;
        byte[] in = {(byte) 1, (byte) 2, (byte) 3, (byte) 4};

        // setup test input
        db.exec("create table TEST (res blob)",null);
        inStream = Class.forName(this.getClass().getName()).getResourceAsStream("/blob.c");
        assertNotNull(inStream);


        // insert byte array in db
        try {
            statement2 = db.prepare("insert into TEST(res) values (?)");
            statement2.bind(1, in);
            statement2.step();
            statement2.close();
        } catch (Exception e) {
            fail("Error happened inserting blob" + e.getMessage());
            e.printStackTrace();
        }

        // read from db
        byte[] output = null;
        Blob blob;

            blob = db.open_blob(dbFile.getPath(), "TEST", "res", 1, true);
            if (blob == null) {
                fail("Blob could not be retrieved");
            }
            //read from blob and compare values (positive case)
            InputStream is = blob.getInputStream();

            int i = 0;
            int outByte = 0;
            byte[] out = new byte[4];
            while ((outByte = is.read()) > -1) {
                out[i] = (byte) outByte;
                i++;
            }
            is.close();

            blob.close();

            assertTrue(Arrays.equals(in, out));

            //read from blob and compare values (default blob)
            db.exec("insert into TEST values(zeroblob(128))", null);
            Blob blob2 = db.open_blob(dbFile.getPath(), "TEST", "res", 2, true);
            is = blob2.getInputStream();
            for (i = 0; i < 128; i++)  {
               assertEquals(0, is.read());
            }
            is.close();
    }

    /**
     * @tests {@link Database#is3()}
     */
    @TestTargetNew(
        level = TestLevel.COMPLETE,
        notes = "method test",
        method = "is3",
        args = {}
    )
    public void testIs3() {
        int ver = Integer.parseInt(db.version().substring(0,1));
        if (db.is3()) {
            assertTrue( ver == 3);
        } else {
            assertTrue(ver != 3);
        }
    }

    /**
     * @tests {@link Database#progress_handler(int, ProgressHandler)}
     */
    @TestTargets ({
    @TestTargetNew(
        level = TestLevel.COMPLETE,
        notes = "method test",
        method = "progress_handler",
        args = {int.class, ProgressHandler.class}
    ),
    @TestTargetNew(
            level = TestLevel.COMPLETE,
            notes = "method test",
            method = "progress",
            clazz = ProgressHandler.class,
            args = {}
        )
    })
    public void testProgress_handler() {
        int inputVal = 3;
        TestProgressHandler prog = new TestProgressHandler();
        try {
            db.exec("create table TEST5(id integer, firstname text, lastname text)",null);
            Vm vm = db.compile("select * from TEST5; "
                    + "insert into TEST5 values(3, 'James', 'Bond'); "
                    + "delete from TEST5 where id = 3; "
                    + "select * from TEST5");
            int stmt = 0;
            do {
                ++stmt;
                if (stmt > inputVal) {
                    db.progress_handler(inputVal, prog);
                } else {
                    assertEquals(0, prog.getCounts());
                }
                while (vm.step(prog)) {
                }
            } while (vm.compile());
            assertEquals(inputVal,prog.getCounts());
        } catch (Exception e) {
            fail("Error in test setup: "+e.getMessage());
            e.printStackTrace();
        }

        // Boundary value test
        inputVal = 0;
        TestProgressHandler progBoundary = new TestProgressHandler();
        db.progress_handler(inputVal, progBoundary);
        try {
        Vm vm2 = db.compile("select * from TEST5; "
                + "insert into TEST5 values(3, 'James', 'Bond'); "
                + "delete from TEST5 where id = 3; "
                + "select * from TEST5");
        do {
            vm2.step(progBoundary);
        } while (vm2.compile());
        assertEquals(inputVal, progBoundary.getCounts());
        }catch (Exception e) {
            fail("Error in test setup: "+e.getMessage());
            e.printStackTrace();
        }

        try {
            db.exec("drop table TEST5",null);
        } catch (Exception e) {
            System.out.println(e.getMessage());
            e.printStackTrace();
        }
    }



    class SinFunc implements Function {


        public void function(FunctionContext fc, String args[]) {
            Double d = new Double(args[0]);
            fc.set_result(Math.sin(d.doubleValue()));
        }

        public void last_step(FunctionContext fc) {
            // TODO Auto-generated method stub

        }

        public void step(FunctionContext fc, String[] args) {
            // TODO Auto-generated method stub

        }
    }

   @TestTargetClass(Trace.class)
    class TestTrace implements Trace,Callback {

    private StringBuffer buf = new StringBuffer();

    public boolean traceCalled = false;

    public String getTrace() {
        return buf.toString();
    }

    public void trace(String stmt) {
        traceCalled = true;
        buf.append(stmt);
    }

    public void columns(String[] coldata) {
        // TODO Auto-generated method stub

    }

    public boolean newrow(String[] rowdata) {
        // TODO Auto-generated method stub
        return false;
    }

    public void types(String[] types) {
        // TODO Auto-generated method stub

    }
   }

   @TestTargetClass(Authorizer.class)
   class AuthorizerCallback implements Authorizer,Callback {
       private boolean isAuthorizing = false;

       public boolean wasCalled() {
           return isAuthorizing;
       }

    public int authorize(int action, String arg1, String arg2, String arg3,
            String arg4) {
        Logger.global.info("DB authorization callback "+action+" "+arg1+" "+arg2+" "+arg3+" "+arg4+" ");
        this.isAuthorizing = true;
        if (action != Constants.SQLITE_SELECT || arg1.contains("private_table")) {
        return Constants.SQLITE_DENY;
        } else {
        return Constants.SQLITE_OK;
        }
    }

    public void columns(String[] coldata) {
        // TODO Auto-generated method stub

    }

    public boolean newrow(String[] rowdata) {
        // TODO Auto-generated method stub
        return false;
    }

    public void types(String[] types) {
        // TODO Auto-generated method stub

    }

   }

   class TestBusyHandler implements BusyHandler, Callback {

    public boolean busy(String table, int count) {
        System.out.println("BUSY!");
        return true;
    }

    public void columns(String[] coldata) {
        // TODO Auto-generated method stub

    }

    public boolean newrow(String[] rowdata) {
        // TODO Auto-generated method stub
        return false;
    }

    public void types(String[] types) {
        // TODO Auto-generated method stub

    }

   }

   class TestProgressHandler implements ProgressHandler,Callback {

    private boolean progressed = false;

    private int counter = 0;

    public boolean isProgressed() {
        return progressed;
    }

    public int getCounts() {
        return counter;
    }

    public boolean progress() {
        this.progressed = true;
        counter++;
        return true;
    }

    public void columns(String[] coldata) {
        // TODO Auto-generated method stub

    }

    public boolean newrow(String[] rowdata) {
        // TODO Auto-generated method stub
        return false;
    }

    public void types(String[] types) {
        // TODO Auto-generated method stub

    }

   }

//   class dbBusyThread implements Runnable {
//
//       String dbName = "sqliteTest.db";
//
//       Thread runner;
//       public dbBusyThread() {
//       }
//       public dbBusyThread(String threadName) {
//           runner = new Thread(this, threadName); // (1) Create a new thread.
//           System.out.println(runner.getName());
//           runner.start(); // (2) Start the thread.
//       }
//       public void run() {
//            insert(3000);
//        }
//
//       public void runNoDelay() {
//           insert(0);
//       }
//
//       synchronized private void insert(long delay) {
//           Database db2 = new Database();
//           try {
//               db2.open(dbName, 0);
//               db2.exec("insert into TEST5 values (4,'Anglina','Jolie');",
//                       null);
//               wait(delay);
//           } catch (Exception e) {
//               System.out.println("Error in Thread " + e.getMessage());
//               e.printStackTrace();
//           } catch (InterruptedException e2) {
//               System.out.println("Error in Thread " + e2.getMessage());
//               e2.printStackTrace();
//           } finally {
//              try {
//               db2.close();
//           } catch (Exception e) {
//               // We do not handle this case
//           }
//           }
//       }
//   }

   /**
    * This method creates a Runnable that executes insert operation for the
    * first table
    */
   private static Runnable createTask2Interrupt(final int id,
            final String dbName, final ErrorTracker errorTracker) {
        return new Runnable() {
            public void run() {
                Database db = new Database();
                try {
                    String value = DatabaseCreator.defaultString + id;

                    db.open(dbName, 0);
                    String insertQuery = "INSERT INTO "
                            + DatabaseCreator.TEST_TABLE1
                            + " (id, field1, field2, field3) VALUES(" + id
                            + ", '" + value + "', " + id + ", " + id + ")";
                    db.exec(insertQuery, null);
                } catch (Exception e) {
                    errorTracker.registerException(this, e);
                    try {
                        db.interrupt();
                        db.exec("DELETE FROM " + DatabaseCreator.SIMPLE_TABLE1
                                + " WHERE id=" + id, null);
                    } catch (Exception e1) {
                        errorTracker.registerException(this, e1);
                    }
                }
            }
        };
   }

   /**
    * This method creates a Runnable that executes delete operation for the
    * first table
    */
   private static Runnable createTask1(final int id,final String dbName, final ErrorTracker errorTracker) {
       return new Runnable() {
           public void run() {
               try {
                   Database db = new Database();
                   db.open(dbName, 0);
                   db.exec("DELETE FROM "
                           + DatabaseCreator.SIMPLE_TABLE1 + " WHERE id=" + id,null);
               } catch (Exception e) {
                   errorTracker.registerException(this, e);
               }
           }
       };
   }

   /**
    * This method creates a Runnable that executes insert operation for the
    * first table
    */
   private static Runnable createTask2(final int id, final String dbName, final ErrorTracker errorTracker ) {
       return new Runnable() {
           public void run() {
               try {
                   String value = DatabaseCreator.defaultString + id;
                   Database db = new Database();
                   db.open(dbName, 0);
                   String insertQuery = "INSERT INTO "
                           + DatabaseCreator.TEST_TABLE1
                           + " (id, field1, field2, field3) VALUES(" + id
                           + ", '" + value + "', " + id + ", " + id + ")";
                   db.exec(insertQuery,null);
               } catch (Exception e) {
                   errorTracker.registerException(this, e);

               }
           }
       };
   }

   /**
    * This method creates a Runnable that executes update operation for the one
    * record of the first table
    */
   private static Runnable createTask3(final int oldID, final String dbName,
            final int newID, final ErrorTracker errorTracker) {
        return new Runnable() {
            public void run() {
                Database db = new Database();
                try {
                    db.open(dbName, 0);
                    String value = DatabaseCreator.defaultString + newID;
                    String updateQuery = "UPDATE "
                            + DatabaseCreator.TEST_TABLE1 + " SET id=" + newID
                            + ", field1='" + value + "', field2=" + newID
                            + ", field3=" + newID + " WHERE id=" + oldID;
                    db.exec(updateQuery, null);
                } catch (Exception e) {
                    errorTracker.registerException(this, e);
                }
            }
        };
    }

   private class ErrorTracker {
        private List<String> errors = new ArrayList<String>();

        public void registerException(Runnable runnable, Exception e) {
            System.out.println("Registered: "+e.getMessage());
            errors.add(e.getMessage());
        }

        public List<String> getErrors() {
            return errors;
        }

        public void reset() {
            errors.clear();
        }
    }

}
