Code

c5cdb9a1b8bb47b7ed05e680b6af239ab82172e3
[roundup.git] / test / test_db.py
1 #
2 # Copyright (c) 2001 Bizar Software Pty Ltd (http://www.bizarsoftware.com.au/)
3 # This module is free software, and you may redistribute it and/or modify
4 # under the same terms as Python, so long as this copyright message and
5 # disclaimer are retained in their original form.
6 #
7 # IN NO EVENT SHALL BIZAR SOFTWARE PTY LTD BE LIABLE TO ANY PARTY FOR
8 # DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING
9 # OUT OF THE USE OF THIS CODE, EVEN IF THE AUTHOR HAS BEEN ADVISED OF THE
10 # POSSIBILITY OF SUCH DAMAGE.
11 #
12 # BIZAR SOFTWARE PTY LTD SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING,
13 # BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
14 # FOR A PARTICULAR PURPOSE.  THE CODE PROVIDED HEREUNDER IS ON AN "AS IS"
15 # BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE,
16 # SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
17
18 # $Id: test_db.py,v 1.40 2002-08-23 04:48:36 richard Exp $ 
20 import unittest, os, shutil, time
22 from roundup.hyperdb import String, Password, Link, Multilink, Date, \
23     Interval, DatabaseError, Boolean, Number
24 from roundup import date, password
25 from roundup.indexer import Indexer
27 def setupSchema(db, create, module):
28     status = module.Class(db, "status", name=String())
29     status.setkey("name")
30     user = module.Class(db, "user", username=String(), password=Password(),
31         assignable=Boolean(), age=Number(), roles=String())
32     user.setkey("username")
33     file = module.FileClass(db, "file", name=String(), type=String(),
34         comment=String(indexme="yes"))
35     issue = module.IssueClass(db, "issue", title=String(indexme="yes"),
36         status=Link("status"), nosy=Multilink("user"), deadline=Date(),
37         foo=Interval(), files=Multilink("file"), assignedto=Link('user'))
38     session = module.Class(db, 'session', title=String())
39     session.disableJournalling()
40     db.post_init()
41     if create:
42         status.create(name="unread")
43         status.create(name="in-progress")
44         status.create(name="testing")
45         status.create(name="resolved")
46     db.commit()
48 class MyTestCase(unittest.TestCase):
49     def tearDown(self):
50         if os.path.exists('_test_dir'):
51             shutil.rmtree('_test_dir')
53 class config:
54     DATABASE='_test_dir'
55     MAILHOST = 'localhost'
56     MAIL_DOMAIN = 'fill.me.in.'
57     INSTANCE_NAME = 'Roundup issue tracker'
58     ISSUE_TRACKER_EMAIL = 'issue_tracker@%s'%MAIL_DOMAIN
59     ISSUE_TRACKER_WEB = 'http://some.useful.url/'
60     ADMIN_EMAIL = 'roundup-admin@%s'%MAIL_DOMAIN
61     FILTER_POSITION = 'bottom'      # one of 'top', 'bottom', 'top and bottom'
62     ANONYMOUS_ACCESS = 'deny'       # either 'deny' or 'allow'
63     ANONYMOUS_REGISTER = 'deny'     # either 'deny' or 'allow'
64     MESSAGES_TO_AUTHOR = 'no'       # either 'yes' or 'no'
65     EMAIL_SIGNATURE_POSITION = 'bottom'
67 class anydbmDBTestCase(MyTestCase):
68     def setUp(self):
69         from roundup.backends import anydbm
70         # remove previous test, ignore errors
71         if os.path.exists(config.DATABASE):
72             shutil.rmtree(config.DATABASE)
73         os.makedirs(config.DATABASE + '/files')
74         self.db = anydbm.Database(config, 'test')
75         setupSchema(self.db, 1, anydbm)
76         self.db2 = anydbm.Database(config, 'test')
77         setupSchema(self.db2, 0, anydbm)
79     def testStringChange(self):
80         self.db.issue.create(title="spam", status='1')
81         self.assertEqual(self.db.issue.get('1', 'title'), 'spam')
82         self.db.issue.set('1', title='eggs')
83         self.assertEqual(self.db.issue.get('1', 'title'), 'eggs')
84         self.db.commit()
85         self.assertEqual(self.db.issue.get('1', 'title'), 'eggs')
86         self.db.issue.create(title="spam", status='1')
87         self.db.commit()
88         self.assertEqual(self.db.issue.get('2', 'title'), 'spam')
89         self.db.issue.set('2', title='ham')
90         self.assertEqual(self.db.issue.get('2', 'title'), 'ham')
91         self.db.commit()
92         self.assertEqual(self.db.issue.get('2', 'title'), 'ham')
93         self.db.issue.set('1', title=None)
94         self.assertEqual(self.db.issue.get('1', "title"), None)
96     def testLinkChange(self):
97         self.db.issue.create(title="spam", status='1')
98         self.assertEqual(self.db.issue.get('1', "status"), '1')
99         self.db.issue.set('1', status='2')
100         self.assertEqual(self.db.issue.get('1', "status"), '2')
101         self.db.issue.set('1', status=None)
102         self.assertEqual(self.db.issue.get('1', "status"), None)
104     def testDateChange(self):
105         self.db.issue.create(title="spam", status='1')
106         a = self.db.issue.get('1', "deadline")
107         self.db.issue.set('1', deadline=date.Date())
108         b = self.db.issue.get('1', "deadline")
109         self.db.commit()
110         self.assertNotEqual(a, b)
111         self.assertNotEqual(b, date.Date('1970-1-1 00:00:00'))
112         self.db.issue.set('1', deadline=date.Date())
113         self.db.issue.set('1', deadline=None)
114         self.assertEqual(self.db.issue.get('1', "deadline"), None)
116     def testIntervalChange(self):
117         self.db.issue.create(title="spam", status='1')
118         a = self.db.issue.get('1', "foo")
119         self.db.issue.set('1', foo=date.Interval('-1d'))
120         self.assertNotEqual(self.db.issue.get('1', "foo"), a)
121         self.db.issue.set('1', foo=None)
122         self.assertEqual(self.db.issue.get('1', "foo"), None)
124     def testBooleanChange(self):
125         userid = self.db.user.create(username='foo', assignable=1)
126         self.db.user.create(username='foo2', assignable=0)
127         a = self.db.user.get(userid, 'assignable')
128         self.db.user.set(userid, assignable=0)
129         self.assertNotEqual(self.db.user.get(userid, 'assignable'), a)
130         self.db.user.set(userid, assignable=0)
131         self.db.user.set(userid, assignable=1)
132         self.db.user.set('1', assignable=None)
133         self.assertEqual(self.db.user.get('1', "assignable"), None)
135     def testNumberChange(self):
136         self.db.user.create(username='foo', age='1')
137         a = self.db.user.get('1', 'age')
138         self.db.user.set('1', age='3')
139         self.assertNotEqual(self.db.user.get('1', 'age'), a)
140         self.db.user.set('1', age='1.0')
141         self.db.user.set('1', age=None)
142         self.assertEqual(self.db.user.get('1', "age"), None)
144     def testNewProperty(self):
145         self.db.issue.create(title="spam", status='1')
146         self.db.issue.addprop(fixer=Link("user"))
147         # force any post-init stuff to happen
148         self.db.post_init()
149         props = self.db.issue.getprops()
150         keys = props.keys()
151         keys.sort()
152         self.assertEqual(keys, ['activity', 'assignedto', 'creation',
153             'creator', 'deadline', 'files', 'fixer', 'foo', 'id', 'messages',
154             'nosy', 'status', 'superseder', 'title'])
155         self.assertEqual(self.db.issue.get('1', "fixer"), None)
157     def testRetire(self):
158         self.db.issue.create(title="spam", status='1')
159         b = self.db.status.get('1', 'name')
160         a = self.db.status.list()
161         self.db.status.retire('1')
162         # make sure the list is different 
163         self.assertNotEqual(a, self.db.status.list())
164         # can still access the node if necessary
165         self.assertEqual(self.db.status.get('1', 'name'), b)
166         self.db.commit()
167         self.assertEqual(self.db.status.get('1', 'name'), b)
168         self.assertNotEqual(a, self.db.status.list())
170     def testSerialisation(self):
171         self.db.issue.create(title="spam", status='1',
172             deadline=date.Date(), foo=date.Interval('-1d'))
173         self.db.commit()
174         assert isinstance(self.db.issue.get('1', 'deadline'), date.Date)
175         assert isinstance(self.db.issue.get('1', 'foo'), date.Interval)
176         self.db.user.create(username="fozzy",
177             password=password.Password('t. bear'))
178         self.db.commit()
179         assert isinstance(self.db.user.get('1', 'password'), password.Password)
181     def testTransactions(self):
182         # remember the number of items we started
183         num_issues = len(self.db.issue.list())
184         num_files = self.db.numfiles()
185         self.db.issue.create(title="don't commit me!", status='1')
186         self.assertNotEqual(num_issues, len(self.db.issue.list()))
187         self.db.rollback()
188         self.assertEqual(num_issues, len(self.db.issue.list()))
189         self.db.issue.create(title="please commit me!", status='1')
190         self.assertNotEqual(num_issues, len(self.db.issue.list()))
191         self.db.commit()
192         self.assertNotEqual(num_issues, len(self.db.issue.list()))
193         self.db.rollback()
194         self.assertNotEqual(num_issues, len(self.db.issue.list()))
195         self.db.file.create(name="test", type="text/plain", content="hi")
196         self.db.rollback()
197         self.assertEqual(num_files, self.db.numfiles())
198         for i in range(10):
199             self.db.file.create(name="test", type="text/plain", 
200                     content="hi %d"%(i))
201             self.db.commit()
202         num_files2 = self.db.numfiles()
203         self.assertNotEqual(num_files, num_files2)
204         self.db.file.create(name="test", type="text/plain", content="hi")
205         self.db.rollback()
206         self.assertNotEqual(num_files, self.db.numfiles())
207         self.assertEqual(num_files2, self.db.numfiles())
209     def testDestroyNoJournalling(self):
210         self.innerTestDestroy(klass=self.db.session)
212     def testDestroyJournalling(self):
213         self.innerTestDestroy(klass=self.db.issue)
215     def innerTestDestroy(self, klass):
216         newid = klass.create(title='Mr Friendly')
217         n = len(klass.list())
218         self.assertEqual(klass.get(newid, 'title'), 'Mr Friendly')
219         klass.destroy(newid)
220         self.assertRaises(IndexError, klass.get, newid, 'title')
221         self.assertNotEqual(len(klass.list()), n)
222         if klass.do_journal:
223             self.assertRaises(IndexError, klass.history, newid)
225         # now with a commit
226         newid = klass.create(title='Mr Friendly')
227         n = len(klass.list())
228         self.assertEqual(klass.get(newid, 'title'), 'Mr Friendly')
229         self.db.commit()
230         klass.destroy(newid)
231         self.assertRaises(IndexError, klass.get, newid, 'title')
232         self.db.commit()
233         self.assertRaises(IndexError, klass.get, newid, 'title')
234         self.assertNotEqual(len(klass.list()), n)
235         if klass.do_journal:
236             self.assertRaises(IndexError, klass.history, newid)
238         # now with a rollback
239         newid = klass.create(title='Mr Friendly')
240         n = len(klass.list())
241         self.assertEqual(klass.get(newid, 'title'), 'Mr Friendly')
242         self.db.commit()
243         klass.destroy(newid)
244         self.assertNotEqual(len(klass.list()), n)
245         self.assertRaises(IndexError, klass.get, newid, 'title')
246         self.db.rollback()
247         self.assertEqual(klass.get(newid, 'title'), 'Mr Friendly')
248         self.assertEqual(len(klass.list()), n)
249         if klass.do_journal:
250             self.assertNotEqual(klass.history(newid), [])
252     def testExceptions(self):
253         # this tests the exceptions that should be raised
254         ar = self.assertRaises
256         #
257         # class create
258         #
259         # string property
260         ar(TypeError, self.db.status.create, name=1)
261         # invalid property name
262         ar(KeyError, self.db.status.create, foo='foo')
263         # key name clash
264         ar(ValueError, self.db.status.create, name='unread')
265         # invalid link index
266         ar(IndexError, self.db.issue.create, title='foo', status='bar')
267         # invalid link value
268         ar(ValueError, self.db.issue.create, title='foo', status=1)
269         # invalid multilink type
270         ar(TypeError, self.db.issue.create, title='foo', status='1',
271             nosy='hello')
272         # invalid multilink index type
273         ar(ValueError, self.db.issue.create, title='foo', status='1',
274             nosy=[1])
275         # invalid multilink index
276         ar(IndexError, self.db.issue.create, title='foo', status='1',
277             nosy=['10'])
279         #
280         # key property
281         # 
282         # key must be a String
283         ar(TypeError, self.db.user.setkey, 'password')
284         # key must exist
285         ar(KeyError, self.db.user.setkey, 'fubar')
287         #
288         # class get
289         #
290         # invalid node id
291         ar(IndexError, self.db.issue.get, '1', 'title')
292         # invalid property name
293         ar(KeyError, self.db.status.get, '2', 'foo')
295         #
296         # class set
297         #
298         # invalid node id
299         ar(IndexError, self.db.issue.set, '1', title='foo')
300         # invalid property name
301         ar(KeyError, self.db.status.set, '1', foo='foo')
302         # string property
303         ar(TypeError, self.db.status.set, '1', name=1)
304         # key name clash
305         ar(ValueError, self.db.status.set, '2', name='unread')
306         # set up a valid issue for me to work on
307         self.db.issue.create(title="spam", status='1')
308         # invalid link index
309         ar(IndexError, self.db.issue.set, '6', title='foo', status='bar')
310         # invalid link value
311         ar(ValueError, self.db.issue.set, '6', title='foo', status=1)
312         # invalid multilink type
313         ar(TypeError, self.db.issue.set, '6', title='foo', status='1',
314             nosy='hello')
315         # invalid multilink index type
316         ar(ValueError, self.db.issue.set, '6', title='foo', status='1',
317             nosy=[1])
318         # invalid multilink index
319         ar(IndexError, self.db.issue.set, '6', title='foo', status='1',
320             nosy=['10'])
321         # invalid number value
322         ar(TypeError, self.db.user.create, username='foo', age='a')
323         # invalid boolean value
324         ar(TypeError, self.db.user.create, username='foo', assignable='true')
325         self.db.user.create(username='foo')
326         # invalid number value
327         ar(TypeError, self.db.user.set, '3', username='foo', age='a')
328         # invalid boolean value
329         ar(TypeError, self.db.user.set, '3', username='foo', assignable='true')
331     def testJournals(self):
332         self.db.user.create(username="mary")
333         self.db.user.create(username="pete")
334         self.db.issue.create(title="spam", status='1')
335         self.db.commit()
337         # journal entry for issue create
338         journal = self.db.getjournal('issue', '1')
339         self.assertEqual(1, len(journal))
340         (nodeid, date_stamp, journaltag, action, params) = journal[0]
341         self.assertEqual(nodeid, '1')
342         self.assertEqual(journaltag, 'test')
343         self.assertEqual(action, 'create')
344         keys = params.keys()
345         keys.sort()
346         self.assertEqual(keys, ['assignedto', 'deadline', 'files',
347             'foo', 'messages', 'nosy', 'status', 'superseder', 'title'])
348         self.assertEqual(None,params['deadline'])
349         self.assertEqual(None,params['foo'])
350         self.assertEqual([],params['nosy'])
351         self.assertEqual('1',params['status'])
352         self.assertEqual('spam',params['title'])
354         # journal entry for link
355         journal = self.db.getjournal('user', '1')
356         self.assertEqual(1, len(journal))
357         self.db.issue.set('1', assignedto='1')
358         self.db.commit()
359         journal = self.db.getjournal('user', '1')
360         self.assertEqual(2, len(journal))
361         (nodeid, date_stamp, journaltag, action, params) = journal[1]
362         self.assertEqual('1', nodeid)
363         self.assertEqual('test', journaltag)
364         self.assertEqual('link', action)
365         self.assertEqual(('issue', '1', 'assignedto'), params)
367         # journal entry for unlink
368         self.db.issue.set('1', assignedto='2')
369         self.db.commit()
370         journal = self.db.getjournal('user', '1')
371         self.assertEqual(3, len(journal))
372         (nodeid, date_stamp, journaltag, action, params) = journal[2]
373         self.assertEqual('1', nodeid)
374         self.assertEqual('test', journaltag)
375         self.assertEqual('unlink', action)
376         self.assertEqual(('issue', '1', 'assignedto'), params)
378         # test disabling journalling
379         # ... get the last entry
380         time.sleep(1)
381         entry = self.db.getjournal('issue', '1')[-1]
382         (x, date_stamp, x, x, x) = entry
383         self.db.issue.disableJournalling()
384         self.db.issue.set('1', title='hello world')
385         self.db.commit()
386         entry = self.db.getjournal('issue', '1')[-1]
387         (x, date_stamp2, x, x, x) = entry
388         # see if the change was journalled when it shouldn't have been
389         self.assertEqual(date_stamp, date_stamp2)
390         self.db.issue.enableJournalling()
391         self.db.issue.set('1', title='hello world 2')
392         self.db.commit()
393         entry = self.db.getjournal('issue', '1')[-1]
394         (x, date_stamp2, x, x, x) = entry
395         # see if the change was journalled
396         self.assertNotEqual(date_stamp, date_stamp2)
398     def testPack(self):
399         self.db.issue.create(title="spam", status='1')
400         self.db.commit()
401         self.db.issue.set('1', status='2')
402         self.db.commit()
404         # sleep for at least a second, then get a date to pack at
405         time.sleep(1)
406         pack_before = date.Date('.')
408         # one more entry
409         self.db.issue.set('1', status='3')
410         self.db.commit()
412         # pack
413         self.db.pack(pack_before)
414         journal = self.db.getjournal('issue', '1')
416         # we should have one entry now
417         self.assertEqual(1, len(journal))
419     def testIDGeneration(self):
420         id1 = self.db.issue.create(title="spam", status='1')
421         id2 = self.db2.issue.create(title="eggs", status='2')
422         self.assertNotEqual(id1, id2)
424     def testSearching(self):
425         self.db.file.create(content='hello', type="text/plain")
426         self.db.file.create(content='world', type="text/frozz",
427             comment='blah blah')
428         self.db.issue.create(files=['1', '2'], title="flebble plop")
429         self.db.issue.create(title="flebble frooz")
430         self.db.commit()
431         self.assertEquals(self.db.indexer.search(['hello'], self.db.issue),
432             {'1': {'files': ['1']}})
433         self.assertEquals(self.db.indexer.search(['world'], self.db.issue), {})
434         self.assertEquals(self.db.indexer.search(['frooz'], self.db.issue),
435             {'2': {}})
436         self.assertEquals(self.db.indexer.search(['flebble'], self.db.issue),
437             {'2': {}, '1': {}})
439     def testReindexing(self):
440         self.db.issue.create(title="frooz")
441         self.db.commit()
442         self.assertEquals(self.db.indexer.search(['frooz'], self.db.issue),
443             {'1': {}})
444         self.db.issue.set('1', title="dooble")
445         self.db.commit()
446         self.assertEquals(self.db.indexer.search(['dooble'], self.db.issue),
447             {'1': {}})
448         self.assertEquals(self.db.indexer.search(['frooz'], self.db.issue), {})
450     def testForcedReindexing(self):
451         self.db.issue.create(title="flebble frooz")
452         self.db.commit()
453         self.assertEquals(self.db.indexer.search(['flebble'], self.db.issue),
454             {'1': {}})
455         self.db.indexer.quiet = 1
456         self.db.indexer.force_reindex()
457         self.db.post_init()
458         self.db.indexer.quiet = 9
459         self.assertEquals(self.db.indexer.search(['flebble'], self.db.issue),
460             {'1': {}})
462 class anydbmReadOnlyDBTestCase(MyTestCase):
463     def setUp(self):
464         from roundup.backends import anydbm
465         # remove previous test, ignore errors
466         if os.path.exists(config.DATABASE):
467             shutil.rmtree(config.DATABASE)
468         os.makedirs(config.DATABASE + '/files')
469         db = anydbm.Database(config, 'test')
470         setupSchema(db, 1, anydbm)
471         self.db = anydbm.Database(config)
472         setupSchema(self.db, 0, anydbm)
473         self.db2 = anydbm.Database(config, 'test')
474         setupSchema(self.db2, 0, anydbm)
476     def testExceptions(self):
477         # this tests the exceptions that should be raised
478         ar = self.assertRaises
480         # this tests the exceptions that should be raised
481         ar(DatabaseError, self.db.status.create, name="foo")
482         ar(DatabaseError, self.db.status.set, '1', name="foo")
483         ar(DatabaseError, self.db.status.retire, '1')
486 class bsddbDBTestCase(anydbmDBTestCase):
487     def setUp(self):
488         from roundup.backends import bsddb
489         # remove previous test, ignore errors
490         if os.path.exists(config.DATABASE):
491             shutil.rmtree(config.DATABASE)
492         os.makedirs(config.DATABASE + '/files')
493         self.db = bsddb.Database(config, 'test')
494         setupSchema(self.db, 1, bsddb)
495         self.db2 = bsddb.Database(config, 'test')
496         setupSchema(self.db2, 0, bsddb)
498 class bsddbReadOnlyDBTestCase(anydbmReadOnlyDBTestCase):
499     def setUp(self):
500         from roundup.backends import bsddb
501         # remove previous test, ignore errors
502         if os.path.exists(config.DATABASE):
503             shutil.rmtree(config.DATABASE)
504         os.makedirs(config.DATABASE + '/files')
505         db = bsddb.Database(config, 'test')
506         setupSchema(db, 1, bsddb)
507         self.db = bsddb.Database(config)
508         setupSchema(self.db, 0, bsddb)
509         self.db2 = bsddb.Database(config, 'test')
510         setupSchema(self.db2, 0, bsddb)
513 class bsddb3DBTestCase(anydbmDBTestCase):
514     def setUp(self):
515         from roundup.backends import bsddb3
516         # remove previous test, ignore errors
517         if os.path.exists(config.DATABASE):
518             shutil.rmtree(config.DATABASE)
519         os.makedirs(config.DATABASE + '/files')
520         self.db = bsddb3.Database(config, 'test')
521         setupSchema(self.db, 1, bsddb3)
522         self.db2 = bsddb3.Database(config, 'test')
523         setupSchema(self.db2, 0, bsddb3)
525 class bsddb3ReadOnlyDBTestCase(anydbmReadOnlyDBTestCase):
526     def setUp(self):
527         from roundup.backends import bsddb3
528         # remove previous test, ignore errors
529         if os.path.exists(config.DATABASE):
530             shutil.rmtree(config.DATABASE)
531         os.makedirs(config.DATABASE + '/files')
532         db = bsddb3.Database(config, 'test')
533         setupSchema(db, 1, bsddb3)
534         self.db = bsddb3.Database(config)
535         setupSchema(self.db, 0, bsddb3)
536         self.db2 = bsddb3.Database(config, 'test')
537         setupSchema(self.db2, 0, bsddb3)
540 class gadflyDBTestCase(anydbmDBTestCase):
541     ''' Gadfly doesn't support multiple connections to the one local
542         database
543     '''
544     def setUp(self):
545         from roundup.backends import gadfly
546         # remove previous test, ignore errors
547         if os.path.exists(config.DATABASE):
548             shutil.rmtree(config.DATABASE)
549         config.GADFLY_DATABASE = ('test', config.DATABASE)
550         os.makedirs(config.DATABASE + '/files')
551         self.db = gadfly.Database(config, 'test')
552         setupSchema(self.db, 1, gadfly)
554     def testIDGeneration(self):
555         id1 = self.db.issue.create(title="spam", status='1')
556         id2 = self.db.issue.create(title="eggs", status='2')
557         self.assertNotEqual(id1, id2)
559     def testNewProperty(self):
560         # gadfly doesn't have an ALTER TABLE command :(
561         pass
563 class gadflyReadOnlyDBTestCase(anydbmReadOnlyDBTestCase):
564     def setUp(self):
565         from roundup.backends import gadfly
566         # remove previous test, ignore errors
567         if os.path.exists(config.DATABASE):
568             shutil.rmtree(config.DATABASE)
569         config.GADFLY_DATABASE = ('test', config.DATABASE)
570         os.makedirs(config.DATABASE + '/files')
571         db = gadfly.Database(config, 'test')
572         setupSchema(db, 1, gadfly)
573         self.db = gadfly.Database(config)
574         setupSchema(self.db, 0, gadfly)
577 class metakitDBTestCase(anydbmDBTestCase):
578     def setUp(self):
579         from roundup.backends import metakit
580         import weakref
581         metakit._instances = weakref.WeakValueDictionary()
582         # remove previous test, ignore errors
583         if os.path.exists(config.DATABASE):
584             shutil.rmtree(config.DATABASE)
585         os.makedirs(config.DATABASE + '/files')
586         self.db = metakit.Database(config, 'test')
587         setupSchema(self.db, 1, metakit)
588         self.db2 = metakit.Database(config, 'test')
589         setupSchema(self.db2, 0, metakit)
591     def testTransactions(self):
592         # remember the number of items we started
593         num_issues = len(self.db.issue.list())
594         self.db.issue.create(title="don't commit me!", status='1')
595         self.assertNotEqual(num_issues, len(self.db.issue.list()))
596         self.db.rollback()
597         self.assertEqual(num_issues, len(self.db.issue.list()))
598         self.db.issue.create(title="please commit me!", status='1')
599         self.assertNotEqual(num_issues, len(self.db.issue.list()))
600         self.db.commit()
601         self.assertNotEqual(num_issues, len(self.db.issue.list()))
602         self.db.rollback()
603         self.assertNotEqual(num_issues, len(self.db.issue.list()))
604         self.db.file.create(name="test", type="text/plain", content="hi")
605         self.db.rollback()
606         for i in range(10):
607             self.db.file.create(name="test", type="text/plain", 
608                     content="hi %d"%(i))
609             self.db.commit()
610         # TODO: would be good to be able to ensure the file is not on disk after
611         # a rollback...
612         self.assertNotEqual(num_files, num_files2)
613         self.db.file.create(name="test", type="text/plain", content="hi")
614         self.db.rollback()
616 class metakitReadOnlyDBTestCase(anydbmReadOnlyDBTestCase):
617     def setUp(self):
618         from roundup.backends import metakit
619         import weakref
620         metakit._instances = weakref.WeakValueDictionary()
621         # remove previous test, ignore errors
622         if os.path.exists(config.DATABASE):
623             shutil.rmtree(config.DATABASE)
624         os.makedirs(config.DATABASE + '/files')
625         db = metakit.Database(config, 'test')
626         setupSchema(db, 1, metakit)
627         self.db = metakit.Database(config)
628         setupSchema(self.db, 0, metakit)
629         self.db2 = metakit.Database(config, 'test')
630         setupSchema(self.db2, 0, metakit)
632 def suite():
633     l = [
634          unittest.makeSuite(anydbmDBTestCase, 'test'),
635          unittest.makeSuite(anydbmReadOnlyDBTestCase, 'test')
636     ]
637     #return unittest.TestSuite(l)
639     try:
640         import bsddb
641         l.append(unittest.makeSuite(bsddbDBTestCase, 'test'))
642         l.append(unittest.makeSuite(bsddbReadOnlyDBTestCase, 'test'))
643     except:
644         print 'bsddb module not found, skipping bsddb DBTestCase'
646     try:
647         import bsddb3
648         l.append(unittest.makeSuite(bsddb3DBTestCase, 'test'))
649         l.append(unittest.makeSuite(bsddb3ReadOnlyDBTestCase, 'test'))
650     except:
651         print 'bsddb3 module not found, skipping bsddb3 DBTestCase'
653     try:
654         import gadfly
655         l.append(unittest.makeSuite(gadflyDBTestCase, 'test'))
656         l.append(unittest.makeSuite(gadflyReadOnlyDBTestCase, 'test'))
657     except:
658         print 'gadfly module not found, skipping gadfly DBTestCase'
660     try:
661         import metakit
662         l.append(unittest.makeSuite(metakitDBTestCase, 'test'))
663         l.append(unittest.makeSuite(metakitReadOnlyDBTestCase, 'test'))
664     except:
665         print 'metakit module not found, skipping metakit DBTestCase'
667     return unittest.TestSuite(l)
670 # $Log: not supported by cvs2svn $
671 # Revision 1.39  2002/07/31 23:57:37  richard
672 #  . web forms may now unset Link values (like assignedto)
674 # Revision 1.38  2002/07/26 08:27:00  richard
675 # Very close now. The cgi and mailgw now use the new security API. The two
676 # templates have been migrated to that setup. Lots of unit tests. Still some
677 # issue in the web form for editing Roles assigned to users.
679 # Revision 1.37  2002/07/25 07:14:06  richard
680 # Bugger it. Here's the current shape of the new security implementation.
681 # Still to do:
682 #  . call the security funcs from cgi and mailgw
683 #  . change shipped templates to include correct initialisation and remove
684 #    the old config vars
685 # ... that seems like a lot. The bulk of the work has been done though. Honest :)
687 # Revision 1.36  2002/07/19 03:36:34  richard
688 # Implemented the destroy() method needed by the session database (and possibly
689 # others). At the same time, I removed the leading underscores from the hyperdb
690 # methods that Really Didn't Need Them.
691 # The journal also raises IndexError now for all situations where there is a
692 # request for the journal of a node that doesn't have one. It used to return
693 # [] in _some_ situations, but not all. This _may_ break code, but the tests
694 # pass...
696 # Revision 1.35  2002/07/18 23:07:08  richard
697 # Unit tests and a few fixes.
699 # Revision 1.34  2002/07/18 11:52:00  richard
700 # oops
702 # Revision 1.33  2002/07/18 11:50:58  richard
703 # added tests for number type too
705 # Revision 1.32  2002/07/18 11:41:10  richard
706 # added tests for boolean type, and fixes to anydbm backend
708 # Revision 1.31  2002/07/14 23:17:45  richard
709 # minor change to make testing easier
711 # Revision 1.30  2002/07/14 06:06:34  richard
712 # Did some old TODOs
714 # Revision 1.29  2002/07/14 04:03:15  richard
715 # Implemented a switch to disable journalling for a Class. CGI session
716 # database now uses it.
718 # Revision 1.28  2002/07/14 02:16:29  richard
719 # Fixes for the metakit backend (removed the cut-n-paste IssueClass, removed
720 # a special case for it in testing)
722 # Revision 1.27  2002/07/14 02:05:54  richard
723 # . all storage-specific code (ie. backend) is now implemented by the backends
725 # Revision 1.26  2002/07/11 01:11:03  richard
726 # Added metakit backend to the db tests and fixed the more easily fixable test
727 # failures.
729 # Revision 1.25  2002/07/09 04:19:09  richard
730 # Added reindex command to roundup-admin.
731 # Fixed reindex on first access.
732 # Also fixed reindexing of entries that change.
734 # Revision 1.24  2002/07/09 03:02:53  richard
735 # More indexer work:
736 # - all String properties may now be indexed too. Currently there's a bit of
737 #   "issue" specific code in the actual searching which needs to be
738 #   addressed. In a nutshell:
739 #   + pass 'indexme="yes"' as a String() property initialisation arg, eg:
740 #         file = FileClass(db, "file", name=String(), type=String(),
741 #             comment=String(indexme="yes"))
742 #   + the comment will then be indexed and be searchable, with the results
743 #     related back to the issue that the file is linked to
744 # - as a result of this work, the FileClass has a default MIME type that may
745 #   be overridden in a subclass, or by the use of a "type" property as is
746 #   done in the default templates.
747 # - the regeneration of the indexes (if necessary) is done once the schema is
748 #   set up in the dbinit.
750 # Revision 1.23  2002/06/20 23:51:48  richard
751 # Cleaned up the hyperdb tests
753 # Revision 1.22  2002/05/21 05:52:11  richard
754 # Well whadya know, bsddb3 works again.
755 # The backend is implemented _exactly_ the same as bsddb - so there's no
756 # using its transaction or locking support. It'd be nice to use those some
757 # day I suppose.
759 # Revision 1.21  2002/04/15 23:25:15  richard
760 # . node ids are now generated from a lockable store - no more race conditions
762 # We're using the portalocker code by Jonathan Feinberg that was contributed
763 # to the ASPN Python cookbook. This gives us locking across Unix and Windows.
765 # Revision 1.20  2002/04/03 05:54:31  richard
766 # Fixed serialisation problem by moving the serialisation step out of the
767 # hyperdb.Class (get, set) into the hyperdb.Database.
769 # Also fixed htmltemplate after the showid changes I made yesterday.
771 # Unit tests for all of the above written.
773 # Revision 1.19  2002/02/25 14:34:31  grubert
774 #  . use blobfiles in back_anydbm which is used in back_bsddb.
775 #    change test_db as dirlist does not work for subdirectories.
776 #    ATTENTION: blobfiles now creates subdirectories for files.
778 # Revision 1.18  2002/01/22 07:21:13  richard
779 # . fixed back_bsddb so it passed the journal tests
781 # ... it didn't seem happy using the back_anydbm _open method, which is odd.
782 # Yet another occurrance of whichdb not being able to recognise older bsddb
783 # databases. Yadda yadda. Made the HYPERDBDEBUG stuff more sane in the
784 # process.
786 # Revision 1.17  2002/01/22 05:06:09  rochecompaan
787 # We need to keep the last 'set' entry in the journal to preserve
788 # information on 'activity' for nodes.
790 # Revision 1.16  2002/01/21 16:33:20  rochecompaan
791 # You can now use the roundup-admin tool to pack the database
793 # Revision 1.15  2002/01/19 13:16:04  rochecompaan
794 # Journal entries for link and multilink properties can now be switched on
795 # or off.
797 # Revision 1.14  2002/01/16 07:02:57  richard
798 #  . lots of date/interval related changes:
799 #    - more relaxed date format for input
801 # Revision 1.13  2002/01/14 02:20:15  richard
802 #  . changed all config accesses so they access either the instance or the
803 #    config attriubute on the db. This means that all config is obtained from
804 #    instance_config instead of the mish-mash of classes. This will make
805 #    switching to a ConfigParser setup easier too, I hope.
807 # At a minimum, this makes migration a _little_ easier (a lot easier in the
808 # 0.5.0 switch, I hope!)
810 # Revision 1.12  2001/12/17 03:52:48  richard
811 # Implemented file store rollback. As a bonus, the hyperdb is now capable of
812 # storing more than one file per node - if a property name is supplied,
813 # the file is called designator.property.
814 # I decided not to migrate the existing files stored over to the new naming
815 # scheme - the FileClass just doesn't specify the property name.
817 # Revision 1.11  2001/12/10 23:17:20  richard
818 # Added transaction tests to test_db
820 # Revision 1.10  2001/12/03 21:33:39  richard
821 # Fixes so the tests use commit and not close
823 # Revision 1.9  2001/12/02 05:06:16  richard
824 # . We now use weakrefs in the Classes to keep the database reference, so
825 #   the close() method on the database is no longer needed.
826 #   I bumped the minimum python requirement up to 2.1 accordingly.
827 # . #487480 ] roundup-server
828 # . #487476 ] INSTALL.txt
830 # I also cleaned up the change message / post-edit stuff in the cgi client.
831 # There's now a clearly marked "TODO: append the change note" where I believe
832 # the change note should be added there. The "changes" list will obviously
833 # have to be modified to be a dict of the changes, or somesuch.
835 # More testing needed.
837 # Revision 1.8  2001/10/09 07:25:59  richard
838 # Added the Password property type. See "pydoc roundup.password" for
839 # implementation details. Have updated some of the documentation too.
841 # Revision 1.7  2001/08/29 06:23:59  richard
842 # Disabled the bsddb3 module entirely in the unit testing. See CHANGES for
843 # details.
845 # Revision 1.6  2001/08/07 00:24:43  richard
846 # stupid typo
848 # Revision 1.5  2001/08/07 00:15:51  richard
849 # Added the copyright/license notice to (nearly) all files at request of
850 # Bizar Software.
852 # Revision 1.4  2001/07/30 03:45:56  richard
853 # Added more DB to test_db. Can skip tests where imports fail.
855 # Revision 1.3  2001/07/29 07:01:39  richard
856 # Added vim command to all source so that we don't get no steenkin' tabs :)
858 # Revision 1.2  2001/07/29 04:09:20  richard
859 # Added the fabricated property "id" to all hyperdb classes.
861 # Revision 1.1  2001/07/27 06:55:07  richard
862 # moving tests -> test
864 # Revision 1.7  2001/07/27 06:26:43  richard
865 # oops - wasn't deleting the test dir after the read-only tests
867 # Revision 1.6  2001/07/27 06:23:59  richard
868 # consistency
870 # Revision 1.5  2001/07/27 06:23:09  richard
871 # Added some new hyperdb tests to make sure we raise the right exceptions.
873 # Revision 1.4  2001/07/25 04:34:31  richard
874 # Added id and log to tests files...
877 # vim: set filetype=python ts=4 sw=4 et si