"""Test script for the dumbdbm module
   Original by Roger E. Masse
"""

import os
import stat
import sys
import unittest
import dumbdbm
from test import test_support

_fname = test_support.TESTFN

def _delete_files():
    for ext in [".dir", ".dat", ".bak"]:
        try:
            os.unlink(_fname + ext)
        except OSError:
            pass

class DumbDBMTestCase(unittest.TestCase):
    _dict = {'0': '',
             'a': 'Python:',
             'b': 'Programming',
             'c': 'the',
             'd': 'way',
             'f': 'Guido',
             'g': 'intended'
             }

    def __init__(self, *args):
        unittest.TestCase.__init__(self, *args)

    def test_dumbdbm_creation(self):
        f = dumbdbm.open(_fname, 'c')
        self.assertEqual(f.keys(), [])
        for key in self._dict:
            f[key] = self._dict[key]
        self.read_helper(f)
        f.close()

    @unittest.skipUnless(hasattr(os, 'chmod'), 'os.chmod not available')
    @unittest.skipUnless(hasattr(os, 'umask'), 'os.umask not available')
    def test_dumbdbm_creation_mode(self):
        try:
            old_umask = os.umask(0002)
            f = dumbdbm.open(_fname, 'c', 0637)
            f.close()
        finally:
            os.umask(old_umask)

        expected_mode = 0635
        if os.name != 'posix' or sys.platform == 'cli':
            # Windows and IronPython only support setting the read-only attribute.
            # This shouldn't fail, but doesn't work like Unix either.
            expected_mode = 0666

        import stat
        st = os.stat(_fname + '.dat')
        self.assertEqual(stat.S_IMODE(st.st_mode), expected_mode)
        st = os.stat(_fname + '.dir')
        self.assertEqual(stat.S_IMODE(st.st_mode), expected_mode)

    def test_close_twice(self):
        f = dumbdbm.open(_fname)
        f['a'] = 'b'
        self.assertEqual(f['a'], 'b')
        f.close()
        f.close()

    def test_dumbdbm_modification(self):
        self.init_db()
        f = dumbdbm.open(_fname, 'w')
        self._dict['g'] = f['g'] = "indented"
        self.read_helper(f)
        f.close()

    def test_dumbdbm_read(self):
        self.init_db()
        f = dumbdbm.open(_fname, 'r')
        self.read_helper(f)
        f.close()

    def test_dumbdbm_keys(self):
        self.init_db()
        f = dumbdbm.open(_fname)
        keys = self.keys_helper(f)
        f.close()

    def test_write_write_read(self):
        # test for bug #482460
        f = dumbdbm.open(_fname)
        f['1'] = 'hello'
        f['1'] = 'hello2'
        f.close()
        f = dumbdbm.open(_fname)
        self.assertEqual(f['1'], 'hello2')
        f.close()

    def test_line_endings(self):
        # test for bug #1172763: dumbdbm would die if the line endings
        # weren't what was expected.
        f = dumbdbm.open(_fname)
        f['1'] = 'hello'
        f['2'] = 'hello2'
        f.close()

        # Mangle the file by adding \r before each newline
        with open(_fname + '.dir') as f:
            data = f.read()
        data = data.replace('\n', '\r\n')
        with open(_fname + '.dir', 'wb') as f:
            f.write(data)

        f = dumbdbm.open(_fname)
        self.assertEqual(f['1'], 'hello')
        self.assertEqual(f['2'], 'hello2')

        f.close()

    def read_helper(self, f):
        keys = self.keys_helper(f)
        for key in self._dict:
            self.assertEqual(self._dict[key], f[key])

    def init_db(self):
        f = dumbdbm.open(_fname, 'w')
        for k in self._dict:
            f[k] = self._dict[k]
        f.close()

    def keys_helper(self, f):
        keys = f.keys()
        keys.sort()
        dkeys = self._dict.keys()
        dkeys.sort()
        self.assertEqual(keys, dkeys)
        return keys

    # Perform randomized operations.  This doesn't make assumptions about
    # what *might* fail.
    def test_random(self):
        import random
        d = {}  # mirror the database
        for dummy in range(5):
            f = dumbdbm.open(_fname)
            for dummy in range(100):
                k = random.choice('abcdefghijklm')
                if random.random() < 0.2:
                    if k in d:
                        del d[k]
                        del f[k]
                else:
                    v = random.choice('abc') * random.randrange(10000)
                    d[k] = v
                    f[k] = v
                    self.assertEqual(f[k], v)
            f.close()

            f = dumbdbm.open(_fname)
            expected = d.items()
            expected.sort()
            got = f.items()
            got.sort()
            self.assertEqual(expected, got)
            f.close()

    def test_eval(self):
        with open(_fname + '.dir', 'w') as stream:
            stream.write("str(__import__('sys').stdout.write('Hacked!')), 0\n")
        with test_support.captured_stdout() as stdout:
            with self.assertRaises(ValueError):
                dumbdbm.open(_fname).close()
            self.assertEqual(stdout.getvalue(), '')

    @unittest.skipUnless(hasattr(os, 'chmod'), 'test needs os.chmod()')
    @unittest.skipIf(sys.platform == 'cli', 'TODO: investigate failure')
    def test_readonly_files(self):
        dir = _fname
        os.mkdir(dir)
        try:
            fname = os.path.join(dir, 'db')
            f = dumbdbm.open(fname, 'n')
            self.assertEqual(list(f.keys()), [])
            for key in self._dict:
                f[key] = self._dict[key]
            f.close()
            os.chmod(fname + ".dir", stat.S_IRUSR)
            os.chmod(fname + ".dat", stat.S_IRUSR)
            os.chmod(dir, stat.S_IRUSR|stat.S_IXUSR)
            f = dumbdbm.open(fname, 'r')
            self.assertEqual(sorted(f.keys()), sorted(self._dict))
            f.close()  # don't write
        finally:
            test_support.rmtree(dir)

    def tearDown(self):
        _delete_files()

    def setUp(self):
        _delete_files()

def test_main():
    try:
        test_support.run_unittest(DumbDBMTestCase)
    finally:
        _delete_files()

if __name__ == "__main__":
    test_main()