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