import unittest
import os, glob

from test_all import db, test_support, get_new_environment_path, \
        get_new_database_path

#----------------------------------------------------------------------

class DBEnv(unittest.TestCase):
    def setUp(self):
        self.homeDir = get_new_environment_path()
        self.env = db.DBEnv()

    def tearDown(self):
        self.env.close()
        del self.env
        test_support.rmtree(self.homeDir)

class DBEnv_general(DBEnv) :
    def test_get_open_flags(self) :
        flags = db.DB_CREATE | db.DB_INIT_MPOOL
        self.env.open(self.homeDir, flags)
        self.assertEqual(flags, self.env.get_open_flags())

    def test_get_open_flags2(self) :
        flags = db.DB_CREATE | db.DB_INIT_MPOOL | \
                db.DB_INIT_LOCK | db.DB_THREAD
        self.env.open(self.homeDir, flags)
        self.assertEqual(flags, self.env.get_open_flags())

    if db.version() >= (4, 7) :
        def test_lk_partitions(self) :
            for i in [10, 20, 40] :
                self.env.set_lk_partitions(i)
                self.assertEqual(i, self.env.get_lk_partitions())

        def test_getset_intermediate_dir_mode(self) :
            self.assertEqual(None, self.env.get_intermediate_dir_mode())
            for mode in ["rwx------", "rw-rw-rw-", "rw-r--r--"] :
                self.env.set_intermediate_dir_mode(mode)
                self.assertEqual(mode, self.env.get_intermediate_dir_mode())
            self.assertRaises(db.DBInvalidArgError,
                    self.env.set_intermediate_dir_mode, "abcde")

    if db.version() >= (4, 6) :
        def test_thread(self) :
            for i in [16, 100, 1000] :
                self.env.set_thread_count(i)
                self.assertEqual(i, self.env.get_thread_count())

        def test_cache_max(self) :
            for size in [64, 128] :
                size = size*1024*1024  # Megabytes
                self.env.set_cache_max(0, size)
                size2 = self.env.get_cache_max()
                self.assertEqual(0, size2[0])
                self.assertLessEqual(size, size2[1])
                self.assertGreater(2*size, size2[1])

    if db.version() >= (4, 4) :
        def test_mutex_stat(self) :
            self.env.open(self.homeDir, db.DB_CREATE | db.DB_INIT_MPOOL |
                    db.DB_INIT_LOCK)
            stat = self.env.mutex_stat()
            self.assertIn("mutex_inuse_max", stat)

        def test_lg_filemode(self) :
            for i in [0600, 0660, 0666] :
                self.env.set_lg_filemode(i)
                self.assertEqual(i, self.env.get_lg_filemode())

    def test_mp_max_openfd(self) :
        for i in [17, 31, 42] :
            self.env.set_mp_max_openfd(i)
            self.assertEqual(i, self.env.get_mp_max_openfd())

    def test_mp_max_write(self) :
        for i in [100, 200, 300] :
            for j in [1, 2, 3] :
                j *= 1000000
                self.env.set_mp_max_write(i, j)
                v=self.env.get_mp_max_write()
                self.assertEqual((i, j), v)

        def test_invalid_txn(self) :
            # This environment doesn't support transactions
            self.assertRaises(db.DBInvalidArgError, self.env.txn_begin)

        def test_mp_mmapsize(self) :
            for i in [16, 32, 64] :
                i *= 1024*1024
                self.env.set_mp_mmapsize(i)
                self.assertEqual(i, self.env.get_mp_mmapsize())

        def test_tmp_dir(self) :
            for i in ["a", "bb", "ccc"] :
                self.env.set_tmp_dir(i)
                self.assertEqual(i, self.env.get_tmp_dir())

        def test_flags(self) :
            self.env.set_flags(db.DB_AUTO_COMMIT, 1)
            self.assertEqual(db.DB_AUTO_COMMIT, self.env.get_flags())
            self.env.set_flags(db.DB_TXN_NOSYNC, 1)
            self.assertEqual(db.DB_AUTO_COMMIT | db.DB_TXN_NOSYNC,
                    self.env.get_flags())
            self.env.set_flags(db.DB_AUTO_COMMIT, 0)
            self.assertEqual(db.DB_TXN_NOSYNC, self.env.get_flags())
            self.env.set_flags(db.DB_TXN_NOSYNC, 0)
            self.assertEqual(0, self.env.get_flags())

        def test_lk_max_objects(self) :
            for i in [1000, 2000, 3000] :
                self.env.set_lk_max_objects(i)
                self.assertEqual(i, self.env.get_lk_max_objects())

        def test_lk_max_locks(self) :
            for i in [1000, 2000, 3000] :
                self.env.set_lk_max_locks(i)
                self.assertEqual(i, self.env.get_lk_max_locks())

        def test_lk_max_lockers(self) :
            for i in [1000, 2000, 3000] :
                self.env.set_lk_max_lockers(i)
                self.assertEqual(i, self.env.get_lk_max_lockers())

        def test_lg_regionmax(self) :
            for i in [128, 256, 1000] :
                i = i*1024*1024
                self.env.set_lg_regionmax(i)
                j = self.env.get_lg_regionmax()
                self.assertLessEqual(i, j)
                self.assertGreater(2*i, j)

        def test_lk_detect(self) :
            flags= [db.DB_LOCK_DEFAULT, db.DB_LOCK_EXPIRE, db.DB_LOCK_MAXLOCKS,
                    db.DB_LOCK_MINLOCKS, db.DB_LOCK_MINWRITE,
                    db.DB_LOCK_OLDEST, db.DB_LOCK_RANDOM, db.DB_LOCK_YOUNGEST]

            flags.append(db.DB_LOCK_MAXWRITE)

            for i in flags :
                self.env.set_lk_detect(i)
                self.assertEqual(i, self.env.get_lk_detect())

        def test_lg_dir(self) :
            for i in ["a", "bb", "ccc", "dddd"] :
                self.env.set_lg_dir(i)
                self.assertEqual(i, self.env.get_lg_dir())

        def test_lg_bsize(self) :
            log_size = 70*1024
            self.env.set_lg_bsize(log_size)
            self.assertGreaterEqual(self.env.get_lg_bsize(), log_size)
            self.assertLess(self.env.get_lg_bsize(), 4*log_size)
            self.env.set_lg_bsize(4*log_size)
            self.assertGreaterEqual(self.env.get_lg_bsize(), 4*log_size)

        def test_setget_data_dirs(self) :
            dirs = ("a", "b", "c", "d")
            for i in dirs :
                self.env.set_data_dir(i)
            self.assertEqual(dirs, self.env.get_data_dirs())

        def test_setget_cachesize(self) :
            cachesize = (0, 512*1024*1024, 3)
            self.env.set_cachesize(*cachesize)
            self.assertEqual(cachesize, self.env.get_cachesize())

            cachesize = (0, 1*1024*1024, 5)
            self.env.set_cachesize(*cachesize)
            cachesize2 = self.env.get_cachesize()
            self.assertEqual(cachesize[0], cachesize2[0])
            self.assertEqual(cachesize[2], cachesize2[2])
            # Berkeley DB expands the cache 25% accounting overhead,
            # if the cache is small.
            self.assertEqual(125, int(100.0*cachesize2[1]/cachesize[1]))

            # You can not change configuration after opening
            # the environment.
            self.env.open(self.homeDir, db.DB_CREATE | db.DB_INIT_MPOOL)
            cachesize = (0, 2*1024*1024, 1)
            self.assertRaises(db.DBInvalidArgError,
                              self.env.set_cachesize, *cachesize)
            cachesize3 = self.env.get_cachesize()
            self.assertEqual(cachesize2[0], cachesize3[0])
            self.assertEqual(cachesize2[2], cachesize3[2])
            # In Berkeley DB 5.1, the cachesize can change when opening the Env
            self.assertLessEqual(cachesize2[1], cachesize3[1])

        def test_set_cachesize_dbenv_db(self) :
            # You can not configure the cachesize using
            # the database handle, if you are using an environment.
            d = db.DB(self.env)
            self.assertRaises(db.DBInvalidArgError,
                d.set_cachesize, 0, 1024*1024, 1)

        def test_setget_shm_key(self) :
            shm_key=137
            self.env.set_shm_key(shm_key)
            self.assertEqual(shm_key, self.env.get_shm_key())
            self.env.set_shm_key(shm_key+1)
            self.assertEqual(shm_key+1, self.env.get_shm_key())

            # You can not change configuration after opening
            # the environment.
            self.env.open(self.homeDir, db.DB_CREATE | db.DB_INIT_MPOOL)
            # If we try to reconfigure cache after opening the
            # environment, core dump.
            self.assertRaises(db.DBInvalidArgError,
                self.env.set_shm_key, shm_key)
            self.assertEqual(shm_key+1, self.env.get_shm_key())

    if db.version() >= (4, 4) :
        def test_mutex_setget_max(self) :
            v = self.env.mutex_get_max()
            v2 = v*2+1

            self.env.mutex_set_max(v2)
            self.assertEqual(v2, self.env.mutex_get_max())

            self.env.mutex_set_max(v)
            self.assertEqual(v, self.env.mutex_get_max())

            # You can not change configuration after opening
            # the environment.
            self.env.open(self.homeDir, db.DB_CREATE)
            self.assertRaises(db.DBInvalidArgError,
                    self.env.mutex_set_max, v2)

        def test_mutex_setget_increment(self) :
            v = self.env.mutex_get_increment()
            v2 = 127

            self.env.mutex_set_increment(v2)
            self.assertEqual(v2, self.env.mutex_get_increment())

            self.env.mutex_set_increment(v)
            self.assertEqual(v, self.env.mutex_get_increment())

            # You can not change configuration after opening
            # the environment.
            self.env.open(self.homeDir, db.DB_CREATE)
            self.assertRaises(db.DBInvalidArgError,
                    self.env.mutex_set_increment, v2)

        def test_mutex_setget_tas_spins(self) :
            self.env.mutex_set_tas_spins(0)  # Default = BDB decides
            v = self.env.mutex_get_tas_spins()
            v2 = v*2+1

            self.env.mutex_set_tas_spins(v2)
            self.assertEqual(v2, self.env.mutex_get_tas_spins())

            self.env.mutex_set_tas_spins(v)
            self.assertEqual(v, self.env.mutex_get_tas_spins())

            # In this case, you can change configuration
            # after opening the environment.
            self.env.open(self.homeDir, db.DB_CREATE)
            self.env.mutex_set_tas_spins(v2)

        def test_mutex_setget_align(self) :
            v = self.env.mutex_get_align()
            v2 = 64
            if v == 64 :
                v2 = 128

            self.env.mutex_set_align(v2)
            self.assertEqual(v2, self.env.mutex_get_align())

            # Requires a nonzero power of two
            self.assertRaises(db.DBInvalidArgError,
                    self.env.mutex_set_align, 0)
            self.assertRaises(db.DBInvalidArgError,
                    self.env.mutex_set_align, 17)

            self.env.mutex_set_align(2*v2)
            self.assertEqual(2*v2, self.env.mutex_get_align())

            # You can not change configuration after opening
            # the environment.
            self.env.open(self.homeDir, db.DB_CREATE)
            self.assertRaises(db.DBInvalidArgError,
                    self.env.mutex_set_align, v2)


