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.73 2003-03-03 21:05:19 richard Exp $
20 import unittest, os, shutil, time
22 from roundup.hyperdb import String, Password, Link, Multilink, Date, \
23 Interval, DatabaseError, Boolean, Number, Node
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"), fooz=Password())
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 user.create(username="admin", roles='Admin')
43 status.create(name="unread")
44 status.create(name="in-progress")
45 status.create(name="testing")
46 status.create(name="resolved")
47 db.commit()
49 class MyTestCase(unittest.TestCase):
50 def tearDown(self):
51 self.db.close()
52 if os.path.exists('_test_dir'):
53 shutil.rmtree('_test_dir')
55 class config:
56 DATABASE='_test_dir'
57 MAILHOST = 'localhost'
58 MAIL_DOMAIN = 'fill.me.in.'
59 TRACKER_NAME = 'Roundup issue tracker'
60 TRACKER_EMAIL = 'issue_tracker@%s'%MAIL_DOMAIN
61 TRACKER_WEB = 'http://some.useful.url/'
62 ADMIN_EMAIL = 'roundup-admin@%s'%MAIL_DOMAIN
63 FILTER_POSITION = 'bottom' # one of 'top', 'bottom', 'top and bottom'
64 ANONYMOUS_ACCESS = 'deny' # either 'deny' or 'allow'
65 ANONYMOUS_REGISTER = 'deny' # either 'deny' or 'allow'
66 MESSAGES_TO_AUTHOR = 'no' # either 'yes' or 'no'
67 EMAIL_SIGNATURE_POSITION = 'bottom'
68 # Mysql connection data
69 MYSQL_DBHOST = 'localhost'
70 MYSQL_DBUSER = 'rounduptest'
71 MYSQL_DBPASSWORD = 'rounduptest'
72 MYSQL_DBNAME = 'rounduptest'
73 MYSQL_DATABASE = (MYSQL_DBHOST, MYSQL_DBUSER, MYSQL_DBPASSWORD, MYSQL_DBNAME)
75 class nodbconfig(config):
76 MYSQL_DATABASE = (config.MYSQL_DBHOST, config.MYSQL_DBUSER, config.MYSQL_DBPASSWORD)
78 class anydbmDBTestCase(MyTestCase):
79 def setUp(self):
80 from roundup.backends import anydbm
81 # remove previous test, ignore errors
82 if os.path.exists(config.DATABASE):
83 shutil.rmtree(config.DATABASE)
84 os.makedirs(config.DATABASE + '/files')
85 self.db = anydbm.Database(config, 'admin')
86 setupSchema(self.db, 1, anydbm)
88 #
89 # schema mutation
90 #
91 def testAddProperty(self):
92 self.db.issue.create(title="spam", status='1')
93 self.db.commit()
95 self.db.issue.addprop(fixer=Link("user"))
96 # force any post-init stuff to happen
97 self.db.post_init()
98 props = self.db.issue.getprops()
99 keys = props.keys()
100 keys.sort()
101 self.assertEqual(keys, ['activity', 'assignedto', 'creation',
102 'creator', 'deadline', 'files', 'fixer', 'foo', 'id', 'messages',
103 'nosy', 'status', 'superseder', 'title'])
104 self.assertEqual(self.db.issue.get('1', "fixer"), None)
106 def testRemoveProperty(self):
107 self.db.issue.create(title="spam", status='1')
108 self.db.commit()
110 del self.db.issue.properties['title']
111 self.db.post_init()
112 props = self.db.issue.getprops()
113 keys = props.keys()
114 keys.sort()
115 self.assertEqual(keys, ['activity', 'assignedto', 'creation',
116 'creator', 'deadline', 'files', 'foo', 'id', 'messages',
117 'nosy', 'status', 'superseder'])
118 self.assertEqual(self.db.issue.list(), ['1'])
120 def testAddRemoveProperty(self):
121 self.db.issue.create(title="spam", status='1')
122 self.db.commit()
124 self.db.issue.addprop(fixer=Link("user"))
125 del self.db.issue.properties['title']
126 self.db.post_init()
127 props = self.db.issue.getprops()
128 keys = props.keys()
129 keys.sort()
130 self.assertEqual(keys, ['activity', 'assignedto', 'creation',
131 'creator', 'deadline', 'files', 'fixer', 'foo', 'id', 'messages',
132 'nosy', 'status', 'superseder'])
133 self.assertEqual(self.db.issue.list(), ['1'])
135 #
136 # basic operations
137 #
138 def testIDGeneration(self):
139 id1 = self.db.issue.create(title="spam", status='1')
140 id2 = self.db.issue.create(title="eggs", status='2')
141 self.assertNotEqual(id1, id2)
143 def testStringChange(self):
144 for commit in (0,1):
145 # test set & retrieve
146 nid = self.db.issue.create(title="spam", status='1')
147 self.assertEqual(self.db.issue.get(nid, 'title'), 'spam')
149 # change and make sure we retrieve the correct value
150 self.db.issue.set(nid, title='eggs')
151 if commit: self.db.commit()
152 self.assertEqual(self.db.issue.get(nid, 'title'), 'eggs')
154 def testStringUnset(self):
155 for commit in (0,1):
156 nid = self.db.issue.create(title="spam", status='1')
157 if commit: self.db.commit()
158 self.assertEqual(self.db.issue.get(nid, 'title'), 'spam')
159 # make sure we can unset
160 self.db.issue.set(nid, title=None)
161 if commit: self.db.commit()
162 self.assertEqual(self.db.issue.get(nid, "title"), None)
164 def testLinkChange(self):
165 for commit in (0,1):
166 nid = self.db.issue.create(title="spam", status='1')
167 if commit: self.db.commit()
168 self.assertEqual(self.db.issue.get(nid, "status"), '1')
169 self.db.issue.set(nid, status='2')
170 if commit: self.db.commit()
171 self.assertEqual(self.db.issue.get(nid, "status"), '2')
173 def testLinkUnset(self):
174 for commit in (0,1):
175 nid = self.db.issue.create(title="spam", status='1')
176 if commit: self.db.commit()
177 self.db.issue.set(nid, status=None)
178 if commit: self.db.commit()
179 self.assertEqual(self.db.issue.get(nid, "status"), None)
181 def testMultilinkChange(self):
182 for commit in (0,1):
183 u1 = self.db.user.create(username='foo%s'%commit)
184 u2 = self.db.user.create(username='bar%s'%commit)
185 nid = self.db.issue.create(title="spam", nosy=[u1])
186 if commit: self.db.commit()
187 self.assertEqual(self.db.issue.get(nid, "nosy"), [u1])
188 self.db.issue.set(nid, nosy=[])
189 if commit: self.db.commit()
190 self.assertEqual(self.db.issue.get(nid, "nosy"), [])
191 self.db.issue.set(nid, nosy=[u1,u2])
192 if commit: self.db.commit()
193 self.assertEqual(self.db.issue.get(nid, "nosy"), [u1,u2])
195 def testDateChange(self):
196 for commit in (0,1):
197 nid = self.db.issue.create(title="spam", status='1')
198 a = self.db.issue.get(nid, "deadline")
199 if commit: self.db.commit()
200 self.db.issue.set(nid, deadline=date.Date())
201 b = self.db.issue.get(nid, "deadline")
202 if commit: self.db.commit()
203 self.assertNotEqual(a, b)
204 self.assertNotEqual(b, date.Date('1970-1-1 00:00:00'))
206 def testDateUnset(self):
207 for commit in (0,1):
208 nid = self.db.issue.create(title="spam", status='1')
209 self.db.issue.set(nid, deadline=date.Date())
210 if commit: self.db.commit()
211 self.assertNotEqual(self.db.issue.get(nid, "deadline"), None)
212 self.db.issue.set(nid, deadline=None)
213 if commit: self.db.commit()
214 self.assertEqual(self.db.issue.get(nid, "deadline"), None)
216 def testIntervalChange(self):
217 for commit in (0,1):
218 nid = self.db.issue.create(title="spam", status='1')
219 if commit: self.db.commit()
220 a = self.db.issue.get(nid, "foo")
221 i = date.Interval('-1d')
222 self.db.issue.set(nid, foo=i)
223 if commit: self.db.commit()
224 self.assertNotEqual(self.db.issue.get(nid, "foo"), a)
225 self.assertEqual(i, self.db.issue.get(nid, "foo"))
226 j = date.Interval('1y')
227 self.db.issue.set(nid, foo=j)
228 if commit: self.db.commit()
229 self.assertNotEqual(self.db.issue.get(nid, "foo"), i)
230 self.assertEqual(j, self.db.issue.get(nid, "foo"))
232 def testIntervalUnset(self):
233 for commit in (0,1):
234 nid = self.db.issue.create(title="spam", status='1')
235 self.db.issue.set(nid, foo=date.Interval('-1d'))
236 if commit: self.db.commit()
237 self.assertNotEqual(self.db.issue.get(nid, "foo"), None)
238 self.db.issue.set(nid, foo=None)
239 if commit: self.db.commit()
240 self.assertEqual(self.db.issue.get(nid, "foo"), None)
242 def testBooleanChange(self):
243 userid = self.db.user.create(username='foo', assignable=1)
244 self.assertEqual(1, self.db.user.get(userid, 'assignable'))
245 self.db.user.set(userid, assignable=0)
246 self.assertEqual(self.db.user.get(userid, 'assignable'), 0)
248 def testBooleanUnset(self):
249 nid = self.db.user.create(username='foo', assignable=1)
250 self.db.user.set(nid, assignable=None)
251 self.assertEqual(self.db.user.get(nid, "assignable"), None)
253 def testNumberChange(self):
254 nid = self.db.user.create(username='foo', age=1)
255 self.assertEqual(1, self.db.user.get(nid, 'age'))
256 self.db.user.set(nid, age=3)
257 self.assertNotEqual(self.db.user.get(nid, 'age'), 1)
258 self.db.user.set(nid, age=1.0)
260 def testNumberUnset(self):
261 nid = self.db.user.create(username='foo', age=1)
262 self.db.user.set(nid, age=None)
263 self.assertEqual(self.db.user.get(nid, "age"), None)
265 def testKeyValue(self):
266 newid = self.db.user.create(username="spam")
267 self.assertEqual(self.db.user.lookup('spam'), newid)
268 self.db.commit()
269 self.assertEqual(self.db.user.lookup('spam'), newid)
270 self.db.user.retire(newid)
271 self.assertRaises(KeyError, self.db.user.lookup, 'spam')
273 # use the key again now that the old is retired
274 newid2 = self.db.user.create(username="spam")
275 self.assertNotEqual(newid, newid2)
277 def testRetire(self):
278 self.db.issue.create(title="spam", status='1')
279 b = self.db.status.get('1', 'name')
280 a = self.db.status.list()
281 self.db.status.retire('1')
282 # make sure the list is different
283 self.assertNotEqual(a, self.db.status.list())
284 # can still access the node if necessary
285 self.assertEqual(self.db.status.get('1', 'name'), b)
286 self.db.commit()
287 self.assertEqual(self.db.status.get('1', 'name'), b)
288 self.assertNotEqual(a, self.db.status.list())
290 def testSerialisation(self):
291 nid = self.db.issue.create(title="spam", status='1',
292 deadline=date.Date(), foo=date.Interval('-1d'))
293 self.db.commit()
294 assert isinstance(self.db.issue.get(nid, 'deadline'), date.Date)
295 assert isinstance(self.db.issue.get(nid, 'foo'), date.Interval)
296 uid = self.db.user.create(username="fozzy",
297 password=password.Password('t. bear'))
298 self.db.commit()
299 assert isinstance(self.db.user.get(uid, 'password'), password.Password)
301 def testTransactions(self):
302 # remember the number of items we started
303 num_issues = len(self.db.issue.list())
304 num_files = self.db.numfiles()
305 self.db.issue.create(title="don't commit me!", status='1')
306 self.assertNotEqual(num_issues, len(self.db.issue.list()))
307 self.db.rollback()
308 self.assertEqual(num_issues, len(self.db.issue.list()))
309 self.db.issue.create(title="please commit me!", status='1')
310 self.assertNotEqual(num_issues, len(self.db.issue.list()))
311 self.db.commit()
312 self.assertNotEqual(num_issues, len(self.db.issue.list()))
313 self.db.rollback()
314 self.assertNotEqual(num_issues, len(self.db.issue.list()))
315 self.db.file.create(name="test", type="text/plain", content="hi")
316 self.db.rollback()
317 self.assertEqual(num_files, self.db.numfiles())
318 for i in range(10):
319 self.db.file.create(name="test", type="text/plain",
320 content="hi %d"%(i))
321 self.db.commit()
322 num_files2 = self.db.numfiles()
323 self.assertNotEqual(num_files, num_files2)
324 self.db.file.create(name="test", type="text/plain", content="hi")
325 self.db.rollback()
326 self.assertNotEqual(num_files, self.db.numfiles())
327 self.assertEqual(num_files2, self.db.numfiles())
329 def testDestroyNoJournalling(self):
330 self.innerTestDestroy(klass=self.db.session)
332 def testDestroyJournalling(self):
333 self.innerTestDestroy(klass=self.db.issue)
335 def innerTestDestroy(self, klass):
336 newid = klass.create(title='Mr Friendly')
337 n = len(klass.list())
338 self.assertEqual(klass.get(newid, 'title'), 'Mr Friendly')
339 klass.destroy(newid)
340 self.assertRaises(IndexError, klass.get, newid, 'title')
341 self.assertNotEqual(len(klass.list()), n)
342 if klass.do_journal:
343 self.assertRaises(IndexError, klass.history, newid)
345 # now with a commit
346 newid = klass.create(title='Mr Friendly')
347 n = len(klass.list())
348 self.assertEqual(klass.get(newid, 'title'), 'Mr Friendly')
349 self.db.commit()
350 klass.destroy(newid)
351 self.assertRaises(IndexError, klass.get, newid, 'title')
352 self.db.commit()
353 self.assertRaises(IndexError, klass.get, newid, 'title')
354 self.assertNotEqual(len(klass.list()), n)
355 if klass.do_journal:
356 self.assertRaises(IndexError, klass.history, newid)
358 # now with a rollback
359 newid = klass.create(title='Mr Friendly')
360 n = len(klass.list())
361 self.assertEqual(klass.get(newid, 'title'), 'Mr Friendly')
362 self.db.commit()
363 klass.destroy(newid)
364 self.assertNotEqual(len(klass.list()), n)
365 self.assertRaises(IndexError, klass.get, newid, 'title')
366 self.db.rollback()
367 self.assertEqual(klass.get(newid, 'title'), 'Mr Friendly')
368 self.assertEqual(len(klass.list()), n)
369 if klass.do_journal:
370 self.assertNotEqual(klass.history(newid), [])
372 def testExceptions(self):
373 # this tests the exceptions that should be raised
374 ar = self.assertRaises
376 #
377 # class create
378 #
379 # string property
380 ar(TypeError, self.db.status.create, name=1)
381 # invalid property name
382 ar(KeyError, self.db.status.create, foo='foo')
383 # key name clash
384 ar(ValueError, self.db.status.create, name='unread')
385 # invalid link index
386 ar(IndexError, self.db.issue.create, title='foo', status='bar')
387 # invalid link value
388 ar(ValueError, self.db.issue.create, title='foo', status=1)
389 # invalid multilink type
390 ar(TypeError, self.db.issue.create, title='foo', status='1',
391 nosy='hello')
392 # invalid multilink index type
393 ar(ValueError, self.db.issue.create, title='foo', status='1',
394 nosy=[1])
395 # invalid multilink index
396 ar(IndexError, self.db.issue.create, title='foo', status='1',
397 nosy=['10'])
399 #
400 # key property
401 #
402 # key must be a String
403 ar(TypeError, self.db.file.setkey, 'fooz')
404 # key must exist
405 ar(KeyError, self.db.file.setkey, 'fubar')
407 #
408 # class get
409 #
410 # invalid node id
411 ar(IndexError, self.db.issue.get, '99', 'title')
412 # invalid property name
413 ar(KeyError, self.db.status.get, '2', 'foo')
415 #
416 # class set
417 #
418 # invalid node id
419 ar(IndexError, self.db.issue.set, '99', title='foo')
420 # invalid property name
421 ar(KeyError, self.db.status.set, '1', foo='foo')
422 # string property
423 ar(TypeError, self.db.status.set, '1', name=1)
424 # key name clash
425 ar(ValueError, self.db.status.set, '2', name='unread')
426 # set up a valid issue for me to work on
427 id = self.db.issue.create(title="spam", status='1')
428 # invalid link index
429 ar(IndexError, self.db.issue.set, id, title='foo', status='bar')
430 # invalid link value
431 ar(ValueError, self.db.issue.set, id, title='foo', status=1)
432 # invalid multilink type
433 ar(TypeError, self.db.issue.set, id, title='foo', status='1',
434 nosy='hello')
435 # invalid multilink index type
436 ar(ValueError, self.db.issue.set, id, title='foo', status='1',
437 nosy=[1])
438 # invalid multilink index
439 ar(IndexError, self.db.issue.set, id, title='foo', status='1',
440 nosy=['10'])
441 # NOTE: the following increment the username to avoid problems
442 # within metakit's backend (it creates the node, and then sets the
443 # info, so the create (and by a fluke the username set) go through
444 # before the age/assignable/etc. set, which raises the exception)
445 # invalid number value
446 ar(TypeError, self.db.user.create, username='foo', age='a')
447 # invalid boolean value
448 ar(TypeError, self.db.user.create, username='foo2', assignable='true')
449 nid = self.db.user.create(username='foo3')
450 # invalid number value
451 ar(TypeError, self.db.user.set, nid, age='a')
452 # invalid boolean value
453 ar(TypeError, self.db.user.set, nid, assignable='true')
455 def testJournals(self):
456 self.db.user.create(username="mary")
457 self.db.user.create(username="pete")
458 self.db.issue.create(title="spam", status='1')
459 self.db.commit()
461 # journal entry for issue create
462 journal = self.db.getjournal('issue', '1')
463 self.assertEqual(1, len(journal))
464 (nodeid, date_stamp, journaltag, action, params) = journal[0]
465 self.assertEqual(nodeid, '1')
466 self.assertEqual(journaltag, self.db.user.lookup('admin'))
467 self.assertEqual(action, 'create')
468 keys = params.keys()
469 keys.sort()
470 self.assertEqual(keys, [])
472 # journal entry for link
473 journal = self.db.getjournal('user', '1')
474 self.assertEqual(1, len(journal))
475 self.db.issue.set('1', assignedto='1')
476 self.db.commit()
477 journal = self.db.getjournal('user', '1')
478 self.assertEqual(2, len(journal))
479 (nodeid, date_stamp, journaltag, action, params) = journal[1]
480 self.assertEqual('1', nodeid)
481 self.assertEqual('1', journaltag)
482 self.assertEqual('link', action)
483 self.assertEqual(('issue', '1', 'assignedto'), params)
485 # journal entry for unlink
486 self.db.issue.set('1', assignedto='2')
487 self.db.commit()
488 journal = self.db.getjournal('user', '1')
489 self.assertEqual(3, len(journal))
490 (nodeid, date_stamp, journaltag, action, params) = journal[2]
491 self.assertEqual('1', nodeid)
492 self.assertEqual('1', journaltag)
493 self.assertEqual('unlink', action)
494 self.assertEqual(('issue', '1', 'assignedto'), params)
496 # test disabling journalling
497 # ... get the last entry
498 time.sleep(1)
499 entry = self.db.getjournal('issue', '1')[-1]
500 (x, date_stamp, x, x, x) = entry
501 self.db.issue.disableJournalling()
502 self.db.issue.set('1', title='hello world')
503 self.db.commit()
504 entry = self.db.getjournal('issue', '1')[-1]
505 (x, date_stamp2, x, x, x) = entry
506 # see if the change was journalled when it shouldn't have been
507 self.assertEqual(date_stamp, date_stamp2)
508 time.sleep(1)
509 self.db.issue.enableJournalling()
510 self.db.issue.set('1', title='hello world 2')
511 self.db.commit()
512 entry = self.db.getjournal('issue', '1')[-1]
513 (x, date_stamp2, x, x, x) = entry
514 # see if the change was journalled
515 self.assertNotEqual(date_stamp, date_stamp2)
517 def testPack(self):
518 id = self.db.issue.create(title="spam", status='1')
519 self.db.commit()
520 self.db.issue.set(id, status='2')
521 self.db.commit()
523 # sleep for at least a second, then get a date to pack at
524 time.sleep(1)
525 pack_before = date.Date('.')
527 # wait another second and add one more entry
528 time.sleep(1)
529 self.db.issue.set(id, status='3')
530 self.db.commit()
531 jlen = len(self.db.getjournal('issue', id))
533 # pack
534 self.db.pack(pack_before)
536 # we should have the create and last set entries now
537 self.assertEqual(jlen-1, len(self.db.getjournal('issue', id)))
539 def testSearching(self):
540 self.db.file.create(content='hello', type="text/plain")
541 self.db.file.create(content='world', type="text/frozz",
542 comment='blah blah')
543 self.db.issue.create(files=['1', '2'], title="flebble plop")
544 self.db.issue.create(title="flebble frooz")
545 self.db.commit()
546 self.assertEquals(self.db.indexer.search(['hello'], self.db.issue),
547 {'1': {'files': ['1']}})
548 self.assertEquals(self.db.indexer.search(['world'], self.db.issue), {})
549 self.assertEquals(self.db.indexer.search(['frooz'], self.db.issue),
550 {'2': {}})
551 self.assertEquals(self.db.indexer.search(['flebble'], self.db.issue),
552 {'2': {}, '1': {}})
554 def testReindexing(self):
555 self.db.issue.create(title="frooz")
556 self.db.commit()
557 self.assertEquals(self.db.indexer.search(['frooz'], self.db.issue),
558 {'1': {}})
559 self.db.issue.set('1', title="dooble")
560 self.db.commit()
561 self.assertEquals(self.db.indexer.search(['dooble'], self.db.issue),
562 {'1': {}})
563 self.assertEquals(self.db.indexer.search(['frooz'], self.db.issue), {})
565 def testForcedReindexing(self):
566 self.db.issue.create(title="flebble frooz")
567 self.db.commit()
568 self.assertEquals(self.db.indexer.search(['flebble'], self.db.issue),
569 {'1': {}})
570 self.db.indexer.quiet = 1
571 self.db.indexer.force_reindex()
572 self.db.post_init()
573 self.db.indexer.quiet = 9
574 self.assertEquals(self.db.indexer.search(['flebble'], self.db.issue),
575 {'1': {}})
577 #
578 # searching tests follow
579 #
580 def testFind(self):
581 self.db.user.create(username='test')
582 ids = []
583 ids.append(self.db.issue.create(status="1", nosy=['1']))
584 oddid = self.db.issue.create(status="2", nosy=['2'])
585 ids.append(self.db.issue.create(status="1", nosy=['1','2']))
586 self.db.issue.create(status="3", nosy=['1'])
587 ids.sort()
589 # should match first and third
590 got = self.db.issue.find(status='1')
591 got.sort()
592 self.assertEqual(got, ids)
594 # none
595 self.assertEqual(self.db.issue.find(status='4'), [])
597 # should match first three
598 got = self.db.issue.find(status='1', nosy='2')
599 got.sort()
600 ids.append(oddid)
601 ids.sort()
602 self.assertEqual(got, ids)
604 # none
605 self.assertEqual(self.db.issue.find(status='4', nosy='3'), [])
607 def testStringFind(self):
608 ids = []
609 ids.append(self.db.issue.create(title="spam"))
610 self.db.issue.create(title="not spam")
611 ids.append(self.db.issue.create(title="spam"))
612 ids.sort()
613 got = self.db.issue.stringFind(title='spam')
614 got.sort()
615 self.assertEqual(got, ids)
616 self.assertEqual(self.db.issue.stringFind(title='fubar'), [])
618 def filteringSetup(self):
619 for user in (
620 {'username': 'bleep'},
621 {'username': 'blop'},
622 {'username': 'blorp'}):
623 self.db.user.create(**user)
624 iss = self.db.issue
625 for issue in (
626 {'title': 'issue one', 'status': '2'},
627 {'title': 'issue two', 'status': '1'},
628 {'title': 'issue three', 'status': '1', 'nosy': ['1','2']}):
629 self.db.issue.create(**issue)
630 self.db.commit()
631 return self.assertEqual, self.db.issue.filter
633 def testFilteringID(self):
634 ae, filt = self.filteringSetup()
635 ae(filt(None, {'id': '1'}, ('+','id'), (None,None)), ['1'])
637 def testFilteringString(self):
638 ae, filt = self.filteringSetup()
639 ae(filt(None, {'title': 'issue one'}, ('+','id'), (None,None)), ['1'])
640 ae(filt(None, {'title': 'issue'}, ('+','id'), (None,None)),
641 ['1','2','3'])
643 def testFilteringLink(self):
644 ae, filt = self.filteringSetup()
645 ae(filt(None, {'status': '1'}, ('+','id'), (None,None)), ['2','3'])
647 def testFilteringMultilink(self):
648 ae, filt = self.filteringSetup()
649 ae(filt(None, {'nosy': '2'}, ('+','id'), (None,None)), ['3'])
651 def testFilteringMany(self):
652 ae, filt = self.filteringSetup()
653 ae(filt(None, {'nosy': '2', 'status': '1'}, ('+','id'), (None,None)),
654 ['3'])
656 def testNode1(self):
657 node1 = self.db.user.getnode('1')
658 name = node1.username
659 self.db.user.set('1', username = name+name)
660 name1 = node1.username
661 self.db.rollback()
662 node2 = self.db.user.getnode('1')
663 self.assertEqual(name, node1.username)
664 self.assertEqual(name, node2.username)
666 def testNode2(self):
667 node1 = Node(self.db.user, '1')
668 name = node1.username
669 self.db.user.set('1', username = name+name)
670 name1 = node1.username
671 self.db.rollback()
672 node2 = Node(self.db.user, '1')
673 self.assertEqual(name, node1.username)
674 self.assertEqual(name, node2.username)
677 # TODO test auditors and reactors
679 class anydbmReadOnlyDBTestCase(MyTestCase):
680 def setUp(self):
681 from roundup.backends import anydbm
682 # remove previous test, ignore errors
683 if os.path.exists(config.DATABASE):
684 shutil.rmtree(config.DATABASE)
685 os.makedirs(config.DATABASE + '/files')
686 db = anydbm.Database(config, 'admin')
687 setupSchema(db, 1, anydbm)
688 db.close()
689 self.db = anydbm.Database(config)
690 setupSchema(self.db, 0, anydbm)
692 def testExceptions(self):
693 # this tests the exceptions that should be raised
694 ar = self.assertRaises
696 # this tests the exceptions that should be raised
697 ar(DatabaseError, self.db.status.create, name="foo")
698 ar(DatabaseError, self.db.status.set, '1', name="foo")
699 ar(DatabaseError, self.db.status.retire, '1')
702 class bsddbDBTestCase(anydbmDBTestCase):
703 def setUp(self):
704 from roundup.backends import bsddb
705 # remove previous test, ignore errors
706 if os.path.exists(config.DATABASE):
707 shutil.rmtree(config.DATABASE)
708 os.makedirs(config.DATABASE + '/files')
709 self.db = bsddb.Database(config, 'admin')
710 setupSchema(self.db, 1, bsddb)
712 class bsddbReadOnlyDBTestCase(anydbmReadOnlyDBTestCase):
713 def setUp(self):
714 from roundup.backends import bsddb
715 # remove previous test, ignore errors
716 if os.path.exists(config.DATABASE):
717 shutil.rmtree(config.DATABASE)
718 os.makedirs(config.DATABASE + '/files')
719 db = bsddb.Database(config, 'admin')
720 setupSchema(db, 1, bsddb)
721 db.close()
722 self.db = bsddb.Database(config)
723 setupSchema(self.db, 0, bsddb)
726 class bsddb3DBTestCase(anydbmDBTestCase):
727 def setUp(self):
728 from roundup.backends import bsddb3
729 # remove previous test, ignore errors
730 if os.path.exists(config.DATABASE):
731 shutil.rmtree(config.DATABASE)
732 os.makedirs(config.DATABASE + '/files')
733 self.db = bsddb3.Database(config, 'admin')
734 setupSchema(self.db, 1, bsddb3)
736 class bsddb3ReadOnlyDBTestCase(anydbmReadOnlyDBTestCase):
737 def setUp(self):
738 from roundup.backends import bsddb3
739 # remove previous test, ignore errors
740 if os.path.exists(config.DATABASE):
741 shutil.rmtree(config.DATABASE)
742 os.makedirs(config.DATABASE + '/files')
743 db = bsddb3.Database(config, 'admin')
744 setupSchema(db, 1, bsddb3)
745 db.close()
746 self.db = bsddb3.Database(config)
747 setupSchema(self.db, 0, bsddb3)
750 class gadflyDBTestCase(anydbmDBTestCase):
751 ''' Gadfly doesn't support multiple connections to the one local
752 database
753 '''
754 def setUp(self):
755 from roundup.backends import gadfly
756 # remove previous test, ignore errors
757 if os.path.exists(config.DATABASE):
758 shutil.rmtree(config.DATABASE)
759 config.GADFLY_DATABASE = ('test', config.DATABASE)
760 os.makedirs(config.DATABASE + '/files')
761 self.db = gadfly.Database(config, 'admin')
762 setupSchema(self.db, 1, gadfly)
764 def testFilteringString(self):
765 ae, filt = self.filteringSetup()
766 ae(filt(None, {'title': 'issue one'}, ('+','id'), (None,None)), ['1'])
767 # XXX gadfly can't do substring LIKE searches
768 #ae(filt(None, {'title': 'issue'}, ('+','id'), (None,None)),
769 # ['1','2','3'])
771 class gadflyReadOnlyDBTestCase(anydbmReadOnlyDBTestCase):
772 def setUp(self):
773 from roundup.backends import gadfly
774 # remove previous test, ignore errors
775 if os.path.exists(config.DATABASE):
776 shutil.rmtree(config.DATABASE)
777 config.GADFLY_DATABASE = ('test', config.DATABASE)
778 os.makedirs(config.DATABASE + '/files')
779 db = gadfly.Database(config, 'admin')
780 setupSchema(db, 1, gadfly)
781 db.close()
782 self.db = gadfly.Database(config)
783 setupSchema(self.db, 0, gadfly)
785 class mysqlDBTestCase(anydbmDBTestCase):
786 def setUp(self):
787 from roundup.backends import mysql
788 # remove previous test, ignore errors
789 if os.path.exists(config.DATABASE):
790 shutil.rmtree(config.DATABASE)
791 os.makedirs(config.DATABASE + '/files')
792 # open database for testing
793 self.db = mysql.Database(config, 'admin')
794 setupSchema(self.db, 1, mysql)
796 def tearDown(self):
797 from roundup.backends import mysql
798 self.db.close()
799 mysql.Database.nuke(config)
801 class mysqlReadOnlyDBTestCase(anydbmReadOnlyDBTestCase):
802 def setUp(self):
803 from roundup.backends import mysql
804 # remove previous test, ignore errors
805 if os.path.exists(config.DATABASE):
806 shutil.rmtree(config.DATABASE)
807 os.makedirs(config.DATABASE + '/files')
808 self.db = mysql.Database(config)
809 setupSchema(self.db, 0, mysql)
811 def tearDown(self):
812 from roundup.backends import mysql
813 self.db.close()
814 mysql.Database.nuke(config)
816 class sqliteDBTestCase(anydbmDBTestCase):
817 def setUp(self):
818 from roundup.backends import sqlite
819 # remove previous test, ignore errors
820 if os.path.exists(config.DATABASE):
821 shutil.rmtree(config.DATABASE)
822 os.makedirs(config.DATABASE + '/files')
823 self.db = sqlite.Database(config, 'admin')
824 setupSchema(self.db, 1, sqlite)
826 class sqliteReadOnlyDBTestCase(anydbmReadOnlyDBTestCase):
827 def setUp(self):
828 from roundup.backends import sqlite
829 # remove previous test, ignore errors
830 if os.path.exists(config.DATABASE):
831 shutil.rmtree(config.DATABASE)
832 os.makedirs(config.DATABASE + '/files')
833 db = sqlite.Database(config, 'admin')
834 setupSchema(db, 1, sqlite)
835 db.close()
836 self.db = sqlite.Database(config)
837 setupSchema(self.db, 0, sqlite)
840 class metakitDBTestCase(anydbmDBTestCase):
841 def setUp(self):
842 from roundup.backends import metakit
843 import weakref
844 metakit._instances = weakref.WeakValueDictionary()
845 # remove previous test, ignore errors
846 if os.path.exists(config.DATABASE):
847 shutil.rmtree(config.DATABASE)
848 os.makedirs(config.DATABASE + '/files')
849 self.db = metakit.Database(config, 'admin')
850 setupSchema(self.db, 1, metakit)
852 def testTransactions(self):
853 # remember the number of items we started
854 num_issues = len(self.db.issue.list())
855 self.db.issue.create(title="don't commit me!", status='1')
856 self.assertNotEqual(num_issues, len(self.db.issue.list()))
857 self.db.rollback()
858 self.assertEqual(num_issues, len(self.db.issue.list()))
859 self.db.issue.create(title="please commit me!", status='1')
860 self.assertNotEqual(num_issues, len(self.db.issue.list()))
861 self.db.commit()
862 self.assertNotEqual(num_issues, len(self.db.issue.list()))
863 self.db.rollback()
864 self.assertNotEqual(num_issues, len(self.db.issue.list()))
865 self.db.file.create(name="test", type="text/plain", content="hi")
866 self.db.rollback()
867 num_files = len(self.db.file.list())
868 for i in range(10):
869 self.db.file.create(name="test", type="text/plain",
870 content="hi %d"%(i))
871 self.db.commit()
872 # TODO: would be good to be able to ensure the file is not on disk after
873 # a rollback...
874 num_files2 = len(self.db.file.list())
875 self.assertNotEqual(num_files, num_files2)
876 self.db.file.create(name="test", type="text/plain", content="hi")
877 num_rfiles = len(os.listdir(self.db.config.DATABASE + '/files/file/0'))
878 self.db.rollback()
879 num_rfiles2 = len(os.listdir(self.db.config.DATABASE + '/files/file/0'))
880 self.assertEqual(num_files2, len(self.db.file.list()))
881 self.assertEqual(num_rfiles2, num_rfiles-1)
883 def testBooleanUnset(self):
884 # XXX: metakit can't unset Booleans :(
885 nid = self.db.user.create(username='foo', assignable=1)
886 self.db.user.set(nid, assignable=None)
887 self.assertEqual(self.db.user.get(nid, "assignable"), 0)
889 def testNumberUnset(self):
890 # XXX: metakit can't unset Numbers :(
891 nid = self.db.user.create(username='foo', age=1)
892 self.db.user.set(nid, age=None)
893 self.assertEqual(self.db.user.get(nid, "age"), 0)
895 class metakitReadOnlyDBTestCase(anydbmReadOnlyDBTestCase):
896 def setUp(self):
897 from roundup.backends import metakit
898 import weakref
899 metakit._instances = weakref.WeakValueDictionary()
900 # remove previous test, ignore errors
901 if os.path.exists(config.DATABASE):
902 shutil.rmtree(config.DATABASE)
903 os.makedirs(config.DATABASE + '/files')
904 db = metakit.Database(config, 'admin')
905 setupSchema(db, 1, metakit)
906 db.close()
907 self.db = metakit.Database(config)
908 setupSchema(self.db, 0, metakit)
910 def suite():
911 l = [
912 unittest.makeSuite(anydbmDBTestCase, 'test'),
913 unittest.makeSuite(anydbmReadOnlyDBTestCase, 'test')
914 ]
915 # return unittest.TestSuite(l)
917 from roundup import backends
918 p = []
919 # if hasattr(backends, 'mysql'):
920 # from roundup.backends import mysql
921 # try:
922 # # Check if we can run mysql tests
923 # import MySQLdb
924 # db = mysql.Database(nodbconfig, 'admin')
925 # db.conn.select_db(config.MYSQL_DBNAME)
926 # db.sql("SHOW TABLES");
927 # tables = db.sql_fetchall()
928 # if tables:
929 # # Database should be empty. We don't dare to delete any data
930 # raise DatabaseError, "(Database %s contains tables)" % config.MYSQL_DBNAME
931 # db.sql("DROP DATABASE %s" % config.MYSQL_DBNAME)
932 # db.sql("CREATE DATABASE %s" % config.MYSQL_DBNAME)
933 # db.close()
934 # except (MySQLdb.ProgrammingError, DatabaseError), msg:
935 # print "Warning! Mysql tests will not be performed", msg
936 # print "See doc/mysql.txt for more details."
937 # else:
938 # p.append('mysql')
939 # l.append(unittest.makeSuite(mysqlDBTestCase, 'test'))
940 # l.append(unittest.makeSuite(mysqlReadOnlyDBTestCase, 'test'))
941 #return unittest.TestSuite(l)
943 if hasattr(backends, 'gadfly'):
944 p.append('gadfly')
945 l.append(unittest.makeSuite(gadflyDBTestCase, 'test'))
946 l.append(unittest.makeSuite(gadflyReadOnlyDBTestCase, 'test'))
948 if hasattr(backends, 'sqlite'):
949 p.append('sqlite')
950 l.append(unittest.makeSuite(sqliteDBTestCase, 'test'))
951 l.append(unittest.makeSuite(sqliteReadOnlyDBTestCase, 'test'))
953 if hasattr(backends, 'bsddb'):
954 p.append('bsddb')
955 l.append(unittest.makeSuite(bsddbDBTestCase, 'test'))
956 l.append(unittest.makeSuite(bsddbReadOnlyDBTestCase, 'test'))
958 if hasattr(backends, 'bsddb3'):
959 p.append('bsddb3')
960 l.append(unittest.makeSuite(bsddb3DBTestCase, 'test'))
961 l.append(unittest.makeSuite(bsddb3ReadOnlyDBTestCase, 'test'))
963 if hasattr(backends, 'metakit'):
964 p.append('metakit')
965 l.append(unittest.makeSuite(metakitDBTestCase, 'test'))
966 l.append(unittest.makeSuite(metakitReadOnlyDBTestCase, 'test'))
968 print 'running %s backend tests'%(', '.join(p))
969 return unittest.TestSuite(l)
971 # vim: set filetype=python ts=4 sw=4 et si