blob: 257b3846fea6a84e5e06593fea139de7b1cd2ee5 [file] [log] [blame]
Martin v. Löwis1c6b1a22002-11-19 17:47:07 +00001"""
2Basic TestCases for BTree and hash DBs, with and without a DBEnv, with
3various DB flags, etc.
4"""
5
6import sys, os, string
7import tempfile
8from pprint import pprint
9import unittest
10
11from bsddb import db
12
13from test.test_support import verbose
14
15
16#----------------------------------------------------------------------
17
18class VersionTestCase(unittest.TestCase):
19 def test00_version(self):
20 info = db.version()
21 if verbose:
22 print '\n', '-=' * 20
23 print 'bsddb.db.version(): %s' % (info, )
24 print db.DB_VERSION_STRING
25 print '-=' * 20
26 assert info == (db.DB_VERSION_MAJOR, db.DB_VERSION_MINOR, db.DB_VERSION_PATCH)
27
28#----------------------------------------------------------------------
29
30class BasicTestCase(unittest.TestCase):
31 dbtype = db.DB_UNKNOWN # must be set in derived class
32 dbopenflags = 0
33 dbsetflags = 0
34 dbmode = 0660
35 dbname = None
36 useEnv = 0
37 envflags = 0
38
39 def setUp(self):
40 if self.useEnv:
41 homeDir = os.path.join(os.path.dirname(sys.argv[0]), 'db_home')
42 try: os.mkdir(homeDir)
43 except os.error: pass
44 self.env = db.DBEnv()
45 self.env.set_lg_max(1024*1024)
46 self.env.open(homeDir, self.envflags | db.DB_CREATE)
47 tempfile.tempdir = homeDir
48 self.filename = os.path.split(tempfile.mktemp())[1]
49 tempfile.tempdir = None
50 self.homeDir = homeDir
51 else:
52 self.env = None
53 self.filename = tempfile.mktemp()
54
55 # create and open the DB
56 self.d = db.DB(self.env)
57 self.d.set_flags(self.dbsetflags)
58 if self.dbname:
59 self.d.open(self.filename, self.dbname, self.dbtype,
60 self.dbopenflags|db.DB_CREATE, self.dbmode)
61 else:
62 self.d.open(self.filename, # try out keyword args
63 mode = self.dbmode,
64 dbtype = self.dbtype, flags = self.dbopenflags|db.DB_CREATE)
65
66 self.populateDB()
67
68
69 def tearDown(self):
70 self.d.close()
71 if self.env is not None:
72 self.env.close()
73
74 import glob
75 files = glob.glob(os.path.join(self.homeDir, '*'))
76 for file in files:
77 os.remove(file)
78
79 ## Make a new DBEnv to remove the env files from the home dir.
80 ## (It can't be done while the env is open, nor after it has been
81 ## closed, so we make a new one to do it.)
82 #e = db.DBEnv()
83 #e.remove(self.homeDir)
84 #os.remove(os.path.join(self.homeDir, self.filename))
85
86 else:
87 os.remove(self.filename)
88
89
90
91 def populateDB(self):
92 d = self.d
93 for x in range(500):
94 key = '%04d' % (1000 - x) # insert keys in reverse order
95 data = self.makeData(key)
96 d.put(key, data)
97
98 for x in range(500):
99 key = '%04d' % x # and now some in forward order
100 data = self.makeData(key)
101 d.put(key, data)
102
103 num = len(d)
104 if verbose:
105 print "created %d records" % num
106
107
108 def makeData(self, key):
109 return string.join([key] * 5, '-')
110
111
112
113 #----------------------------------------
114
115 def test01_GetsAndPuts(self):
116 d = self.d
117
118 if verbose:
119 print '\n', '-=' * 30
120 print "Running %s.test01_GetsAndPuts..." % self.__class__.__name__
121
122 for key in ['0001', '0100', '0400', '0700', '0999']:
123 data = d.get(key)
124 if verbose:
125 print data
126
127 assert d.get('0321') == '0321-0321-0321-0321-0321'
128
129 # By default non-existant keys return None...
130 assert d.get('abcd') == None
131
132 # ...but they raise exceptions in other situations. Call
133 # set_get_returns_none() to change it.
134 try:
135 d.delete('abcd')
136 except db.DBNotFoundError, val:
137 assert val[0] == db.DB_NOTFOUND
138 if verbose: print val
139 else:
140 self.fail("expected exception")
141
142
143 d.put('abcd', 'a new record')
144 assert d.get('abcd') == 'a new record'
145
146 d.put('abcd', 'same key')
147 if self.dbsetflags & db.DB_DUP:
148 assert d.get('abcd') == 'a new record'
149 else:
150 assert d.get('abcd') == 'same key'
151
152
153 try:
154 d.put('abcd', 'this should fail', flags=db.DB_NOOVERWRITE)
155 except db.DBKeyExistError, val:
156 assert val[0] == db.DB_KEYEXIST
157 if verbose: print val
158 else:
159 self.fail("expected exception")
160
161 if self.dbsetflags & db.DB_DUP:
162 assert d.get('abcd') == 'a new record'
163 else:
164 assert d.get('abcd') == 'same key'
165
166
167 d.sync()
168 d.close()
169 del d
170
171 self.d = db.DB(self.env)
172 if self.dbname:
173 self.d.open(self.filename, self.dbname)
174 else:
175 self.d.open(self.filename)
176 d = self.d
177
178 assert d.get('0321') == '0321-0321-0321-0321-0321'
179 if self.dbsetflags & db.DB_DUP:
180 assert d.get('abcd') == 'a new record'
181 else:
182 assert d.get('abcd') == 'same key'
183
184 rec = d.get_both('0555', '0555-0555-0555-0555-0555')
185 if verbose:
186 print rec
187
188 assert d.get_both('0555', 'bad data') == None
189
190 # test default value
191 data = d.get('bad key', 'bad data')
192 assert data == 'bad data'
193
194 # any object can pass through
195 data = d.get('bad key', self)
196 assert data == self
197
198 s = d.stat()
199 assert type(s) == type({})
200 if verbose:
201 print 'd.stat() returned this dictionary:'
202 pprint(s)
203
204
205 #----------------------------------------
206
207 def test02_DictionaryMethods(self):
208 d = self.d
209
210 if verbose:
211 print '\n', '-=' * 30
212 print "Running %s.test02_DictionaryMethods..." % self.__class__.__name__
213
214 for key in ['0002', '0101', '0401', '0701', '0998']:
215 data = d[key]
216 assert data == self.makeData(key)
217 if verbose:
218 print data
219
220 assert len(d) == 1000
221 keys = d.keys()
222 assert len(keys) == 1000
223 assert type(keys) == type([])
224
225 d['new record'] = 'a new record'
226 assert len(d) == 1001
227 keys = d.keys()
228 assert len(keys) == 1001
229
230 d['new record'] = 'a replacement record'
231 assert len(d) == 1001
232 keys = d.keys()
233 assert len(keys) == 1001
234
235 if verbose:
236 print "the first 10 keys are:"
237 pprint(keys[:10])
238
239 assert d['new record'] == 'a replacement record'
240
241 assert d.has_key('0001') == 1
242 assert d.has_key('spam') == 0
243
244 items = d.items()
245 assert len(items) == 1001
246 assert type(items) == type([])
247 assert type(items[0]) == type(())
248 assert len(items[0]) == 2
249
250 if verbose:
251 print "the first 10 items are:"
252 pprint(items[:10])
253
254 values = d.values()
255 assert len(values) == 1001
256 assert type(values) == type([])
257
258 if verbose:
259 print "the first 10 values are:"
260 pprint(values[:10])
261
262
263
264 #----------------------------------------
265
266 def test03_SimpleCursorStuff(self):
267 if verbose:
268 print '\n', '-=' * 30
269 print "Running %s.test03_SimpleCursorStuff..." % self.__class__.__name__
270
271 c = self.d.cursor()
272
273
274 rec = c.first()
275 count = 0
276 while rec is not None:
277 count = count + 1
278 if verbose and count % 100 == 0:
279 print rec
280 rec = c.next()
281
282 assert count == 1000
283
284
285 rec = c.last()
286 count = 0
287 while rec is not None:
288 count = count + 1
289 if verbose and count % 100 == 0:
290 print rec
291 rec = c.prev()
292
293 assert count == 1000
294
295 rec = c.set('0505')
296 rec2 = c.current()
297 assert rec == rec2
298 assert rec[0] == '0505'
299 assert rec[1] == self.makeData('0505')
300
301 try:
302 c.set('bad key')
303 except db.DBNotFoundError, val:
304 assert val[0] == db.DB_NOTFOUND
305 if verbose: print val
306 else:
307 self.fail("expected exception")
308
309 rec = c.get_both('0404', self.makeData('0404'))
310 assert rec == ('0404', self.makeData('0404'))
311
312 try:
313 c.get_both('0404', 'bad data')
314 except db.DBNotFoundError, val:
315 assert val[0] == db.DB_NOTFOUND
316 if verbose: print val
317 else:
318 self.fail("expected exception")
319
320 if self.d.get_type() == db.DB_BTREE:
321 rec = c.set_range('011')
322 if verbose:
323 print "searched for '011', found: ", rec
324
325 rec = c.set_range('011',dlen=0,doff=0)
326 if verbose:
327 print "searched (partial) for '011', found: ", rec
328 if rec[1] != '': set.fail('expected empty data portion')
329
330 c.set('0499')
331 c.delete()
332 try:
333 rec = c.current()
334 except db.DBKeyEmptyError, val:
335 assert val[0] == db.DB_KEYEMPTY
336 if verbose: print val
337 else:
338 self.fail('exception expected')
339
340 c.next()
341 c2 = c.dup(db.DB_POSITION)
342 assert c.current() == c2.current()
343
344 c2.put('', 'a new value', db.DB_CURRENT)
345 assert c.current() == c2.current()
346 assert c.current()[1] == 'a new value'
347
348 c2.put('', 'er', db.DB_CURRENT, dlen=0, doff=5)
349 assert c2.current()[1] == 'a newer value'
350
351 c.close()
352 c2.close()
353
354 # time to abuse the closed cursors and hope we don't crash
355 methods_to_test = {
356 'current': (),
357 'delete': (),
358 'dup': (db.DB_POSITION,),
359 'first': (),
360 'get': (0,),
361 'next': (),
362 'prev': (),
363 'last': (),
364 'put':('', 'spam', db.DB_CURRENT),
365 'set': ("0505",),
366 }
367 for method, args in methods_to_test.items():
368 try:
369 if verbose:
370 print "attempting to use a closed cursor's %s method" % method
371 # a bug may cause a NULL pointer dereference...
372 apply(getattr(c, method), args)
373 except db.DBError, val:
374 assert val[0] == 0
375 if verbose: print val
376 else:
377 self.fail("no exception raised when using a buggy cursor's %s method" % method)
378
379 #----------------------------------------
380
381 def test04_PartialGetAndPut(self):
382 d = self.d
383 if verbose:
384 print '\n', '-=' * 30
385 print "Running %s.test04_PartialGetAndPut..." % self.__class__.__name__
386
387 key = "partialTest"
388 data = "1" * 1000 + "2" * 1000
389 d.put(key, data)
390 assert d.get(key) == data
391 assert d.get(key, dlen=20, doff=990) == ("1" * 10) + ("2" * 10)
392
393 d.put("partialtest2", ("1" * 30000) + "robin" )
394 assert d.get("partialtest2", dlen=5, doff=30000) == "robin"
395
396 # There seems to be a bug in DB here... Commented out the test for now.
397 ##assert d.get("partialtest2", dlen=5, doff=30010) == ""
398
399 if self.dbsetflags != db.DB_DUP:
400 # Partial put with duplicate records requires a cursor
401 d.put(key, "0000", dlen=2000, doff=0)
402 assert d.get(key) == "0000"
403
404 d.put(key, "1111", dlen=1, doff=2)
405 assert d.get(key) == "0011110"
406
407 #----------------------------------------
408
409 def test05_GetSize(self):
410 d = self.d
411 if verbose:
412 print '\n', '-=' * 30
413 print "Running %s.test05_GetSize..." % self.__class__.__name__
414
415 for i in range(1, 50000, 500):
416 key = "size%s" % i
417 #print "before ", i,
418 d.put(key, "1" * i)
419 #print "after",
420 assert d.get_size(key) == i
421 #print "done"
422
423 #----------------------------------------
424
425 def test06_Truncate(self):
426 d = self.d
427 if verbose:
428 print '\n', '-=' * 30
429 print "Running %s.test99_Truncate..." % self.__class__.__name__
430
431 d.put("abcde", "ABCDE");
432 num = d.truncate()
433 assert num >= 1, "truncate returned <= 0 on non-empty database"
434 num = d.truncate()
435 assert num == 0, "truncate on empty DB returned nonzero (%s)" % `num`
436
437#----------------------------------------------------------------------
438
439
440class BasicBTreeTestCase(BasicTestCase):
441 dbtype = db.DB_BTREE
442
443
444class BasicHashTestCase(BasicTestCase):
445 dbtype = db.DB_HASH
446
447
448class BasicBTreeWithThreadFlagTestCase(BasicTestCase):
449 dbtype = db.DB_BTREE
450 dbopenflags = db.DB_THREAD
451
452
453class BasicHashWithThreadFlagTestCase(BasicTestCase):
454 dbtype = db.DB_HASH
455 dbopenflags = db.DB_THREAD
456
457
458class BasicBTreeWithEnvTestCase(BasicTestCase):
459 dbtype = db.DB_BTREE
460 dbopenflags = db.DB_THREAD
461 useEnv = 1
462 envflags = db.DB_THREAD | db.DB_INIT_MPOOL | db.DB_INIT_LOCK
463
464
465class BasicHashWithEnvTestCase(BasicTestCase):
466 dbtype = db.DB_HASH
467 dbopenflags = db.DB_THREAD
468 useEnv = 1
469 envflags = db.DB_THREAD | db.DB_INIT_MPOOL | db.DB_INIT_LOCK
470
471
472#----------------------------------------------------------------------
473
474class BasicTransactionTestCase(BasicTestCase):
475 dbopenflags = db.DB_THREAD
476 useEnv = 1
477 envflags = db.DB_THREAD | db.DB_INIT_MPOOL | db.DB_INIT_LOCK | db.DB_INIT_TXN
478
479
480 def tearDown(self):
481 self.txn.commit()
482 BasicTestCase.tearDown(self)
483
484
485 def populateDB(self):
486 d = self.d
487 txn = self.env.txn_begin()
488 for x in range(500):
489 key = '%04d' % (1000 - x) # insert keys in reverse order
490 data = self.makeData(key)
491 d.put(key, data, txn)
492
493 for x in range(500):
494 key = '%04d' % x # and now some in forward order
495 data = self.makeData(key)
496 d.put(key, data, txn)
497
498 txn.commit()
499
500 num = len(d)
501 if verbose:
502 print "created %d records" % num
503
504 self.txn = self.env.txn_begin()
505
506
507
508 def test06_Transactions(self):
509 d = self.d
510 if verbose:
511 print '\n', '-=' * 30
512 print "Running %s.test06_Transactions..." % self.__class__.__name__
513
514 assert d.get('new rec', txn=self.txn) == None
515 d.put('new rec', 'this is a new record', self.txn)
516 assert d.get('new rec', txn=self.txn) == 'this is a new record'
517 self.txn.abort()
518 assert d.get('new rec') == None
519
520 self.txn = self.env.txn_begin()
521
522 assert d.get('new rec', txn=self.txn) == None
523 d.put('new rec', 'this is a new record', self.txn)
524 assert d.get('new rec', txn=self.txn) == 'this is a new record'
525 self.txn.commit()
526 assert d.get('new rec') == 'this is a new record'
527
528 self.txn = self.env.txn_begin()
529 c = d.cursor(self.txn)
530 rec = c.first()
531 count = 0
532 while rec is not None:
533 count = count + 1
534 if verbose and count % 100 == 0:
535 print rec
536 rec = c.next()
537 assert count == 1001
538
539 c.close() # Cursors *MUST* be closed before commit!
540 self.txn.commit()
541
542 # flush pending updates
543 try:
544 self.env.txn_checkpoint (0, 0, 0)
545 except db.DBIncompleteError:
546 pass
547
548 # must have at least one log file present:
549 logs = self.env.log_archive(db.DB_ARCH_ABS | db.DB_ARCH_LOG)
550 assert logs != None
551 for log in logs:
552 if verbose:
553 print 'log file: ' + log
554
555 self.txn = self.env.txn_begin()
556
557 #----------------------------------------
558
559 def test07_TxnTruncate(self):
560 d = self.d
561 if verbose:
562 print '\n', '-=' * 30
563 print "Running %s.test07_TxnTruncate..." % self.__class__.__name__
564
565 d.put("abcde", "ABCDE");
566 txn = self.env.txn_begin()
567 num = d.truncate(txn)
568 assert num >= 1, "truncate returned <= 0 on non-empty database"
569 num = d.truncate(txn)
570 assert num == 0, "truncate on empty DB returned nonzero (%s)" % `num`
571 txn.commit()
572
573
574
575class BTreeTransactionTestCase(BasicTransactionTestCase):
576 dbtype = db.DB_BTREE
577
578class HashTransactionTestCase(BasicTransactionTestCase):
579 dbtype = db.DB_HASH
580
581
582
583#----------------------------------------------------------------------
584
585class BTreeRecnoTestCase(BasicTestCase):
586 dbtype = db.DB_BTREE
587 dbsetflags = db.DB_RECNUM
588
589 def test07_RecnoInBTree(self):
590 d = self.d
591 if verbose:
592 print '\n', '-=' * 30
593 print "Running %s.test07_RecnoInBTree..." % self.__class__.__name__
594
595 rec = d.get(200)
596 assert type(rec) == type(())
597 assert len(rec) == 2
598 if verbose:
599 print "Record #200 is ", rec
600
601 c = d.cursor()
602 c.set('0200')
603 num = c.get_recno()
604 assert type(num) == type(1)
605 if verbose:
606 print "recno of d['0200'] is ", num
607
608 rec = c.current()
609 assert c.set_recno(num) == rec
610
611 c.close()
612
613
614
615class BTreeRecnoWithThreadFlagTestCase(BTreeRecnoTestCase):
616 dbopenflags = db.DB_THREAD
617
618#----------------------------------------------------------------------
619
620class BasicDUPTestCase(BasicTestCase):
621 dbsetflags = db.DB_DUP
622
623 def test08_DuplicateKeys(self):
624 d = self.d
625 if verbose:
626 print '\n', '-=' * 30
627 print "Running %s.test08_DuplicateKeys..." % self.__class__.__name__
628
629 d.put("dup0", "before")
630 for x in string.split("The quick brown fox jumped over the lazy dog."):
631 d.put("dup1", x)
632 d.put("dup2", "after")
633
634 data = d.get("dup1")
635 assert data == "The"
636 if verbose:
637 print data
638
639 c = d.cursor()
640 rec = c.set("dup1")
641 assert rec == ('dup1', 'The')
642
643 next = c.next()
644 assert next == ('dup1', 'quick')
645
646 rec = c.set("dup1")
647 count = c.count()
648 assert count == 9
649
650 next_dup = c.next_dup()
651 assert next_dup == ('dup1', 'quick')
652
653 rec = c.set('dup1')
654 while rec is not None:
655 if verbose:
656 print rec
657 rec = c.next_dup()
658
659 c.set('dup1')
660 rec = c.next_nodup()
661 assert rec[0] != 'dup1'
662 if verbose:
663 print rec
664
665 c.close()
666
667
668
669class BTreeDUPTestCase(BasicDUPTestCase):
670 dbtype = db.DB_BTREE
671
672class HashDUPTestCase(BasicDUPTestCase):
673 dbtype = db.DB_HASH
674
675class BTreeDUPWithThreadTestCase(BasicDUPTestCase):
676 dbtype = db.DB_BTREE
677 dbopenflags = db.DB_THREAD
678
679class HashDUPWithThreadTestCase(BasicDUPTestCase):
680 dbtype = db.DB_HASH
681 dbopenflags = db.DB_THREAD
682
683
684#----------------------------------------------------------------------
685
686class BasicMultiDBTestCase(BasicTestCase):
687 dbname = 'first'
688
689 def otherType(self):
690 if self.dbtype == db.DB_BTREE:
691 return db.DB_HASH
692 else:
693 return db.DB_BTREE
694
695 def test09_MultiDB(self):
696 d1 = self.d
697 if verbose:
698 print '\n', '-=' * 30
699 print "Running %s.test09_MultiDB..." % self.__class__.__name__
700
701 d2 = db.DB(self.env)
702 d2.open(self.filename, "second", self.dbtype, self.dbopenflags|db.DB_CREATE)
703 d3 = db.DB(self.env)
704 d3.open(self.filename, "third", self.otherType(), self.dbopenflags|db.DB_CREATE)
705
706 for x in string.split("The quick brown fox jumped over the lazy dog"):
707 d2.put(x, self.makeData(x))
708
709 for x in string.letters:
710 d3.put(x, x*70)
711
712 d1.sync()
713 d2.sync()
714 d3.sync()
715 d1.close()
716 d2.close()
717 d3.close()
718
719 self.d = d1 = d2 = d3 = None
720
721 self.d = d1 = db.DB(self.env)
722 d1.open(self.filename, self.dbname, flags = self.dbopenflags)
723 d2 = db.DB(self.env)
724 d2.open(self.filename, "second", flags = self.dbopenflags)
725 d3 = db.DB(self.env)
726 d3.open(self.filename, "third", flags = self.dbopenflags)
727
728 c1 = d1.cursor()
729 c2 = d2.cursor()
730 c3 = d3.cursor()
731
732 count = 0
733 rec = c1.first()
734 while rec is not None:
735 count = count + 1
736 if verbose and (count % 50) == 0:
737 print rec
738 rec = c1.next()
739 assert count == 1000
740
741 count = 0
742 rec = c2.first()
743 while rec is not None:
744 count = count + 1
745 if verbose:
746 print rec
747 rec = c2.next()
748 assert count == 9
749
750 count = 0
751 rec = c3.first()
752 while rec is not None:
753 count = count + 1
754 if verbose:
755 print rec
756 rec = c3.next()
757 assert count == 52
758
759
760 c1.close()
761 c2.close()
762 c3.close()
763
764 d2.close()
765 d3.close()
766
767
768
769# Strange things happen if you try to use Multiple DBs per file without a
770# DBEnv with MPOOL and LOCKing...
771
772class BTreeMultiDBTestCase(BasicMultiDBTestCase):
773 dbtype = db.DB_BTREE
774 dbopenflags = db.DB_THREAD
775 useEnv = 1
776 envflags = db.DB_THREAD | db.DB_INIT_MPOOL | db.DB_INIT_LOCK
777
778class HashMultiDBTestCase(BasicMultiDBTestCase):
779 dbtype = db.DB_HASH
780 dbopenflags = db.DB_THREAD
781 useEnv = 1
782 envflags = db.DB_THREAD | db.DB_INIT_MPOOL | db.DB_INIT_LOCK
783
784
785#----------------------------------------------------------------------
786#----------------------------------------------------------------------
787
788def suite():
789 theSuite = unittest.TestSuite()
790
791 theSuite.addTest(unittest.makeSuite(VersionTestCase))
792 theSuite.addTest(unittest.makeSuite(BasicBTreeTestCase))
793 theSuite.addTest(unittest.makeSuite(BasicHashTestCase))
794 theSuite.addTest(unittest.makeSuite(BasicBTreeWithThreadFlagTestCase))
795 theSuite.addTest(unittest.makeSuite(BasicHashWithThreadFlagTestCase))
796 theSuite.addTest(unittest.makeSuite(BasicBTreeWithEnvTestCase))
797 theSuite.addTest(unittest.makeSuite(BasicHashWithEnvTestCase))
798 theSuite.addTest(unittest.makeSuite(BTreeTransactionTestCase))
799 theSuite.addTest(unittest.makeSuite(HashTransactionTestCase))
800 theSuite.addTest(unittest.makeSuite(BTreeRecnoTestCase))
801 theSuite.addTest(unittest.makeSuite(BTreeRecnoWithThreadFlagTestCase))
802 theSuite.addTest(unittest.makeSuite(BTreeDUPTestCase))
803 theSuite.addTest(unittest.makeSuite(HashDUPTestCase))
804 theSuite.addTest(unittest.makeSuite(BTreeDUPWithThreadTestCase))
805 theSuite.addTest(unittest.makeSuite(HashDUPWithThreadTestCase))
806 theSuite.addTest(unittest.makeSuite(BTreeMultiDBTestCase))
807 theSuite.addTest(unittest.makeSuite(HashMultiDBTestCase))
808
809 return theSuite
810
811
812if __name__ == '__main__':
813 unittest.main( defaultTest='suite' )
814