class DBEnv_log(DBEnv) :
    def setUp(self):
        DBEnv.setUp(self)
        self.env.open(self.homeDir, db.DB_CREATE | db.DB_INIT_MPOOL | db.DB_INIT_LOG)

    def test_log_file(self) :
        log_file = self.env.log_file((1, 1))
        self.assertEqual("log.0000000001", log_file[-14:])

    if db.version() >= (4, 4) :
        # The version with transactions is checked in other test object
        def test_log_printf(self) :
            msg = "This is a test..."
            self.env.log_printf(msg)
            logc = self.env.log_cursor()
            self.assertIn(msg, logc.last()[1])

    if db.version() >= (4, 7) :
        def test_log_config(self) :
            self.env.log_set_config(db.DB_LOG_DSYNC | db.DB_LOG_ZERO, 1)
            self.assertTrue(self.env.log_get_config(db.DB_LOG_DSYNC))
            self.assertTrue(self.env.log_get_config(db.DB_LOG_ZERO))
            self.env.log_set_config(db.DB_LOG_ZERO, 0)
            self.assertTrue(self.env.log_get_config(db.DB_LOG_DSYNC))
            self.assertFalse(self.env.log_get_config(db.DB_LOG_ZERO))


class DBEnv_log_txn(DBEnv) :
    def setUp(self):
        DBEnv.setUp(self)
        self.env.open(self.homeDir, db.DB_CREATE | db.DB_INIT_MPOOL |
                db.DB_INIT_LOG | db.DB_INIT_TXN)

    if (db.version() >= (4, 5)) and (db.version() < (5, 2)) :
        def test_tx_max(self) :
            txns=[]
            def tx() :
                for i in xrange(self.env.get_tx_max()) :
                    txns.append(self.env.txn_begin())

            tx()
            self.assertRaises(MemoryError, tx)

            # Abort the transactions before garbage collection,
            # to avoid "warnings".
            for i in txns :
                i.abort()

    if db.version() >= (4, 4) :
        # The version without transactions is checked in other test object
        def test_log_printf(self) :
            msg = "This is a test..."
            txn = self.env.txn_begin()
            self.env.log_printf(msg, txn=txn)
            txn.commit()
            logc = self.env.log_cursor()
            logc.last()  # Skip the commit
            self.assertIn(msg, logc.prev()[1])

            msg = "This is another test..."
            txn = self.env.txn_begin()
            self.env.log_printf(msg, txn=txn)
            txn.abort()  # Do not store the new message
            logc.last()  # Skip the abort
            self.assertNotIn(msg, logc.prev()[1])

            msg = "This is a third test..."
            txn = self.env.txn_begin()
            self.env.log_printf(msg, txn=txn)
            txn.commit()  # Do not store the new message
            logc.last()  # Skip the commit
            self.assertIn(msg, logc.prev()[1])


