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.81 2003-03-22 22:43: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)
259 self.assertEqual(self.db.user.get(nid, 'age'), 1)
260 self.db.user.set(nid, age=0)
261 self.assertEqual(self.db.user.get(nid, 'age'), 0)
263 nid = self.db.user.create(username='bar', age=0)
264 self.assertEqual(self.db.user.get(nid, 'age'), 0)
266 def testNumberUnset(self):
267 nid = self.db.user.create(username='foo', age=1)
268 self.db.user.set(nid, age=None)
269 self.assertEqual(self.db.user.get(nid, "age"), None)
271 def testKeyValue(self):
272 newid = self.db.user.create(username="spam")
273 self.assertEqual(self.db.user.lookup('spam'), newid)
274 self.db.commit()
275 self.assertEqual(self.db.user.lookup('spam'), newid)
276 self.db.user.retire(newid)
277 self.assertRaises(KeyError, self.db.user.lookup, 'spam')
279 # use the key again now that the old is retired
280 newid2 = self.db.user.create(username="spam")
281 self.assertNotEqual(newid, newid2)
282 # try to restore old node. this shouldn't succeed!
283 self.assertRaises(KeyError, self.db.user.restore, newid)
285 def testRetire(self):
286 self.db.issue.create(title="spam", status='1')
287 b = self.db.status.get('1', 'name')
288 a = self.db.status.list()
289 self.db.status.retire('1')
290 # make sure the list is different
291 self.assertNotEqual(a, self.db.status.list())
292 # can still access the node if necessary
293 self.assertEqual(self.db.status.get('1', 'name'), b)
294 self.db.commit()
295 self.assertEqual(self.db.status.get('1', 'name'), b)
296 self.assertNotEqual(a, self.db.status.list())
297 # try to restore retired node
298 self.db.status.restore('1')
299 self.assertEqual(a, self.db.status.list())
301 def testSerialisation(self):
302 nid = self.db.issue.create(title="spam", status='1',
303 deadline=date.Date(), foo=date.Interval('-1d'))
304 self.db.commit()
305 assert isinstance(self.db.issue.get(nid, 'deadline'), date.Date)
306 assert isinstance(self.db.issue.get(nid, 'foo'), date.Interval)
307 uid = self.db.user.create(username="fozzy",
308 password=password.Password('t. bear'))
309 self.db.commit()
310 assert isinstance(self.db.user.get(uid, 'password'), password.Password)
312 def testTransactions(self):
313 # remember the number of items we started
314 num_issues = len(self.db.issue.list())
315 num_files = self.db.numfiles()
316 self.db.issue.create(title="don't commit me!", status='1')
317 self.assertNotEqual(num_issues, len(self.db.issue.list()))
318 self.db.rollback()
319 self.assertEqual(num_issues, len(self.db.issue.list()))
320 self.db.issue.create(title="please commit me!", status='1')
321 self.assertNotEqual(num_issues, len(self.db.issue.list()))
322 self.db.commit()
323 self.assertNotEqual(num_issues, len(self.db.issue.list()))
324 self.db.rollback()
325 self.assertNotEqual(num_issues, len(self.db.issue.list()))
326 self.db.file.create(name="test", type="text/plain", content="hi")
327 self.db.rollback()
328 self.assertEqual(num_files, self.db.numfiles())
329 for i in range(10):
330 self.db.file.create(name="test", type="text/plain",
331 content="hi %d"%(i))
332 self.db.commit()
333 num_files2 = self.db.numfiles()
334 self.assertNotEqual(num_files, num_files2)
335 self.db.file.create(name="test", type="text/plain", content="hi")
336 self.db.rollback()
337 self.assertNotEqual(num_files, self.db.numfiles())
338 self.assertEqual(num_files2, self.db.numfiles())
340 # rollback / cache interaction
341 name1 = self.db.user.get('1', 'username')
342 self.db.user.set('1', username = name1+name1)
343 # get the prop so the info's forced into the cache (if there is one)
344 self.db.user.get('1', 'username')
345 self.db.rollback()
346 name2 = self.db.user.get('1', 'username')
347 self.assertEqual(name1, name2)
349 def testDestroyNoJournalling(self):
350 self.innerTestDestroy(klass=self.db.session)
352 def testDestroyJournalling(self):
353 self.innerTestDestroy(klass=self.db.issue)
355 def innerTestDestroy(self, klass):
356 newid = klass.create(title='Mr Friendly')
357 n = len(klass.list())
358 self.assertEqual(klass.get(newid, 'title'), 'Mr Friendly')
359 klass.destroy(newid)
360 self.assertRaises(IndexError, klass.get, newid, 'title')
361 self.assertNotEqual(len(klass.list()), n)
362 if klass.do_journal:
363 self.assertRaises(IndexError, klass.history, newid)
365 # now with a commit
366 newid = klass.create(title='Mr Friendly')
367 n = len(klass.list())
368 self.assertEqual(klass.get(newid, 'title'), 'Mr Friendly')
369 self.db.commit()
370 klass.destroy(newid)
371 self.assertRaises(IndexError, klass.get, newid, 'title')
372 self.db.commit()
373 self.assertRaises(IndexError, klass.get, newid, 'title')
374 self.assertNotEqual(len(klass.list()), n)
375 if klass.do_journal:
376 self.assertRaises(IndexError, klass.history, newid)
378 # now with a rollback
379 newid = klass.create(title='Mr Friendly')
380 n = len(klass.list())
381 self.assertEqual(klass.get(newid, 'title'), 'Mr Friendly')
382 self.db.commit()
383 klass.destroy(newid)
384 self.assertNotEqual(len(klass.list()), n)
385 self.assertRaises(IndexError, klass.get, newid, 'title')
386 self.db.rollback()
387 self.assertEqual(klass.get(newid, 'title'), 'Mr Friendly')
388 self.assertEqual(len(klass.list()), n)
389 if klass.do_journal:
390 self.assertNotEqual(klass.history(newid), [])
392 def testExceptions(self):
393 # this tests the exceptions that should be raised
394 ar = self.assertRaises
396 #
397 # class create
398 #
399 # string property
400 ar(TypeError, self.db.status.create, name=1)
401 # invalid property name
402 ar(KeyError, self.db.status.create, foo='foo')
403 # key name clash
404 ar(ValueError, self.db.status.create, name='unread')
405 # invalid link index
406 ar(IndexError, self.db.issue.create, title='foo', status='bar')
407 # invalid link value
408 ar(ValueError, self.db.issue.create, title='foo', status=1)
409 # invalid multilink type
410 ar(TypeError, self.db.issue.create, title='foo', status='1',
411 nosy='hello')
412 # invalid multilink index type
413 ar(ValueError, self.db.issue.create, title='foo', status='1',
414 nosy=[1])
415 # invalid multilink index
416 ar(IndexError, self.db.issue.create, title='foo', status='1',
417 nosy=['10'])
419 #
420 # key property
421 #
422 # key must be a String
423 ar(TypeError, self.db.file.setkey, 'fooz')
424 # key must exist
425 ar(KeyError, self.db.file.setkey, 'fubar')
427 #
428 # class get
429 #
430 # invalid node id
431 ar(IndexError, self.db.issue.get, '99', 'title')
432 # invalid property name
433 ar(KeyError, self.db.status.get, '2', 'foo')
435 #
436 # class set
437 #
438 # invalid node id
439 ar(IndexError, self.db.issue.set, '99', title='foo')
440 # invalid property name
441 ar(KeyError, self.db.status.set, '1', foo='foo')
442 # string property
443 ar(TypeError, self.db.status.set, '1', name=1)
444 # key name clash
445 ar(ValueError, self.db.status.set, '2', name='unread')
446 # set up a valid issue for me to work on
447 id = self.db.issue.create(title="spam", status='1')
448 # invalid link index
449 ar(IndexError, self.db.issue.set, id, title='foo', status='bar')
450 # invalid link value
451 ar(ValueError, self.db.issue.set, id, title='foo', status=1)
452 # invalid multilink type
453 ar(TypeError, self.db.issue.set, id, title='foo', status='1',
454 nosy='hello')
455 # invalid multilink index type
456 ar(ValueError, self.db.issue.set, id, title='foo', status='1',
457 nosy=[1])
458 # invalid multilink index
459 ar(IndexError, self.db.issue.set, id, title='foo', status='1',
460 nosy=['10'])
461 # NOTE: the following increment the username to avoid problems
462 # within metakit's backend (it creates the node, and then sets the
463 # info, so the create (and by a fluke the username set) go through
464 # before the age/assignable/etc. set, which raises the exception)
465 # invalid number value
466 ar(TypeError, self.db.user.create, username='foo', age='a')
467 # invalid boolean value
468 ar(TypeError, self.db.user.create, username='foo2', assignable='true')
469 nid = self.db.user.create(username='foo3')
470 # invalid number value
471 ar(TypeError, self.db.user.set, nid, age='a')
472 # invalid boolean value
473 ar(TypeError, self.db.user.set, nid, assignable='true')
475 def testJournals(self):
476 self.db.user.create(username="mary")
477 self.db.user.create(username="pete")
478 self.db.issue.create(title="spam", status='1')
479 self.db.commit()
481 # journal entry for issue create
482 journal = self.db.getjournal('issue', '1')
483 self.assertEqual(1, len(journal))
484 (nodeid, date_stamp, journaltag, action, params) = journal[0]
485 self.assertEqual(nodeid, '1')
486 self.assertEqual(journaltag, self.db.user.lookup('admin'))
487 self.assertEqual(action, 'create')
488 keys = params.keys()
489 keys.sort()
490 self.assertEqual(keys, [])
492 # journal entry for link
493 journal = self.db.getjournal('user', '1')
494 self.assertEqual(1, len(journal))
495 self.db.issue.set('1', assignedto='1')
496 self.db.commit()
497 journal = self.db.getjournal('user', '1')
498 self.assertEqual(2, len(journal))
499 (nodeid, date_stamp, journaltag, action, params) = journal[1]
500 self.assertEqual('1', nodeid)
501 self.assertEqual('1', journaltag)
502 self.assertEqual('link', action)
503 self.assertEqual(('issue', '1', 'assignedto'), params)
505 # journal entry for unlink
506 self.db.issue.set('1', assignedto='2')
507 self.db.commit()
508 journal = self.db.getjournal('user', '1')
509 self.assertEqual(3, len(journal))
510 (nodeid, date_stamp, journaltag, action, params) = journal[2]
511 self.assertEqual('1', nodeid)
512 self.assertEqual('1', journaltag)
513 self.assertEqual('unlink', action)
514 self.assertEqual(('issue', '1', 'assignedto'), params)
516 # test disabling journalling
517 # ... get the last entry
518 time.sleep(1)
519 entry = self.db.getjournal('issue', '1')[-1]
520 (x, date_stamp, x, x, x) = entry
521 self.db.issue.disableJournalling()
522 self.db.issue.set('1', title='hello world')
523 self.db.commit()
524 entry = self.db.getjournal('issue', '1')[-1]
525 (x, date_stamp2, x, x, x) = entry
526 # see if the change was journalled when it shouldn't have been
527 self.assertEqual(date_stamp, date_stamp2)
528 time.sleep(1)
529 self.db.issue.enableJournalling()
530 self.db.issue.set('1', title='hello world 2')
531 self.db.commit()
532 entry = self.db.getjournal('issue', '1')[-1]
533 (x, date_stamp2, x, x, x) = entry
534 # see if the change was journalled
535 self.assertNotEqual(date_stamp, date_stamp2)
537 def testPack(self):
538 id = self.db.issue.create(title="spam", status='1')
539 self.db.commit()
540 self.db.issue.set(id, status='2')
541 self.db.commit()
543 # sleep for at least a second, then get a date to pack at
544 time.sleep(1)
545 pack_before = date.Date('.')
547 # wait another second and add one more entry
548 time.sleep(1)
549 self.db.issue.set(id, status='3')
550 self.db.commit()
551 jlen = len(self.db.getjournal('issue', id))
553 # pack
554 self.db.pack(pack_before)
556 # we should have the create and last set entries now
557 self.assertEqual(jlen-1, len(self.db.getjournal('issue', id)))
559 def testSearching(self):
560 self.db.file.create(content='hello', type="text/plain")
561 self.db.file.create(content='world', type="text/frozz",
562 comment='blah blah')
563 self.db.issue.create(files=['1', '2'], title="flebble plop")
564 self.db.issue.create(title="flebble frooz")
565 self.db.commit()
566 self.assertEquals(self.db.indexer.search(['hello'], self.db.issue),
567 {'1': {'files': ['1']}})
568 self.assertEquals(self.db.indexer.search(['world'], self.db.issue), {})
569 self.assertEquals(self.db.indexer.search(['frooz'], self.db.issue),
570 {'2': {}})
571 self.assertEquals(self.db.indexer.search(['flebble'], self.db.issue),
572 {'2': {}, '1': {}})
574 def testReindexing(self):
575 self.db.issue.create(title="frooz")
576 self.db.commit()
577 self.assertEquals(self.db.indexer.search(['frooz'], self.db.issue),
578 {'1': {}})
579 self.db.issue.set('1', title="dooble")
580 self.db.commit()
581 self.assertEquals(self.db.indexer.search(['dooble'], self.db.issue),
582 {'1': {}})
583 self.assertEquals(self.db.indexer.search(['frooz'], self.db.issue), {})
585 def testForcedReindexing(self):
586 self.db.issue.create(title="flebble frooz")
587 self.db.commit()
588 self.assertEquals(self.db.indexer.search(['flebble'], self.db.issue),
589 {'1': {}})
590 self.db.indexer.quiet = 1
591 self.db.indexer.force_reindex()
592 self.db.post_init()
593 self.db.indexer.quiet = 9
594 self.assertEquals(self.db.indexer.search(['flebble'], self.db.issue),
595 {'1': {}})
597 #
598 # searching tests follow
599 #
600 def testFind(self):
601 self.db.user.create(username='test')
602 ids = []
603 ids.append(self.db.issue.create(status="1", nosy=['1']))
604 oddid = self.db.issue.create(status="2", nosy=['2'])
605 ids.append(self.db.issue.create(status="1", nosy=['1','2']))
606 self.db.issue.create(status="3", nosy=['1'])
607 ids.sort()
609 # should match first and third
610 got = self.db.issue.find(status='1')
611 got.sort()
612 self.assertEqual(got, ids)
614 # none
615 self.assertEqual(self.db.issue.find(status='4'), [])
617 # should match first three
618 got = self.db.issue.find(status='1', nosy='2')
619 got.sort()
620 ids.append(oddid)
621 ids.sort()
622 self.assertEqual(got, ids)
624 # none
625 self.assertEqual(self.db.issue.find(status='4', nosy='3'), [])
627 def testStringFind(self):
628 ids = []
629 ids.append(self.db.issue.create(title="spam"))
630 self.db.issue.create(title="not spam")
631 ids.append(self.db.issue.create(title="spam"))
632 ids.sort()
633 got = self.db.issue.stringFind(title='spam')
634 got.sort()
635 self.assertEqual(got, ids)
636 self.assertEqual(self.db.issue.stringFind(title='fubar'), [])
638 def filteringSetup(self):
639 for user in (
640 {'username': 'bleep'},
641 {'username': 'blop'},
642 {'username': 'blorp'}):
643 self.db.user.create(**user)
644 iss = self.db.issue
645 for issue in (
646 {'title': 'issue one', 'status': '2',
647 'foo': date.Interval('1:10'),
648 'deadline': date.Date('2003-01-01.00:00')},
649 {'title': 'issue two', 'status': '1',
650 'foo': date.Interval('1d'),
651 'deadline': date.Date('2003-02-16.22:50')},
652 {'title': 'issue three', 'status': '1',
653 'nosy': ['1','2'], 'deadline': date.Date('2003-03-08')}):
654 self.db.issue.create(**issue)
655 self.db.commit()
656 return self.assertEqual, self.db.issue.filter
658 def testFilteringID(self):
659 ae, filt = self.filteringSetup()
660 ae(filt(None, {'id': '1'}, ('+','id'), (None,None)), ['1'])
662 def testFilteringString(self):
663 ae, filt = self.filteringSetup()
664 ae(filt(None, {'title': 'issue one'}, ('+','id'), (None,None)), ['1'])
665 ae(filt(None, {'title': 'issue'}, ('+','id'), (None,None)),
666 ['1','2','3'])
668 def testFilteringLink(self):
669 ae, filt = self.filteringSetup()
670 ae(filt(None, {'status': '1'}, ('+','id'), (None,None)), ['2','3'])
672 def testFilteringMultilink(self):
673 ae, filt = self.filteringSetup()
674 ae(filt(None, {'nosy': '2'}, ('+','id'), (None,None)), ['3'])
676 def testFilteringMany(self):
677 ae, filt = self.filteringSetup()
678 ae(filt(None, {'nosy': '2', 'status': '1'}, ('+','id'), (None,None)),
679 ['3'])
681 def testFilteringRange(self):
682 ae, filt = self.filteringSetup()
683 ae(filt(None, {'deadline': 'from 2003-02-10 to 2003-02-23'}), ['2'])
684 ae(filt(None, {'deadline': '2003-02-10; 2003-02-23'}), ['2'])
685 ae(filt(None, {'deadline': '; 2003-02-16'}), ['1'])
686 # Lets assume people won't invent a time machine, otherwise this test
687 # may fail :)
688 ae(filt(None, {'deadline': 'from 2003-02-16'}), ['2', '3'])
689 ae(filt(None, {'deadline': '2003-02-16'}), ['2', '3'])
691 def testFilteringIntervalSort(self):
692 ae, filt = self.filteringSetup()
693 # ascending should sort None, 1:10, 1d
694 ae(filt(None, {}, ('+','foo'), (None,None)), ['3', '1', '2'])
695 # descending should sort 1d, 1:10, None
696 ae(filt(None, {}, ('-','foo'), (None,None)), ['2', '1', '3'])
698 # XXX add sorting tests for other types
699 # XXX test auditors and reactors
701 class anydbmReadOnlyDBTestCase(MyTestCase):
702 def setUp(self):
703 from roundup.backends import anydbm
704 # remove previous test, ignore errors
705 if os.path.exists(config.DATABASE):
706 shutil.rmtree(config.DATABASE)
707 os.makedirs(config.DATABASE + '/files')
708 db = anydbm.Database(config, 'admin')
709 setupSchema(db, 1, anydbm)
710 db.close()
711 self.db = anydbm.Database(config)
712 setupSchema(self.db, 0, anydbm)
714 def testExceptions(self):
715 # this tests the exceptions that should be raised
716 ar = self.assertRaises
718 # this tests the exceptions that should be raised
719 ar(DatabaseError, self.db.status.create, name="foo")
720 ar(DatabaseError, self.db.status.set, '1', name="foo")
721 ar(DatabaseError, self.db.status.retire, '1')
724 class bsddbDBTestCase(anydbmDBTestCase):
725 def setUp(self):
726 from roundup.backends import bsddb
727 # remove previous test, ignore errors
728 if os.path.exists(config.DATABASE):
729 shutil.rmtree(config.DATABASE)
730 os.makedirs(config.DATABASE + '/files')
731 self.db = bsddb.Database(config, 'admin')
732 setupSchema(self.db, 1, bsddb)
734 class bsddbReadOnlyDBTestCase(anydbmReadOnlyDBTestCase):
735 def setUp(self):
736 from roundup.backends import bsddb
737 # remove previous test, ignore errors
738 if os.path.exists(config.DATABASE):
739 shutil.rmtree(config.DATABASE)
740 os.makedirs(config.DATABASE + '/files')
741 db = bsddb.Database(config, 'admin')
742 setupSchema(db, 1, bsddb)
743 db.close()
744 self.db = bsddb.Database(config)
745 setupSchema(self.db, 0, bsddb)
748 class bsddb3DBTestCase(anydbmDBTestCase):
749 def setUp(self):
750 from roundup.backends import bsddb3
751 # remove previous test, ignore errors
752 if os.path.exists(config.DATABASE):
753 shutil.rmtree(config.DATABASE)
754 os.makedirs(config.DATABASE + '/files')
755 self.db = bsddb3.Database(config, 'admin')
756 setupSchema(self.db, 1, bsddb3)
758 class bsddb3ReadOnlyDBTestCase(anydbmReadOnlyDBTestCase):
759 def setUp(self):
760 from roundup.backends import bsddb3
761 # remove previous test, ignore errors
762 if os.path.exists(config.DATABASE):
763 shutil.rmtree(config.DATABASE)
764 os.makedirs(config.DATABASE + '/files')
765 db = bsddb3.Database(config, 'admin')
766 setupSchema(db, 1, bsddb3)
767 db.close()
768 self.db = bsddb3.Database(config)
769 setupSchema(self.db, 0, bsddb3)
772 class mysqlDBTestCase(anydbmDBTestCase):
773 def setUp(self):
774 from roundup.backends import mysql
775 # remove previous test, ignore errors
776 if os.path.exists(config.DATABASE):
777 shutil.rmtree(config.DATABASE)
778 os.makedirs(config.DATABASE + '/files')
779 # open database for testing
780 self.db = mysql.Database(config, 'admin')
781 setupSchema(self.db, 1, mysql)
783 def tearDown(self):
784 from roundup.backends import mysql
785 self.db.close()
786 mysql.Database.nuke(config)
788 class mysqlReadOnlyDBTestCase(anydbmReadOnlyDBTestCase):
789 def setUp(self):
790 from roundup.backends import mysql
791 # remove previous test, ignore errors
792 if os.path.exists(config.DATABASE):
793 shutil.rmtree(config.DATABASE)
794 os.makedirs(config.DATABASE + '/files')
795 self.db = mysql.Database(config)
796 setupSchema(self.db, 0, mysql)
798 def tearDown(self):
799 from roundup.backends import mysql
800 self.db.close()
801 mysql.Database.nuke(config)
803 class sqliteDBTestCase(anydbmDBTestCase):
804 def setUp(self):
805 from roundup.backends import sqlite
806 # remove previous test, ignore errors
807 if os.path.exists(config.DATABASE):
808 shutil.rmtree(config.DATABASE)
809 os.makedirs(config.DATABASE + '/files')
810 self.db = sqlite.Database(config, 'admin')
811 setupSchema(self.db, 1, sqlite)
813 class sqliteReadOnlyDBTestCase(anydbmReadOnlyDBTestCase):
814 def setUp(self):
815 from roundup.backends import sqlite
816 # remove previous test, ignore errors
817 if os.path.exists(config.DATABASE):
818 shutil.rmtree(config.DATABASE)
819 os.makedirs(config.DATABASE + '/files')
820 db = sqlite.Database(config, 'admin')
821 setupSchema(db, 1, sqlite)
822 db.close()
823 self.db = sqlite.Database(config)
824 setupSchema(self.db, 0, sqlite)
827 class metakitDBTestCase(anydbmDBTestCase):
828 def setUp(self):
829 from roundup.backends import metakit
830 import weakref
831 metakit._instances = weakref.WeakValueDictionary()
832 # remove previous test, ignore errors
833 if os.path.exists(config.DATABASE):
834 shutil.rmtree(config.DATABASE)
835 os.makedirs(config.DATABASE + '/files')
836 self.db = metakit.Database(config, 'admin')
837 setupSchema(self.db, 1, metakit)
839 def testTransactions(self):
840 # remember the number of items we started
841 num_issues = len(self.db.issue.list())
842 self.db.issue.create(title="don't commit me!", status='1')
843 self.assertNotEqual(num_issues, len(self.db.issue.list()))
844 self.db.rollback()
845 self.assertEqual(num_issues, len(self.db.issue.list()))
846 self.db.issue.create(title="please commit me!", status='1')
847 self.assertNotEqual(num_issues, len(self.db.issue.list()))
848 self.db.commit()
849 self.assertNotEqual(num_issues, len(self.db.issue.list()))
850 self.db.rollback()
851 self.assertNotEqual(num_issues, len(self.db.issue.list()))
852 self.db.file.create(name="test", type="text/plain", content="hi")
853 self.db.rollback()
854 num_files = len(self.db.file.list())
855 for i in range(10):
856 self.db.file.create(name="test", type="text/plain",
857 content="hi %d"%(i))
858 self.db.commit()
859 # TODO: would be good to be able to ensure the file is not on disk after
860 # a rollback...
861 num_files2 = len(self.db.file.list())
862 self.assertNotEqual(num_files, num_files2)
863 self.db.file.create(name="test", type="text/plain", content="hi")
864 num_rfiles = len(os.listdir(self.db.config.DATABASE + '/files/file/0'))
865 self.db.rollback()
866 num_rfiles2 = len(os.listdir(self.db.config.DATABASE + '/files/file/0'))
867 self.assertEqual(num_files2, len(self.db.file.list()))
868 self.assertEqual(num_rfiles2, num_rfiles-1)
870 def testBooleanUnset(self):
871 # XXX: metakit can't unset Booleans :(
872 nid = self.db.user.create(username='foo', assignable=1)
873 self.db.user.set(nid, assignable=None)
874 self.assertEqual(self.db.user.get(nid, "assignable"), 0)
876 def testNumberUnset(self):
877 # XXX: metakit can't unset Numbers :(
878 nid = self.db.user.create(username='foo', age=1)
879 self.db.user.set(nid, age=None)
880 self.assertEqual(self.db.user.get(nid, "age"), 0)
882 class metakitReadOnlyDBTestCase(anydbmReadOnlyDBTestCase):
883 def setUp(self):
884 from roundup.backends import metakit
885 import weakref
886 metakit._instances = weakref.WeakValueDictionary()
887 # remove previous test, ignore errors
888 if os.path.exists(config.DATABASE):
889 shutil.rmtree(config.DATABASE)
890 os.makedirs(config.DATABASE + '/files')
891 db = metakit.Database(config, 'admin')
892 setupSchema(db, 1, metakit)
893 db.close()
894 self.db = metakit.Database(config)
895 setupSchema(self.db, 0, metakit)
897 def suite():
898 p = []
900 l = [
901 unittest.makeSuite(anydbmDBTestCase, 'test'),
902 unittest.makeSuite(anydbmReadOnlyDBTestCase, 'test')
903 ]
904 p.append('anydbm')
905 # return unittest.TestSuite(l)
907 from roundup import backends
908 if hasattr(backends, 'mysql'):
909 from roundup.backends import mysql
910 try:
911 # Check if we can run mysql tests
912 import MySQLdb
913 db = mysql.Database(nodbconfig, 'admin')
914 db.conn.select_db(config.MYSQL_DBNAME)
915 db.sql("SHOW TABLES");
916 tables = db.sql_fetchall()
917 if tables:
918 # Database should be empty. We don't dare to delete any data
919 raise DatabaseError, "(Database %s contains tables)" % config.MYSQL_DBNAME
920 db.sql("DROP DATABASE %s" % config.MYSQL_DBNAME)
921 db.sql("CREATE DATABASE %s" % config.MYSQL_DBNAME)
922 db.close()
923 except (MySQLdb.ProgrammingError, DatabaseError), msg:
924 print "Warning! Mysql tests will not be performed", msg
925 print "See doc/mysql.txt for more details."
926 else:
927 p.append('mysql')
928 l.append(unittest.makeSuite(mysqlDBTestCase, 'test'))
929 l.append(unittest.makeSuite(mysqlReadOnlyDBTestCase, 'test'))
930 #return unittest.TestSuite(l)
932 if hasattr(backends, 'sqlite'):
933 p.append('sqlite')
934 l.append(unittest.makeSuite(sqliteDBTestCase, 'test'))
935 l.append(unittest.makeSuite(sqliteReadOnlyDBTestCase, 'test'))
937 if hasattr(backends, 'bsddb'):
938 p.append('bsddb')
939 l.append(unittest.makeSuite(bsddbDBTestCase, 'test'))
940 l.append(unittest.makeSuite(bsddbReadOnlyDBTestCase, 'test'))
942 if hasattr(backends, 'bsddb3'):
943 p.append('bsddb3')
944 l.append(unittest.makeSuite(bsddb3DBTestCase, 'test'))
945 l.append(unittest.makeSuite(bsddb3ReadOnlyDBTestCase, 'test'))
947 if hasattr(backends, 'metakit'):
948 p.append('metakit')
949 l.append(unittest.makeSuite(metakitDBTestCase, 'test'))
950 l.append(unittest.makeSuite(metakitReadOnlyDBTestCase, 'test'))
952 print 'running %s backend tests'%(', '.join(p))
953 return unittest.TestSuite(l)
955 # vim: set filetype=python ts=4 sw=4 et si