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 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_bytes(self): 122 with gdbm.open(filename, 'c') as db: 123 db[b'bytes key \xbd'] = b'bytes value \xbd' 124 with gdbm.open(filename, 'r') as db: 125 self.assertEqual(list(db.keys()), [b'bytes key \xbd']) 126 self.assertTrue(b'bytes key \xbd' in db) 127 self.assertEqual(db[b'bytes key \xbd'], b'bytes value \xbd') 128 129 def test_unicode(self): 130 with gdbm.open(filename, 'c') as db: 131 db['Unicode key \U0001f40d'] = 'Unicode value \U0001f40d' 132 with gdbm.open(filename, 'r') as db: 133 self.assertEqual(list(db.keys()), ['Unicode key \U0001f40d'.encode()]) 134 self.assertTrue('Unicode key \U0001f40d'.encode() in db) 135 self.assertTrue('Unicode key \U0001f40d' in db) 136 self.assertEqual(db['Unicode key \U0001f40d'.encode()], 137 'Unicode value \U0001f40d'.encode()) 138 self.assertEqual(db['Unicode key \U0001f40d'], 139 'Unicode value \U0001f40d'.encode()) 140 141 def test_write_readonly_file(self): 142 with gdbm.open(filename, 'c') as db: 143 db[b'bytes key'] = b'bytes value' 144 with gdbm.open(filename, 'r') as db: 145 with self.assertRaises(gdbm.error): 146 del db[b'not exist key'] 147 with self.assertRaises(gdbm.error): 148 del db[b'bytes key'] 149 with self.assertRaises(gdbm.error): 150 db[b'not exist key'] = b'not exist value' 151 152 @unittest.skipUnless(TESTFN_NONASCII, 153 'requires OS support of non-ASCII encodings') 154 def test_nonascii_filename(self): 155 filename = TESTFN_NONASCII 156 self.addCleanup(unlink, filename) 157 with gdbm.open(filename, 'c') as db: 158 db[b'key'] = b'value' 159 self.assertTrue(os.path.exists(filename)) 160 with gdbm.open(filename, 'r') as db: 161 self.assertEqual(list(db.keys()), [b'key']) 162 self.assertTrue(b'key' in db) 163 self.assertEqual(db[b'key'], b'value') 164 165 def test_nonexisting_file(self): 166 nonexisting_file = 'nonexisting-file' 167 with self.assertRaises(gdbm.error) as cm: 168 gdbm.open(nonexisting_file) 169 self.assertIn(nonexisting_file, str(cm.exception)) 170 self.assertEqual(cm.exception.filename, nonexisting_file) 171 172 173if __name__ == '__main__': 174 unittest.main() 175