1from test import support
2gdbm = support.import_module("dbm.gnu") #skip if not supported
3import unittest
4import os
5from test.support import TESTFN, TESTFN_NONASCII, unlink
6
7
8filename = TESTFN
9
10class TestGdbm(unittest.TestCase):
11    @staticmethod
12    def setUpClass():
13        if support.verbose:
14            try:
15                from _gdbm import _GDBM_VERSION as version
16            except ImportError:
17                pass
18            else:
19                print(f"gdbm version: {version}")
20
21    def setUp(self):
22        self.g = None
23
24    def tearDown(self):
25        if self.g is not None:
26            self.g.close()
27        unlink(filename)
28
29    def test_key_methods(self):
30        self.g = gdbm.open(filename, 'c')
31        self.assertEqual(self.g.keys(), [])
32        self.g['a'] = 'b'
33        self.g['12345678910'] = '019237410982340912840198242'
34        self.g[b'bytes'] = b'data'
35        key_set = set(self.g.keys())
36        self.assertEqual(key_set, set([b'a', b'bytes', b'12345678910']))
37        self.assertIn('a', self.g)
38        self.assertIn(b'a', self.g)
39        self.assertEqual(self.g[b'bytes'], b'data')
40        key = self.g.firstkey()
41        while key:
42            self.assertIn(key, key_set)
43            key_set.remove(key)
44            key = self.g.nextkey(key)
45        # get() and setdefault() work as in the dict interface
46        self.assertEqual(self.g.get(b'a'), b'b')
47        self.assertIsNone(self.g.get(b'xxx'))
48        self.assertEqual(self.g.get(b'xxx', b'foo'), b'foo')
49        with self.assertRaises(KeyError):
50            self.g['xxx']
51        self.assertEqual(self.g.setdefault(b'xxx', b'foo'), b'foo')
52        self.assertEqual(self.g[b'xxx'], b'foo')
53
54    def test_error_conditions(self):
55        # Try to open a non-existent database.
56        unlink(filename)
57        self.assertRaises(gdbm.error, gdbm.open, filename, 'r')
58        # Try to access a closed database.
59        self.g = gdbm.open(filename, 'c')
60        self.g.close()
61        self.assertRaises(gdbm.error, lambda: self.g['a'])
62        # try pass an invalid open flag
63        self.assertRaises(gdbm.error, lambda: gdbm.open(filename, 'rx').close())
64
65    def test_flags(self):
66        # Test the flag parameter open() by trying all supported flag modes.
67        all = set(gdbm.open_flags)
68        # Test standard flags (presumably "crwn").
69        modes = all - set('fsu')
70        for mode in sorted(modes):  # put "c" mode first
71            self.g = gdbm.open(filename, mode)
72            self.g.close()
73
74        # Test additional flags (presumably "fsu").
75        flags = all - set('crwn')
76        for mode in modes:
77            for flag in flags:
78                self.g = gdbm.open(filename, mode + flag)
79                self.g.close()
80
81    def test_reorganize(self):
82        self.g = gdbm.open(filename, 'c')
83        size0 = os.path.getsize(filename)
84
85        # bpo-33901: on macOS with gdbm 1.15, an empty database uses 16 MiB
86        # and adding an entry of 10,000 B has no effect on the file size.
87        # Add size0 bytes to make sure that the file size changes.
88        value_size = max(size0, 10000)
89        self.g['x'] = 'x' * value_size
90        size1 = os.path.getsize(filename)
91        self.assertGreater(size1, size0)
92
93        del self.g['x']
94        # 'size' is supposed to be the same even after deleting an entry.
95        self.assertEqual(os.path.getsize(filename), size1)
96
97        self.g.reorganize()
98        size2 = os.path.getsize(filename)
99        self.assertLess(size2, size1)
100        self.assertGreaterEqual(size2, size0)
101
102    def test_context_manager(self):
103        with gdbm.open(filename, 'c') as db:
104            db["gdbm context manager"] = "context manager"
105
106        with gdbm.open(filename, 'r') as db:
107            self.assertEqual(list(db.keys()), [b"gdbm context manager"])
108
109        with self.assertRaises(gdbm.error) as cm:
110            db.keys()
111        self.assertEqual(str(cm.exception),
112                         "GDBM object has already been closed")
113
114    def test_bytes(self):
115        with gdbm.open(filename, 'c') as db:
116            db[b'bytes key \xbd'] = b'bytes value \xbd'
117        with gdbm.open(filename, 'r') as db:
118            self.assertEqual(list(db.keys()), [b'bytes key \xbd'])
119            self.assertTrue(b'bytes key \xbd' in db)
120            self.assertEqual(db[b'bytes key \xbd'], b'bytes value \xbd')
121
122    def test_unicode(self):
123        with gdbm.open(filename, 'c') as db:
124            db['Unicode key \U0001f40d'] = 'Unicode value \U0001f40d'
125        with gdbm.open(filename, 'r') as db:
126            self.assertEqual(list(db.keys()), ['Unicode key \U0001f40d'.encode()])
127            self.assertTrue('Unicode key \U0001f40d'.encode() in db)
128            self.assertTrue('Unicode key \U0001f40d' in db)
129            self.assertEqual(db['Unicode key \U0001f40d'.encode()],
130                             'Unicode value \U0001f40d'.encode())
131            self.assertEqual(db['Unicode key \U0001f40d'],
132                             'Unicode value \U0001f40d'.encode())
133
134    def test_write_readonly_file(self):
135        with gdbm.open(filename, 'c') as db:
136            db[b'bytes key'] = b'bytes value'
137        with gdbm.open(filename, 'r') as db:
138            with self.assertRaises(gdbm.error):
139                del db[b'not exist key']
140            with self.assertRaises(gdbm.error):
141                del db[b'bytes key']
142            with self.assertRaises(gdbm.error):
143                db[b'not exist key'] = b'not exist value'
144
145    @unittest.skipUnless(TESTFN_NONASCII,
146                         'requires OS support of non-ASCII encodings')
147    def test_nonascii_filename(self):
148        filename = TESTFN_NONASCII
149        self.addCleanup(unlink, filename)
150        with gdbm.open(filename, 'c') as db:
151            db[b'key'] = b'value'
152        self.assertTrue(os.path.exists(filename))
153        with gdbm.open(filename, 'r') as db:
154            self.assertEqual(list(db.keys()), [b'key'])
155            self.assertTrue(b'key' in db)
156            self.assertEqual(db[b'key'], b'value')
157
158    def test_nonexisting_file(self):
159        nonexisting_file = 'nonexisting-file'
160        with self.assertRaises(gdbm.error) as cm:
161            gdbm.open(nonexisting_file)
162        self.assertIn(nonexisting_file, str(cm.exception))
163        self.assertEqual(cm.exception.filename, nonexisting_file)
164
165
166if __name__ == '__main__':
167    unittest.main()
168