Code

88330eca4b415cda16cdaf411dfbd921688d5ce7
[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.42 2002-08-23 05:33:32 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 testMultilinkChange(self):
105         u1 = self.db.user.create(username='foo')
106         u2 = self.db.user.create(username='bar')
107         self.db.issue.create(title="spam", nosy=[u1])
108         self.assertEqual(self.db.issue.get('1', "nosy"), [u1])
109         self.db.issue.set('1', nosy=[])
110         self.assertEqual(self.db.issue.get('1', "nosy"), [])
111         self.db.issue.set('1', nosy=[u1,u2])
112         self.assertEqual(self.db.issue.get('1', "nosy"), [u1,u2])
114     def testDateChange(self):
115         self.db.issue.create(title="spam", status='1')
116         a = self.db.issue.get('1', "deadline")
117         self.db.issue.set('1', deadline=date.Date())
118         b = self.db.issue.get('1', "deadline")
119         self.db.commit()
120         self.assertNotEqual(a, b)
121         self.assertNotEqual(b, date.Date('1970-1-1 00:00:00'))
122         self.db.issue.set('1', deadline=date.Date())
123         self.db.issue.set('1', deadline=None)
124         self.assertEqual(self.db.issue.get('1', "deadline"), None)
126     def testIntervalChange(self):
127         self.db.issue.create(title="spam", status='1')
128         a = self.db.issue.get('1', "foo")
129         self.db.issue.set('1', foo=date.Interval('-1d'))
130         self.assertNotEqual(self.db.issue.get('1', "foo"), a)
131         self.db.issue.set('1', foo=None)
132         self.assertEqual(self.db.issue.get('1', "foo"), None)
134     def testBooleanChange(self):
135         userid = self.db.user.create(username='foo', assignable=1)
136         self.db.user.create(username='foo2', assignable=0)
137         a = self.db.user.get(userid, 'assignable')
138         self.db.user.set(userid, assignable=0)
139         self.assertNotEqual(self.db.user.get(userid, 'assignable'), a)
140         self.db.user.set(userid, assignable=0)
141         self.db.user.set(userid, assignable=1)
142         self.db.user.set('1', assignable=None)
143         self.assertEqual(self.db.user.get('1', "assignable"), None)
145     def testNumberChange(self):
146         self.db.user.create(username='foo', age='1')
147         a = self.db.user.get('1', 'age')
148         self.db.user.set('1', age='3')
149         self.assertNotEqual(self.db.user.get('1', 'age'), a)
150         self.db.user.set('1', age='1.0')
151         self.db.user.set('1', age=None)
152         self.assertEqual(self.db.user.get('1', "age"), None)
154     def testNewProperty(self):
155         self.db.issue.create(title="spam", status='1')
156         self.db.issue.addprop(fixer=Link("user"))
157         # force any post-init stuff to happen
158         self.db.post_init()
159         props = self.db.issue.getprops()
160         keys = props.keys()
161         keys.sort()
162         self.assertEqual(keys, ['activity', 'assignedto', 'creation',
163             'creator', 'deadline', 'files', 'fixer', 'foo', 'id', 'messages',
164             'nosy', 'status', 'superseder', 'title'])
165         self.assertEqual(self.db.issue.get('1', "fixer"), None)
167     def testRetire(self):
168         self.db.issue.create(title="spam", status='1')
169         b = self.db.status.get('1', 'name')
170         a = self.db.status.list()
171         self.db.status.retire('1')
172         # make sure the list is different 
173         self.assertNotEqual(a, self.db.status.list())
174         # can still access the node if necessary
175         self.assertEqual(self.db.status.get('1', 'name'), b)
176         self.db.commit()
177         self.assertEqual(self.db.status.get('1', 'name'), b)
178         self.assertNotEqual(a, self.db.status.list())
180     def testSerialisation(self):
181         self.db.issue.create(title="spam", status='1',
182             deadline=date.Date(), foo=date.Interval('-1d'))
183         self.db.commit()
184         assert isinstance(self.db.issue.get('1', 'deadline'), date.Date)
185         assert isinstance(self.db.issue.get('1', 'foo'), date.Interval)
186         self.db.user.create(username="fozzy",
187             password=password.Password('t. bear'))
188         self.db.commit()
189         assert isinstance(self.db.user.get('1', 'password'), password.Password)
191     def testTransactions(self):
192         # remember the number of items we started
193         num_issues = len(self.db.issue.list())
194         num_files = self.db.numfiles()
195         self.db.issue.create(title="don't commit me!", status='1')
196         self.assertNotEqual(num_issues, len(self.db.issue.list()))
197         self.db.rollback()
198         self.assertEqual(num_issues, len(self.db.issue.list()))
199         self.db.issue.create(title="please commit me!", status='1')
200         self.assertNotEqual(num_issues, len(self.db.issue.list()))
201         self.db.commit()
202         self.assertNotEqual(num_issues, len(self.db.issue.list()))
203         self.db.rollback()
204         self.assertNotEqual(num_issues, len(self.db.issue.list()))
205         self.db.file.create(name="test", type="text/plain", content="hi")
206         self.db.rollback()
207         self.assertEqual(num_files, self.db.numfiles())
208         for i in range(10):
209             self.db.file.create(name="test", type="text/plain", 
210                     content="hi %d"%(i))
211             self.db.commit()
212         num_files2 = self.db.numfiles()
213         self.assertNotEqual(num_files, num_files2)
214         self.db.file.create(name="test", type="text/plain", content="hi")
215         self.db.rollback()
216         self.assertNotEqual(num_files, self.db.numfiles())
217         self.assertEqual(num_files2, self.db.numfiles())
219     def testDestroyNoJournalling(self):
220         self.innerTestDestroy(klass=self.db.session)
222     def testDestroyJournalling(self):
223         self.innerTestDestroy(klass=self.db.issue)
225     def innerTestDestroy(self, klass):
226         newid = klass.create(title='Mr Friendly')
227         n = len(klass.list())
228         self.assertEqual(klass.get(newid, 'title'), 'Mr Friendly')
229         klass.destroy(newid)
230         self.assertRaises(IndexError, klass.get, newid, 'title')
231         self.assertNotEqual(len(klass.list()), n)
232         if klass.do_journal:
233             self.assertRaises(IndexError, klass.history, newid)
235         # now with a commit
236         newid = klass.create(title='Mr Friendly')
237         n = len(klass.list())
238         self.assertEqual(klass.get(newid, 'title'), 'Mr Friendly')
239         self.db.commit()
240         klass.destroy(newid)
241         self.assertRaises(IndexError, klass.get, newid, 'title')
242         self.db.commit()
243         self.assertRaises(IndexError, klass.get, newid, 'title')
244         self.assertNotEqual(len(klass.list()), n)
245         if klass.do_journal:
246             self.assertRaises(IndexError, klass.history, newid)
248         # now with a rollback
249         newid = klass.create(title='Mr Friendly')
250         n = len(klass.list())
251         self.assertEqual(klass.get(newid, 'title'), 'Mr Friendly')
252         self.db.commit()
253         klass.destroy(newid)
254         self.assertNotEqual(len(klass.list()), n)
255         self.assertRaises(IndexError, klass.get, newid, 'title')
256         self.db.rollback()
257         self.assertEqual(klass.get(newid, 'title'), 'Mr Friendly')
258         self.assertEqual(len(klass.list()), n)
259         if klass.do_journal:
260             self.assertNotEqual(klass.history(newid), [])
262     def testExceptions(self):
263         # this tests the exceptions that should be raised
264         ar = self.assertRaises
266         #
267         # class create
268         #
269         # string property
270         ar(TypeError, self.db.status.create, name=1)
271         # invalid property name
272         ar(KeyError, self.db.status.create, foo='foo')
273         # key name clash
274         ar(ValueError, self.db.status.create, name='unread')
275         # invalid link index
276         ar(IndexError, self.db.issue.create, title='foo', status='bar')
277         # invalid link value
278         ar(ValueError, self.db.issue.create, title='foo', status=1)
279         # invalid multilink type
280         ar(TypeError, self.db.issue.create, title='foo', status='1',
281             nosy='hello')
282         # invalid multilink index type
283         ar(ValueError, self.db.issue.create, title='foo', status='1',
284             nosy=[1])
285         # invalid multilink index
286         ar(IndexError, self.db.issue.create, title='foo', status='1',
287             nosy=['10'])
289         #
290         # key property
291         # 
292         # key must be a String
293         ar(TypeError, self.db.user.setkey, 'password')
294         # key must exist
295         ar(KeyError, self.db.user.setkey, 'fubar')
297         #
298         # class get
299         #
300         # invalid node id
301         ar(IndexError, self.db.issue.get, '1', 'title')
302         # invalid property name
303         ar(KeyError, self.db.status.get, '2', 'foo')
305         #
306         # class set
307         #
308         # invalid node id
309         ar(IndexError, self.db.issue.set, '1', title='foo')
310         # invalid property name
311         ar(KeyError, self.db.status.set, '1', foo='foo')
312         # string property
313         ar(TypeError, self.db.status.set, '1', name=1)
314         # key name clash
315         ar(ValueError, self.db.status.set, '2', name='unread')
316         # set up a valid issue for me to work on
317         self.db.issue.create(title="spam", status='1')
318         # invalid link index
319         ar(IndexError, self.db.issue.set, '6', title='foo', status='bar')
320         # invalid link value
321         ar(ValueError, self.db.issue.set, '6', title='foo', status=1)
322         # invalid multilink type
323         ar(TypeError, self.db.issue.set, '6', title='foo', status='1',
324             nosy='hello')
325         # invalid multilink index type
326         ar(ValueError, self.db.issue.set, '6', title='foo', status='1',
327             nosy=[1])
328         # invalid multilink index
329         ar(IndexError, self.db.issue.set, '6', title='foo', status='1',
330             nosy=['10'])
331         # invalid number value
332         ar(TypeError, self.db.user.create, username='foo', age='a')
333         # invalid boolean value
334         ar(TypeError, self.db.user.create, username='foo', assignable='true')
335         self.db.user.create(username='foo')
336         # invalid number value
337         ar(TypeError, self.db.user.set, '3', username='foo', age='a')
338         # invalid boolean value
339         ar(TypeError, self.db.user.set, '3', username='foo', assignable='true')
341     def testJournals(self):
342         self.db.user.create(username="mary")
343         self.db.user.create(username="pete")
344         self.db.issue.create(title="spam", status='1')
345         self.db.commit()
347         # journal entry for issue create
348         journal = self.db.getjournal('issue', '1')
349         self.assertEqual(1, len(journal))
350         (nodeid, date_stamp, journaltag, action, params) = journal[0]
351         self.assertEqual(nodeid, '1')
352         self.assertEqual(journaltag, 'test')
353         self.assertEqual(action, 'create')
354         keys = params.keys()
355         keys.sort()
356         self.assertEqual(keys, ['assignedto', 'deadline', 'files',
357             'foo', 'messages', 'nosy', 'status', 'superseder', 'title'])
358         self.assertEqual(None,params['deadline'])
359         self.assertEqual(None,params['foo'])
360         self.assertEqual([],params['nosy'])
361         self.assertEqual('1',params['status'])
362         self.assertEqual('spam',params['title'])
364         # journal entry for link
365         journal = self.db.getjournal('user', '1')
366         self.assertEqual(1, len(journal))
367         self.db.issue.set('1', assignedto='1')
368         self.db.commit()
369         journal = self.db.getjournal('user', '1')
370         self.assertEqual(2, len(journal))
371         (nodeid, date_stamp, journaltag, action, params) = journal[1]
372         self.assertEqual('1', nodeid)
373         self.assertEqual('test', journaltag)
374         self.assertEqual('link', action)
375         self.assertEqual(('issue', '1', 'assignedto'), params)
377         # journal entry for unlink
378         self.db.issue.set('1', assignedto='2')
379         self.db.commit()
380         journal = self.db.getjournal('user', '1')
381         self.assertEqual(3, len(journal))
382         (nodeid, date_stamp, journaltag, action, params) = journal[2]
383         self.assertEqual('1', nodeid)
384         self.assertEqual('test', journaltag)
385         self.assertEqual('unlink', action)
386         self.assertEqual(('issue', '1', 'assignedto'), params)
388         # test disabling journalling
389         # ... get the last entry
390         time.sleep(1)
391         entry = self.db.getjournal('issue', '1')[-1]
392         (x, date_stamp, x, x, x) = entry
393         self.db.issue.disableJournalling()
394         self.db.issue.set('1', title='hello world')
395         self.db.commit()
396         entry = self.db.getjournal('issue', '1')[-1]
397         (x, date_stamp2, x, x, x) = entry
398         # see if the change was journalled when it shouldn't have been
399         self.assertEqual(date_stamp, date_stamp2)
400         self.db.issue.enableJournalling()
401         self.db.issue.set('1', title='hello world 2')
402         self.db.commit()
403         entry = self.db.getjournal('issue', '1')[-1]
404         (x, date_stamp2, x, x, x) = entry
405         # see if the change was journalled
406         self.assertNotEqual(date_stamp, date_stamp2)
408     def testPack(self):
409         self.db.issue.create(title="spam", status='1')
410         self.db.commit()
411         self.db.issue.set('1', status='2')
412         self.db.commit()
414         # sleep for at least a second, then get a date to pack at
415         time.sleep(1)
416         pack_before = date.Date('.')
418         # one more entry
419         self.db.issue.set('1', status='3')
420         self.db.commit()
422         # pack
423         self.db.pack(pack_before)
424         journal = self.db.getjournal('issue', '1')
426         # we should have the create and last set entries now
427         self.assertEqual(2, len(journal))
429     def testIDGeneration(self):
430         id1 = self.db.issue.create(title="spam", status='1')
431         id2 = self.db2.issue.create(title="eggs", status='2')
432         self.assertNotEqual(id1, id2)
434     def testSearching(self):
435         self.db.file.create(content='hello', type="text/plain")
436         self.db.file.create(content='world', type="text/frozz",
437             comment='blah blah')
438         self.db.issue.create(files=['1', '2'], title="flebble plop")
439         self.db.issue.create(title="flebble frooz")
440         self.db.commit()
441         self.assertEquals(self.db.indexer.search(['hello'], self.db.issue),
442             {'1': {'files': ['1']}})
443         self.assertEquals(self.db.indexer.search(['world'], self.db.issue), {})
444         self.assertEquals(self.db.indexer.search(['frooz'], self.db.issue),
445             {'2': {}})
446         self.assertEquals(self.db.indexer.search(['flebble'], self.db.issue),
447             {'2': {}, '1': {}})
449     def testReindexing(self):
450         self.db.issue.create(title="frooz")
451         self.db.commit()
452         self.assertEquals(self.db.indexer.search(['frooz'], self.db.issue),
453             {'1': {}})
454         self.db.issue.set('1', title="dooble")
455         self.db.commit()
456         self.assertEquals(self.db.indexer.search(['dooble'], self.db.issue),
457             {'1': {}})
458         self.assertEquals(self.db.indexer.search(['frooz'], self.db.issue), {})
460     def testForcedReindexing(self):
461         self.db.issue.create(title="flebble frooz")
462         self.db.commit()
463         self.assertEquals(self.db.indexer.search(['flebble'], self.db.issue),
464             {'1': {}})
465         self.db.indexer.quiet = 1
466         self.db.indexer.force_reindex()
467         self.db.post_init()
468         self.db.indexer.quiet = 9
469         self.assertEquals(self.db.indexer.search(['flebble'], self.db.issue),
470             {'1': {}})
472 class anydbmReadOnlyDBTestCase(MyTestCase):
473     def setUp(self):
474         from roundup.backends import anydbm
475         # remove previous test, ignore errors
476         if os.path.exists(config.DATABASE):
477             shutil.rmtree(config.DATABASE)
478         os.makedirs(config.DATABASE + '/files')
479         db = anydbm.Database(config, 'test')
480         setupSchema(db, 1, anydbm)
481         self.db = anydbm.Database(config)
482         setupSchema(self.db, 0, anydbm)
483         self.db2 = anydbm.Database(config, 'test')
484         setupSchema(self.db2, 0, anydbm)
486     def testExceptions(self):
487         # this tests the exceptions that should be raised
488         ar = self.assertRaises
490         # this tests the exceptions that should be raised
491         ar(DatabaseError, self.db.status.create, name="foo")
492         ar(DatabaseError, self.db.status.set, '1', name="foo")
493         ar(DatabaseError, self.db.status.retire, '1')
496 class bsddbDBTestCase(anydbmDBTestCase):
497     def setUp(self):
498         from roundup.backends import bsddb
499         # remove previous test, ignore errors
500         if os.path.exists(config.DATABASE):
501             shutil.rmtree(config.DATABASE)
502         os.makedirs(config.DATABASE + '/files')
503         self.db = bsddb.Database(config, 'test')
504         setupSchema(self.db, 1, bsddb)
505         self.db2 = bsddb.Database(config, 'test')
506         setupSchema(self.db2, 0, bsddb)
508 class bsddbReadOnlyDBTestCase(anydbmReadOnlyDBTestCase):
509     def setUp(self):
510         from roundup.backends import bsddb
511         # remove previous test, ignore errors
512         if os.path.exists(config.DATABASE):
513             shutil.rmtree(config.DATABASE)
514         os.makedirs(config.DATABASE + '/files')
515         db = bsddb.Database(config, 'test')
516         setupSchema(db, 1, bsddb)
517         self.db = bsddb.Database(config)
518         setupSchema(self.db, 0, bsddb)
519         self.db2 = bsddb.Database(config, 'test')
520         setupSchema(self.db2, 0, bsddb)
523 class bsddb3DBTestCase(anydbmDBTestCase):
524     def setUp(self):
525         from roundup.backends import bsddb3
526         # remove previous test, ignore errors
527         if os.path.exists(config.DATABASE):
528             shutil.rmtree(config.DATABASE)
529         os.makedirs(config.DATABASE + '/files')
530         self.db = bsddb3.Database(config, 'test')
531         setupSchema(self.db, 1, bsddb3)
532         self.db2 = bsddb3.Database(config, 'test')
533         setupSchema(self.db2, 0, bsddb3)
535 class bsddb3ReadOnlyDBTestCase(anydbmReadOnlyDBTestCase):
536     def setUp(self):
537         from roundup.backends import bsddb3
538         # remove previous test, ignore errors
539         if os.path.exists(config.DATABASE):
540             shutil.rmtree(config.DATABASE)
541         os.makedirs(config.DATABASE + '/files')
542         db = bsddb3.Database(config, 'test')
543         setupSchema(db, 1, bsddb3)
544         self.db = bsddb3.Database(config)
545         setupSchema(self.db, 0, bsddb3)
546         self.db2 = bsddb3.Database(config, 'test')
547         setupSchema(self.db2, 0, bsddb3)
550 class gadflyDBTestCase(anydbmDBTestCase):
551     ''' Gadfly doesn't support multiple connections to the one local
552         database
553     '''
554     def setUp(self):
555         from roundup.backends import gadfly
556         # remove previous test, ignore errors
557         if os.path.exists(config.DATABASE):
558             shutil.rmtree(config.DATABASE)
559         config.GADFLY_DATABASE = ('test', config.DATABASE)
560         os.makedirs(config.DATABASE + '/files')
561         self.db = gadfly.Database(config, 'test')
562         setupSchema(self.db, 1, gadfly)
564     def testIDGeneration(self):
565         id1 = self.db.issue.create(title="spam", status='1')
566         id2 = self.db.issue.create(title="eggs", status='2')
567         self.assertNotEqual(id1, id2)
569     def testNewProperty(self):
570         # gadfly doesn't have an ALTER TABLE command :(
571         pass
573 class gadflyReadOnlyDBTestCase(anydbmReadOnlyDBTestCase):
574     def setUp(self):
575         from roundup.backends import gadfly
576         # remove previous test, ignore errors
577         if os.path.exists(config.DATABASE):
578             shutil.rmtree(config.DATABASE)
579         config.GADFLY_DATABASE = ('test', config.DATABASE)
580         os.makedirs(config.DATABASE + '/files')
581         db = gadfly.Database(config, 'test')
582         setupSchema(db, 1, gadfly)
583         self.db = gadfly.Database(config)
584         setupSchema(self.db, 0, gadfly)
587 class metakitDBTestCase(anydbmDBTestCase):
588     def setUp(self):
589         from roundup.backends import metakit
590         import weakref
591         metakit._instances = weakref.WeakValueDictionary()
592         # remove previous test, ignore errors
593         if os.path.exists(config.DATABASE):
594             shutil.rmtree(config.DATABASE)
595         os.makedirs(config.DATABASE + '/files')
596         self.db = metakit.Database(config, 'test')
597         setupSchema(self.db, 1, metakit)
598         self.db2 = metakit.Database(config, 'test')
599         setupSchema(self.db2, 0, metakit)
601     def testTransactions(self):
602         # remember the number of items we started
603         num_issues = len(self.db.issue.list())
604         self.db.issue.create(title="don't commit me!", status='1')
605         self.assertNotEqual(num_issues, len(self.db.issue.list()))
606         self.db.rollback()
607         self.assertEqual(num_issues, len(self.db.issue.list()))
608         self.db.issue.create(title="please commit me!", status='1')
609         self.assertNotEqual(num_issues, len(self.db.issue.list()))
610         self.db.commit()
611         self.assertNotEqual(num_issues, len(self.db.issue.list()))
612         self.db.rollback()
613         self.assertNotEqual(num_issues, len(self.db.issue.list()))
614         self.db.file.create(name="test", type="text/plain", content="hi")
615         self.db.rollback()
616         for i in range(10):
617             self.db.file.create(name="test", type="text/plain", 
618                     content="hi %d"%(i))
619             self.db.commit()
620         # TODO: would be good to be able to ensure the file is not on disk after
621         # a rollback...
622         self.assertNotEqual(num_files, num_files2)
623         self.db.file.create(name="test", type="text/plain", content="hi")
624         self.db.rollback()
626 class metakitReadOnlyDBTestCase(anydbmReadOnlyDBTestCase):
627     def setUp(self):
628         from roundup.backends import metakit
629         import weakref
630         metakit._instances = weakref.WeakValueDictionary()
631         # remove previous test, ignore errors
632         if os.path.exists(config.DATABASE):
633             shutil.rmtree(config.DATABASE)
634         os.makedirs(config.DATABASE + '/files')
635         db = metakit.Database(config, 'test')
636         setupSchema(db, 1, metakit)
637         self.db = metakit.Database(config)
638         setupSchema(self.db, 0, metakit)
639         self.db2 = metakit.Database(config, 'test')
640         setupSchema(self.db2, 0, metakit)
642 def suite():
643     l = [
644          unittest.makeSuite(anydbmDBTestCase, 'test'),
645          unittest.makeSuite(anydbmReadOnlyDBTestCase, 'test')
646     ]
647     #return unittest.TestSuite(l)
649     try:
650         import bsddb
651         l.append(unittest.makeSuite(bsddbDBTestCase, 'test'))
652         l.append(unittest.makeSuite(bsddbReadOnlyDBTestCase, 'test'))
653     except:
654         print 'bsddb module not found, skipping bsddb DBTestCase'
656     try:
657         import bsddb3
658         l.append(unittest.makeSuite(bsddb3DBTestCase, 'test'))
659         l.append(unittest.makeSuite(bsddb3ReadOnlyDBTestCase, 'test'))
660     except:
661         print 'bsddb3 module not found, skipping bsddb3 DBTestCase'
663     try:
664         import gadfly
665         l.append(unittest.makeSuite(gadflyDBTestCase, 'test'))
666         l.append(unittest.makeSuite(gadflyReadOnlyDBTestCase, 'test'))
667     except:
668         print 'gadfly module not found, skipping gadfly DBTestCase'
670     try:
671         import metakit
672         l.append(unittest.makeSuite(metakitDBTestCase, 'test'))
673         l.append(unittest.makeSuite(metakitReadOnlyDBTestCase, 'test'))
674     except:
675         print 'metakit module not found, skipping metakit DBTestCase'
677     return unittest.TestSuite(l)
680 # $Log: not supported by cvs2svn $
681 # Revision 1.40  2002/08/23 04:48:36  richard
682 # oops, forgot
684 # Revision 1.39  2002/07/31 23:57:37  richard
685 #  . web forms may now unset Link values (like assignedto)
687 # Revision 1.38  2002/07/26 08:27:00  richard
688 # Very close now. The cgi and mailgw now use the new security API. The two
689 # templates have been migrated to that setup. Lots of unit tests. Still some
690 # issue in the web form for editing Roles assigned to users.
692 # Revision 1.37  2002/07/25 07:14:06  richard
693 # Bugger it. Here's the current shape of the new security implementation.
694 # Still to do:
695 #  . call the security funcs from cgi and mailgw
696 #  . change shipped templates to include correct initialisation and remove
697 #    the old config vars
698 # ... that seems like a lot. The bulk of the work has been done though. Honest :)
700 # Revision 1.36  2002/07/19 03:36:34  richard
701 # Implemented the destroy() method needed by the session database (and possibly
702 # others). At the same time, I removed the leading underscores from the hyperdb
703 # methods that Really Didn't Need Them.
704 # The journal also raises IndexError now for all situations where there is a
705 # request for the journal of a node that doesn't have one. It used to return
706 # [] in _some_ situations, but not all. This _may_ break code, but the tests
707 # pass...
709 # Revision 1.35  2002/07/18 23:07:08  richard
710 # Unit tests and a few fixes.
712 # Revision 1.34  2002/07/18 11:52:00  richard
713 # oops
715 # Revision 1.33  2002/07/18 11:50:58  richard
716 # added tests for number type too
718 # Revision 1.32  2002/07/18 11:41:10  richard
719 # added tests for boolean type, and fixes to anydbm backend
721 # Revision 1.31  2002/07/14 23:17:45  richard
722 # minor change to make testing easier
724 # Revision 1.30  2002/07/14 06:06:34  richard
725 # Did some old TODOs
727 # Revision 1.29  2002/07/14 04:03:15  richard
728 # Implemented a switch to disable journalling for a Class. CGI session
729 # database now uses it.
731 # Revision 1.28  2002/07/14 02:16:29  richard
732 # Fixes for the metakit backend (removed the cut-n-paste IssueClass, removed
733 # a special case for it in testing)
735 # Revision 1.27  2002/07/14 02:05:54  richard
736 # . all storage-specific code (ie. backend) is now implemented by the backends
738 # Revision 1.26  2002/07/11 01:11:03  richard
739 # Added metakit backend to the db tests and fixed the more easily fixable test
740 # failures.
742 # Revision 1.25  2002/07/09 04:19:09  richard
743 # Added reindex command to roundup-admin.
744 # Fixed reindex on first access.
745 # Also fixed reindexing of entries that change.
747 # Revision 1.24  2002/07/09 03:02:53  richard
748 # More indexer work:
749 # - all String properties may now be indexed too. Currently there's a bit of
750 #   "issue" specific code in the actual searching which needs to be
751 #   addressed. In a nutshell:
752 #   + pass 'indexme="yes"' as a String() property initialisation arg, eg:
753 #         file = FileClass(db, "file", name=String(), type=String(),
754 #             comment=String(indexme="yes"))
755 #   + the comment will then be indexed and be searchable, with the results
756 #     related back to the issue that the file is linked to
757 # - as a result of this work, the FileClass has a default MIME type that may
758 #   be overridden in a subclass, or by the use of a "type" property as is
759 #   done in the default templates.
760 # - the regeneration of the indexes (if necessary) is done once the schema is
761 #   set up in the dbinit.
763 # Revision 1.23  2002/06/20 23:51:48  richard
764 # Cleaned up the hyperdb tests
766 # Revision 1.22  2002/05/21 05:52:11  richard
767 # Well whadya know, bsddb3 works again.
768 # The backend is implemented _exactly_ the same as bsddb - so there's no
769 # using its transaction or locking support. It'd be nice to use those some
770 # day I suppose.
772 # Revision 1.21  2002/04/15 23:25:15  richard
773 # . node ids are now generated from a lockable store - no more race conditions
775 # We're using the portalocker code by Jonathan Feinberg that was contributed
776 # to the ASPN Python cookbook. This gives us locking across Unix and Windows.
778 # Revision 1.20  2002/04/03 05:54:31  richard
779 # Fixed serialisation problem by moving the serialisation step out of the
780 # hyperdb.Class (get, set) into the hyperdb.Database.
782 # Also fixed htmltemplate after the showid changes I made yesterday.
784 # Unit tests for all of the above written.
786 # Revision 1.19  2002/02/25 14:34:31  grubert
787 #  . use blobfiles in back_anydbm which is used in back_bsddb.
788 #    change test_db as dirlist does not work for subdirectories.
789 #    ATTENTION: blobfiles now creates subdirectories for files.
791 # Revision 1.18  2002/01/22 07:21:13  richard
792 # . fixed back_bsddb so it passed the journal tests
794 # ... it didn't seem happy using the back_anydbm _open method, which is odd.
795 # Yet another occurrance of whichdb not being able to recognise older bsddb
796 # databases. Yadda yadda. Made the HYPERDBDEBUG stuff more sane in the
797 # process.
799 # Revision 1.17  2002/01/22 05:06:09  rochecompaan
800 # We need to keep the last 'set' entry in the journal to preserve
801 # information on 'activity' for nodes.
803 # Revision 1.16  2002/01/21 16:33:20  rochecompaan
804 # You can now use the roundup-admin tool to pack the database
806 # Revision 1.15  2002/01/19 13:16:04  rochecompaan
807 # Journal entries for link and multilink properties can now be switched on
808 # or off.
810 # Revision 1.14  2002/01/16 07:02:57  richard
811 #  . lots of date/interval related changes:
812 #    - more relaxed date format for input
814 # Revision 1.13  2002/01/14 02:20:15  richard
815 #  . changed all config accesses so they access either the instance or the
816 #    config attriubute on the db. This means that all config is obtained from
817 #    instance_config instead of the mish-mash of classes. This will make
818 #    switching to a ConfigParser setup easier too, I hope.
820 # At a minimum, this makes migration a _little_ easier (a lot easier in the
821 # 0.5.0 switch, I hope!)
823 # Revision 1.12  2001/12/17 03:52:48  richard
824 # Implemented file store rollback. As a bonus, the hyperdb is now capable of
825 # storing more than one file per node - if a property name is supplied,
826 # the file is called designator.property.
827 # I decided not to migrate the existing files stored over to the new naming
828 # scheme - the FileClass just doesn't specify the property name.
830 # Revision 1.11  2001/12/10 23:17:20  richard
831 # Added transaction tests to test_db
833 # Revision 1.10  2001/12/03 21:33:39  richard
834 # Fixes so the tests use commit and not close
836 # Revision 1.9  2001/12/02 05:06:16  richard
837 # . We now use weakrefs in the Classes to keep the database reference, so
838 #   the close() method on the database is no longer needed.
839 #   I bumped the minimum python requirement up to 2.1 accordingly.
840 # . #487480 ] roundup-server
841 # . #487476 ] INSTALL.txt
843 # I also cleaned up the change message / post-edit stuff in the cgi client.
844 # There's now a clearly marked "TODO: append the change note" where I believe
845 # the change note should be added there. The "changes" list will obviously
846 # have to be modified to be a dict of the changes, or somesuch.
848 # More testing needed.
850 # Revision 1.8  2001/10/09 07:25:59  richard
851 # Added the Password property type. See "pydoc roundup.password" for
852 # implementation details. Have updated some of the documentation too.
854 # Revision 1.7  2001/08/29 06:23:59  richard
855 # Disabled the bsddb3 module entirely in the unit testing. See CHANGES for
856 # details.
858 # Revision 1.6  2001/08/07 00:24:43  richard
859 # stupid typo
861 # Revision 1.5  2001/08/07 00:15:51  richard
862 # Added the copyright/license notice to (nearly) all files at request of
863 # Bizar Software.
865 # Revision 1.4  2001/07/30 03:45:56  richard
866 # Added more DB to test_db. Can skip tests where imports fail.
868 # Revision 1.3  2001/07/29 07:01:39  richard
869 # Added vim command to all source so that we don't get no steenkin' tabs :)
871 # Revision 1.2  2001/07/29 04:09:20  richard
872 # Added the fabricated property "id" to all hyperdb classes.
874 # Revision 1.1  2001/07/27 06:55:07  richard
875 # moving tests -> test
877 # Revision 1.7  2001/07/27 06:26:43  richard
878 # oops - wasn't deleting the test dir after the read-only tests
880 # Revision 1.6  2001/07/27 06:23:59  richard
881 # consistency
883 # Revision 1.5  2001/07/27 06:23:09  richard
884 # Added some new hyperdb tests to make sure we raise the right exceptions.
886 # Revision 1.4  2001/07/25 04:34:31  richard
887 # Added id and log to tests files...
890 # vim: set filetype=python ts=4 sw=4 et si