class DBEnv_memp(DBEnv):
    def setUp(self):
        DBEnv.setUp(self)
        self.env.open(self.homeDir, db.DB_CREATE | db.DB_INIT_MPOOL | db.DB_INIT_LOG)
        self.db = db.DB(self.env)
        self.db.open("test", db.DB_HASH, db.DB_CREATE, 0660)

    def tearDown(self):
        self.db.close()
        del self.db
        DBEnv.tearDown(self)

    def test_memp_1_trickle(self) :
        self.db.put("hi", "bye")
        self.assertGreater(self.env.memp_trickle(100), 0)

# Preserve the order, do "memp_trickle" test first
    def test_memp_2_sync(self) :
        self.db.put("hi", "bye")
        self.env.memp_sync()  # Full flush
        # Nothing to do...
        self.assertEqual(self.env.memp_trickle(100), 0)

        self.db.put("hi", "bye2")
        self.env.memp_sync((1, 0))  # NOP, probably
        # Something to do... or not
        self.assertGreaterEqual(self.env.memp_trickle(100), 0)

        self.db.put("hi", "bye3")
        self.env.memp_sync((123, 99))  # Full flush
        # Nothing to do...
        self.assertEqual(self.env.memp_trickle(100), 0)

    def test_memp_stat_1(self) :
        stats = self.env.memp_stat()  # No param
        self.assertEqual(len(stats), 2)
        self.assertIn("cache_miss", stats[0])
        stats = self.env.memp_stat(db.DB_STAT_CLEAR)  # Positional param
        self.assertIn("cache_miss", stats[0])
        stats = self.env.memp_stat(flags=0)  # Keyword param
        self.assertIn("cache_miss", stats[0])

    def test_memp_stat_2(self) :
        stats=self.env.memp_stat()[1]
        self.assertEqual(len(stats), 1)
        self.assertIn("test", stats)
        self.assertIn("page_in", stats["test"])

