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.75 2003-03-10 00:22:21 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 # rollback / cache interaction
330 name1 = self.db.user.get('1', 'username')
331 self.db.user.set('1', username = name1+name1)
332 # get the prop so the info's forced into the cache (if there is one)
333 self.db.user.get('1', 'username')
334 self.db.rollback()
335 name2 = self.db.user.get('1', 'username')
336 self.assertEqual(name1, name2)
338 def testDestroyNoJournalling(self):
339 self.innerTestDestroy(klass=self.db.session)
341 def testDestroyJournalling(self):
342 self.innerTestDestroy(klass=self.db.issue)
344 def innerTestDestroy(self, klass):
345 newid = klass.create(title='Mr Friendly')
346 n = len(klass.list())
347 self.assertEqual(klass.get(newid, 'title'), 'Mr Friendly')
348 klass.destroy(newid)
349 self.assertRaises(IndexError, klass.get, newid, 'title')
350 self.assertNotEqual(len(klass.list()), n)
351 if klass.do_journal:
352 self.assertRaises(IndexError, klass.history, newid)
354 # now with a commit
355 newid = klass.create(title='Mr Friendly')
356 n = len(klass.list())
357 self.assertEqual(klass.get(newid, 'title'), 'Mr Friendly')
358 self.db.commit()
359 klass.destroy(newid)
360 self.assertRaises(IndexError, klass.get, newid, 'title')
361 self.db.commit()
362 self.assertRaises(IndexError, klass.get, newid, 'title')
363 self.assertNotEqual(len(klass.list()), n)
364 if klass.do_journal:
365 self.assertRaises(IndexError, klass.history, newid)
367 # now with a rollback
368 newid = klass.create(title='Mr Friendly')
369 n = len(klass.list())
370 self.assertEqual(klass.get(newid, 'title'), 'Mr Friendly')
371 self.db.commit()
372 klass.destroy(newid)
373 self.assertNotEqual(len(klass.list()), n)
374 self.assertRaises(IndexError, klass.get, newid, 'title')
375 self.db.rollback()
376 self.assertEqual(klass.get(newid, 'title'), 'Mr Friendly')
377 self.assertEqual(len(klass.list()), n)
378 if klass.do_journal:
379 self.assertNotEqual(klass.history(newid), [])
381 def testExceptions(self):
382 # this tests the exceptions that should be raised
383 ar = self.assertRaises
385 #
386 # class create
387 #
388 # string property
389 ar(TypeError, self.db.status.create, name=1)
390 # invalid property name
391 ar(KeyError, self.db.status.create, foo='foo')
392 # key name clash
393 ar(ValueError, self.db.status.create, name='unread')
394 # invalid link index
395 ar(IndexError, self.db.issue.create, title='foo', status='bar')
396 # invalid link value
397 ar(ValueError, self.db.issue.create, title='foo', status=1)
398 # invalid multilink type
399 ar(TypeError, self.db.issue.create, title='foo', status='1',
400 nosy='hello')
401 # invalid multilink index type
402 ar(ValueError, self.db.issue.create, title='foo', status='1',
403 nosy=[1])
404 # invalid multilink index
405 ar(IndexError, self.db.issue.create, title='foo', status='1',
406 nosy=['10'])
408 #
409 # key property
410 #
411 # key must be a String
412 ar(TypeError, self.db.file.setkey, 'fooz')
413 # key must exist
414 ar(KeyError, self.db.file.setkey, 'fubar')
416 #
417 # class get
418 #
419 # invalid node id
420 ar(IndexError, self.db.issue.get, '99', 'title')
421 # invalid property name
422 ar(KeyError, self.db.status.get, '2', 'foo')
424 #
425 # class set
426 #
427 # invalid node id
428 ar(IndexError, self.db.issue.set, '99', title='foo')
429 # invalid property name
430 ar(KeyError, self.db.status.set, '1', foo='foo')
431 # string property
432 ar(TypeError, self.db.status.set, '1', name=1)
433 # key name clash
434 ar(ValueError, self.db.status.set, '2', name='unread')
435 # set up a valid issue for me to work on
436 id = self.db.issue.create(title="spam", status='1')
437 # invalid link index
438 ar(IndexError, self.db.issue.set, id, title='foo', status='bar')
439 # invalid link value
440 ar(ValueError, self.db.issue.set, id, title='foo', status=1)
441 # invalid multilink type
442 ar(TypeError, self.db.issue.set, id, title='foo', status='1',
443 nosy='hello')
444 # invalid multilink index type
445 ar(ValueError, self.db.issue.set, id, title='foo', status='1',
446 nosy=[1])
447 # invalid multilink index
448 ar(IndexError, self.db.issue.set, id, title='foo', status='1',
449 nosy=['10'])
450 # NOTE: the following increment the username to avoid problems
451 # within metakit's backend (it creates the node, and then sets the
452 # info, so the create (and by a fluke the username set) go through
453 # before the age/assignable/etc. set, which raises the exception)
454 # invalid number value
455 ar(TypeError, self.db.user.create, username='foo', age='a')
456 # invalid boolean value
457 ar(TypeError, self.db.user.create, username='foo2', assignable='true')
458 nid = self.db.user.create(username='foo3')
459 # invalid number value
460 ar(TypeError, self.db.user.set, nid, age='a')
461 # invalid boolean value
462 ar(TypeError, self.db.user.set, nid, assignable='true')
464 def testJournals(self):
465 self.db.user.create(username="mary")
466 self.db.user.create(username="pete")
467 self.db.issue.create(title="spam", status='1')
468 self.db.commit()
470 # journal entry for issue create
471 journal = self.db.getjournal('issue', '1')
472 self.assertEqual(1, len(journal))
473 (nodeid, date_stamp, journaltag, action, params) = journal[0]
474 self.assertEqual(nodeid, '1')
475 self.assertEqual(journaltag, self.db.user.lookup('admin'))
476 self.assertEqual(action, 'create')
477 keys = params.keys()
478 keys.sort()
479 self.assertEqual(keys, [])
481 # journal entry for link
482 journal = self.db.getjournal('user', '1')
483 self.assertEqual(1, len(journal))
484 self.db.issue.set('1', assignedto='1')
485 self.db.commit()
486 journal = self.db.getjournal('user', '1')
487 self.assertEqual(2, len(journal))
488 (nodeid, date_stamp, journaltag, action, params) = journal[1]
489 self.assertEqual('1', nodeid)
490 self.assertEqual('1', journaltag)
491 self.assertEqual('link', action)
492 self.assertEqual(('issue', '1', 'assignedto'), params)
494 # journal entry for unlink
495 self.db.issue.set('1', assignedto='2')
496 self.db.commit()
497 journal = self.db.getjournal('user', '1')
498 self.assertEqual(3, len(journal))
499 (nodeid, date_stamp, journaltag, action, params) = journal[2]
500 self.assertEqual('1', nodeid)
501 self.assertEqual('1', journaltag)
502 self.assertEqual('unlink', action)
503 self.assertEqual(('issue', '1', 'assignedto'), params)
505 # test disabling journalling
506 # ... get the last entry
507 time.sleep(1)
508 entry = self.db.getjournal('issue', '1')[-1]
509 (x, date_stamp, x, x, x) = entry
510 self.db.issue.disableJournalling()
511 self.db.issue.set('1', title='hello world')
512 self.db.commit()
513 entry = self.db.getjournal('issue', '1')[-1]
514 (x, date_stamp2, x, x, x) = entry
515 # see if the change was journalled when it shouldn't have been
516 self.assertEqual(date_stamp, date_stamp2)
517 time.sleep(1)
518 self.db.issue.enableJournalling()
519 self.db.issue.set('1', title='hello world 2')
520 self.db.commit()
521 entry = self.db.getjournal('issue', '1')[-1]
522 (x, date_stamp2, x, x, x) = entry
523 # see if the change was journalled
524 self.assertNotEqual(date_stamp, date_stamp2)
526 def testPack(self):
527 id = self.db.issue.create(title="spam", status='1')
528 self.db.commit()
529 self.db.issue.set(id, status='2')
530 self.db.commit()
532 # sleep for at least a second, then get a date to pack at
533 time.sleep(1)
534 pack_before = date.Date('.')
536 # wait another second and add one more entry
537 time.sleep(1)
538 self.db.issue.set(id, status='3')
539 self.db.commit()
540 jlen = len(self.db.getjournal('issue', id))
542 # pack
543 self.db.pack(pack_before)
545 # we should have the create and last set entries now
546 self.assertEqual(jlen-1, len(self.db.getjournal('issue', id)))
548 def testSearching(self):
549 self.db.file.create(content='hello', type="text/plain")
550 self.db.file.create(content='world', type="text/frozz",
551 comment='blah blah')
552 self.db.issue.create(files=['1', '2'], title="flebble plop")
553 self.db.issue.create(title="flebble frooz")
554 self.db.commit()
555 self.assertEquals(self.db.indexer.search(['hello'], self.db.issue),
556 {'1': {'files': ['1']}})
557 self.assertEquals(self.db.indexer.search(['world'], self.db.issue), {})
558 self.assertEquals(self.db.indexer.search(['frooz'], self.db.issue),
559 {'2': {}})
560 self.assertEquals(self.db.indexer.search(['flebble'], self.db.issue),
561 {'2': {}, '1': {}})
563 def testReindexing(self):
564 self.db.issue.create(title="frooz")
565 self.db.commit()
566 self.assertEquals(self.db.indexer.search(['frooz'], self.db.issue),
567 {'1': {}})
568 self.db.issue.set('1', title="dooble")
569 self.db.commit()
570 self.assertEquals(self.db.indexer.search(['dooble'], self.db.issue),
571 {'1': {}})
572 self.assertEquals(self.db.indexer.search(['frooz'], self.db.issue), {})
574 def testForcedReindexing(self):
575 self.db.issue.create(title="flebble frooz")
576 self.db.commit()
577 self.assertEquals(self.db.indexer.search(['flebble'], self.db.issue),
578 {'1': {}})
579 self.db.indexer.quiet = 1
580 self.db.indexer.force_reindex()
581 self.db.post_init()
582 self.db.indexer.quiet = 9
583 self.assertEquals(self.db.indexer.search(['flebble'], self.db.issue),
584 {'1': {}})
586 #
587 # searching tests follow
588 #
589 def testFind(self):
590 self.db.user.create(username='test')
591 ids = []
592 ids.append(self.db.issue.create(status="1", nosy=['1']))
593 oddid = self.db.issue.create(status="2", nosy=['2'])
594 ids.append(self.db.issue.create(status="1", nosy=['1','2']))
595 self.db.issue.create(status="3", nosy=['1'])
596 ids.sort()
598 # should match first and third
599 got = self.db.issue.find(status='1')
600 got.sort()
601 self.assertEqual(got, ids)
603 # none
604 self.assertEqual(self.db.issue.find(status='4'), [])
606 # should match first three
607 got = self.db.issue.find(status='1', nosy='2')
608 got.sort()
609 ids.append(oddid)
610 ids.sort()
611 self.assertEqual(got, ids)
613 # none
614 self.assertEqual(self.db.issue.find(status='4', nosy='3'), [])
616 def testStringFind(self):
617 ids = []
618 ids.append(self.db.issue.create(title="spam"))
619 self.db.issue.create(title="not spam")
620 ids.append(self.db.issue.create(title="spam"))
621 ids.sort()
622 got = self.db.issue.stringFind(title='spam')
623 got.sort()
624 self.assertEqual(got, ids)
625 self.assertEqual(self.db.issue.stringFind(title='fubar'), [])
627 def filteringSetup(self):
628 for user in (
629 {'username': 'bleep'},
630 {'username': 'blop'},
631 {'username': 'blorp'}):
632 self.db.user.create(**user)
633 iss = self.db.issue
634 for issue in (
635 {'title': 'issue one', 'status': '2',
636 'foo': date.Interval('1:10')},
637 {'title': 'issue two', 'status': '1',
638 'foo': date.Interval('1d')},
639 {'title': 'issue three', 'status': '1',
640 'nosy': ['1','2']}):
641 self.db.issue.create(**issue)
642 self.db.commit()
643 return self.assertEqual, self.db.issue.filter
645 def testFilteringID(self):
646 ae, filt = self.filteringSetup()
647 ae(filt(None, {'id': '1'}, ('+','id'), (None,None)), ['1'])
649 def testFilteringString(self):
650 ae, filt = self.filteringSetup()
651 ae(filt(None, {'title': 'issue one'}, ('+','id'), (None,None)), ['1'])
652 ae(filt(None, {'title': 'issue'}, ('+','id'), (None,None)),
653 ['1','2','3'])
655 def testFilteringLink(self):
656 ae, filt = self.filteringSetup()
657 ae(filt(None, {'status': '1'}, ('+','id'), (None,None)), ['2','3'])
659 def testFilteringMultilink(self):
660 ae, filt = self.filteringSetup()
661 ae(filt(None, {'nosy': '2'}, ('+','id'), (None,None)), ['3'])
663 def testFilteringMany(self):
664 ae, filt = self.filteringSetup()
665 ae(filt(None, {'nosy': '2', 'status': '1'}, ('+','id'), (None,None)),
666 ['3'])
668 def testFilteringIntervalSort(self):
669 ae, filt = self.filteringSetup()
670 # ascending should sort None, 1:10, 1d
671 ae(filt(None, {}, ('+','foo'), (None,None)), ['3', '1', '2'])
672 # descending should sort 1d, 1:10, None
673 ae(filt(None, {}, ('-','foo'), (None,None)), ['2', '1', '3'])
676 # XXX add sorting tests for other types
677 # XXX 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