1"""Test script for the dumbdbm module
2   Original by Roger E. Masse
3"""
4
5import os
6import stat
7import unittest
8import dumbdbm
9from test import test_support
10
11_fname = test_support.TESTFN
12
13def _delete_files():
14    for ext in [".dir", ".dat", ".bak"]:
15        try:
16            os.unlink(_fname + ext)
17        except OSError:
18            pass
19
20class DumbDBMTestCase(unittest.TestCase):
21    _dict = {'0': '',
22             'a': 'Python:',
23             'b': 'Programming',
24             'c': 'the',
25             'd': 'way',
26             'f': 'Guido',
27             'g': 'intended'
28             }
29
30    def __init__(self, *args):
31        unittest.TestCase.__init__(self, *args)
32
33    def test_dumbdbm_creation(self):
34        f = dumbdbm.open(_fname, 'c')
35        self.assertEqual(f.keys(), [])
36        for key in self._dict:
37            f[key] = self._dict[key]
38        self.read_helper(f)
39        f.close()
40
41    @unittest.skipUnless(hasattr(os, 'chmod'), 'os.chmod not available')
42    @unittest.skipUnless(hasattr(os, 'umask'), 'os.umask not available')
43    def test_dumbdbm_creation_mode(self):
44        try:
45            old_umask = os.umask(0002)
46            f = dumbdbm.open(_fname, 'c', 0637)
47            f.close()
48        finally:
49            os.umask(old_umask)
50
51        expected_mode = 0635
52        if os.name != 'posix':
53            # Windows only supports setting the read-only attribute.
54            # This shouldn't fail, but doesn't work like Unix either.
55            expected_mode = 0666
56
57        import stat
58        st = os.stat(_fname + '.dat')
59        self.assertEqual(stat.S_IMODE(st.st_mode), expected_mode)
60        st = os.stat(_fname + '.dir')
61        self.assertEqual(stat.S_IMODE(st.st_mode), expected_mode)
62
63    def test_close_twice(self):
64        f = dumbdbm.open(_fname)
65        f['a'] = 'b'
66        self.assertEqual(f['a'], 'b')
67        f.close()
68        f.close()
69
70    def test_dumbdbm_modification(self):
71        self.init_db()
72        f = dumbdbm.open(_fname, 'w')
73        self._dict['g'] = f['g'] = "indented"
74        self.read_helper(f)
75        f.close()
76
77    def test_dumbdbm_read(self):
78        self.init_db()
79        f = dumbdbm.open(_fname, 'r')
80        self.read_helper(f)
81        f.close()
82
83    def test_dumbdbm_keys(self):
84        self.init_db()
85        f = dumbdbm.open(_fname)
86        keys = self.keys_helper(f)
87        f.close()
88
89    def test_write_write_read(self):
90        # test for bug #482460
91        f = dumbdbm.open(_fname)
92        f['1'] = 'hello'
93        f['1'] = 'hello2'
94        f.close()
95        f = dumbdbm.open(_fname)
96        self.assertEqual(f['1'], 'hello2')
97        f.close()
98
99    def test_line_endings(self):
100        # test for bug #1172763: dumbdbm would die if the line endings
101        # weren't what was expected.
102        f = dumbdbm.open(_fname)
103        f['1'] = 'hello'
104        f['2'] = 'hello2'
105        f.close()
106
107        # Mangle the file by adding \r before each newline
108        data = open(_fname + '.dir').read()
109        data = data.replace('\n', '\r\n')
110        open(_fname + '.dir', 'wb').write(data)
111
112        f = dumbdbm.open(_fname)
113        self.assertEqual(f['1'], 'hello')
114        self.assertEqual(f['2'], 'hello2')
115
116
117    def read_helper(self, f):
118        keys = self.keys_helper(f)
119        for key in self._dict:
120            self.assertEqual(self._dict[key], f[key])
121
122    def init_db(self):
123        f = dumbdbm.open(_fname, 'w')
124        for k in self._dict:
125            f[k] = self._dict[k]
126        f.close()
127
128    def keys_helper(self, f):
129        keys = f.keys()
130        keys.sort()
131        dkeys = self._dict.keys()
132        dkeys.sort()
133        self.assertEqual(keys, dkeys)
134        return keys
135
136    # Perform randomized operations.  This doesn't make assumptions about
137    # what *might* fail.
138    def test_random(self):
139        import random
140        d = {}  # mirror the database
141        for dummy in range(5):
142            f = dumbdbm.open(_fname)
143            for dummy in range(100):
144                k = random.choice('abcdefghijklm')
145                if random.random() < 0.2:
146                    if k in d:
147                        del d[k]
148                        del f[k]
149                else:
150                    v = random.choice('abc') * random.randrange(10000)
151                    d[k] = v
152                    f[k] = v
153                    self.assertEqual(f[k], v)
154            f.close()
155
156            f = dumbdbm.open(_fname)
157            expected = d.items()
158            expected.sort()
159            got = f.items()
160            got.sort()
161            self.assertEqual(expected, got)
162            f.close()
163
164    def test_eval(self):
165        with open(_fname + '.dir', 'w') as stream:
166            stream.write("str(__import__('sys').stdout.write('Hacked!')), 0\n")
167        with test_support.captured_stdout() as stdout:
168            with self.assertRaises(ValueError):
169                dumbdbm.open(_fname).close()
170            self.assertEqual(stdout.getvalue(), '')
171
172    @unittest.skipUnless(hasattr(os, 'chmod'), 'test needs os.chmod()')
173    def test_readonly_files(self):
174        dir = _fname
175        os.mkdir(dir)
176        try:
177            fname = os.path.join(dir, 'db')
178            f = dumbdbm.open(fname, 'n')
179            self.assertEqual(list(f.keys()), [])
180            for key in self._dict:
181                f[key] = self._dict[key]
182            f.close()
183            os.chmod(fname + ".dir", stat.S_IRUSR)
184            os.chmod(fname + ".dat", stat.S_IRUSR)
185            os.chmod(dir, stat.S_IRUSR|stat.S_IXUSR)
186            f = dumbdbm.open(fname, 'r')
187            self.assertEqual(sorted(f.keys()), sorted(self._dict))
188            f.close()  # don't write
189        finally:
190            test_support.rmtree(dir)
191
192    def tearDown(self):
193        _delete_files()
194
195    def setUp(self):
196        _delete_files()
197
198def test_main():
199    try:
200        test_support.run_unittest(DumbDBMTestCase)
201    finally:
202        _delete_files()
203
204if __name__ == "__main__":
205    test_main()
206