class DBEnv_logcursor(DBEnv):
    def setUp(self):
        DBEnv.setUp(self)
        self.env.open(self.homeDir, db.DB_CREATE | db.DB_INIT_MPOOL |
                db.DB_INIT_LOG | db.DB_INIT_TXN)
        txn = self.env.txn_begin()
        self.db = db.DB(self.env)
        self.db.open("test", db.DB_HASH, db.DB_CREATE, 0660, txn=txn)
        txn.commit()
        for i in ["2", "8", "20"] :
            txn = self.env.txn_begin()
            self.db.put(key = i, data = i*int(i), txn=txn)
            txn.commit()

    def tearDown(self):
        self.db.close()
        del self.db
        DBEnv.tearDown(self)

    def _check_return(self, value) :
        self.assertIsInstance(value, tuple)
        self.assertEqual(len(value), 2)
        self.assertIsInstance(value[0], tuple)
        self.assertEqual(len(value[0]), 2)
        self.assertIsInstance(value[0][0], int)
        self.assertIsInstance(value[0][1], int)
        self.assertIsInstance(value[1], str)

    # Preserve test order
    def test_1_first(self) :
        logc = self.env.log_cursor()
        v = logc.first()
        self._check_return(v)
        self.assertLess((1, 1), v[0])
        self.assertGreater(len(v[1]), 0)

    def test_2_last(self) :
        logc = self.env.log_cursor()
        lsn_first = logc.first()[0]
        v = logc.last()
        self._check_return(v)
        self.assertLess(lsn_first, v[0])

    def test_3_next(self) :
        logc = self.env.log_cursor()
        lsn_last = logc.last()[0]
        self.assertEqual(logc.next(), None)
        lsn_first = logc.first()[0]
        v = logc.next()
        self._check_return(v)
        self.assertLess(lsn_first, v[0])
        self.assertGreater(lsn_last, v[0])

        v2 = logc.next()
        self.assertGreater(v2[0], v[0])
        self.assertGreater(lsn_last, v2[0])

        v3 = logc.next()
        self.assertGreater(v3[0], v2[0])
        self.assertGreater(lsn_last, v3[0])

    def test_4_prev(self) :
        logc = self.env.log_cursor()
        lsn_first = logc.first()[0]
        self.assertEqual(logc.prev(), None)
        lsn_last = logc.last()[0]
        v = logc.prev()
        self._check_return(v)
        self.assertLess(lsn_first, v[0])
        self.assertGreater(lsn_last, v[0])

        v2 = logc.prev()
        self.assertLess(v2[0], v[0])
        self.assertLess(lsn_first, v2[0])

        v3 = logc.prev()
        self.assertLess(v3[0], v2[0])
        self.assertLess(lsn_first, v3[0])

    def test_5_current(self) :
        logc = self.env.log_cursor()
        logc.first()
        v = logc.next()
        self.assertEqual(v, logc.current())

    def test_6_set(self) :
        logc = self.env.log_cursor()
        logc.first()
        v = logc.next()
        self.assertNotEqual(v, logc.next())
        self.assertNotEqual(v, logc.next())
        self.assertEqual(v, logc.set(v[0]))

    def test_explicit_close(self) :
        logc = self.env.log_cursor()
        logc.close()
        self.assertRaises(db.DBCursorClosedError, logc.next)

    def test_implicit_close(self) :
        logc =  [self.env.log_cursor() for i in xrange(10)]
        self.env.close()  # This close should close too all its tree
        for i in logc :
            self.assertRaises(db.DBCursorClosedError, i.next)

def test_suite():
    suite = unittest.TestSuite()

    suite.addTest(unittest.makeSuite(DBEnv_general))
    suite.addTest(unittest.makeSuite(DBEnv_memp))
    suite.addTest(unittest.makeSuite(DBEnv_logcursor))
    suite.addTest(unittest.makeSuite(DBEnv_log))
    suite.addTest(unittest.makeSuite(DBEnv_log_txn))

    return suite

if __name__ == '__main__':
    unittest.main(defaultTest='test_suite')
