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.88 2003-04-22 20:53:55 kedder 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 testJournalPreCommit(self):
538 id = self.db.user.create(username="mary")
539 self.assertEqual(len(self.db.getjournal('user', id)), 1)
540 self.db.commit()
542 def testPack(self):
543 id = self.db.issue.create(title="spam", status='1')
544 self.db.commit()
545 self.db.issue.set(id, status='2')
546 self.db.commit()
548 # sleep for at least a second, then get a date to pack at
549 time.sleep(1)
550 pack_before = date.Date('.')
552 # wait another second and add one more entry
553 time.sleep(1)
554 self.db.issue.set(id, status='3')
555 self.db.commit()
556 jlen = len(self.db.getjournal('issue', id))
558 # pack
559 self.db.pack(pack_before)
561 # we should have the create and last set entries now
562 self.assertEqual(jlen-1, len(self.db.getjournal('issue', id)))
564 def testSearching(self):
565 self.db.file.create(content='hello', type="text/plain")
566 self.db.file.create(content='world', type="text/frozz",
567 comment='blah blah')
568 self.db.issue.create(files=['1', '2'], title="flebble plop")
569 self.db.issue.create(title="flebble frooz")
570 self.db.commit()
571 self.assertEquals(self.db.indexer.search(['hello'], self.db.issue),
572 {'1': {'files': ['1']}})
573 self.assertEquals(self.db.indexer.search(['world'], self.db.issue), {})
574 self.assertEquals(self.db.indexer.search(['frooz'], self.db.issue),
575 {'2': {}})
576 self.assertEquals(self.db.indexer.search(['flebble'], self.db.issue),
577 {'2': {}, '1': {}})
579 def testReindexing(self):
580 self.db.issue.create(title="frooz")
581 self.db.commit()
582 self.assertEquals(self.db.indexer.search(['frooz'], self.db.issue),
583 {'1': {}})
584 self.db.issue.set('1', title="dooble")
585 self.db.commit()
586 self.assertEquals(self.db.indexer.search(['dooble'], self.db.issue),
587 {'1': {}})
588 self.assertEquals(self.db.indexer.search(['frooz'], self.db.issue), {})
590 def testForcedReindexing(self):
591 self.db.issue.create(title="flebble frooz")
592 self.db.commit()
593 self.assertEquals(self.db.indexer.search(['flebble'], self.db.issue),
594 {'1': {}})
595 self.db.indexer.quiet = 1
596 self.db.indexer.force_reindex()
597 self.db.post_init()
598 self.db.indexer.quiet = 9
599 self.assertEquals(self.db.indexer.search(['flebble'], self.db.issue),
600 {'1': {}})
602 #
603 # searching tests follow
604 #
605 def testFind(self):
606 self.db.user.create(username='test')
607 ids = []
608 ids.append(self.db.issue.create(status="1", nosy=['1']))
609 oddid = self.db.issue.create(status="2", nosy=['2'], assignedto='2')
610 ids.append(self.db.issue.create(status="1", nosy=['1','2']))
611 self.db.issue.create(status="3", nosy=['1'], assignedto='1')
612 ids.sort()
614 # should match first and third
615 got = self.db.issue.find(status='1')
616 got.sort()
617 self.assertEqual(got, ids)
619 # none
620 self.assertEqual(self.db.issue.find(status='4'), [])
622 # should match first and third
623 got = self.db.issue.find(assignedto=None)
624 got.sort()
625 self.assertEqual(got, ids)
627 # should match first three
628 got = self.db.issue.find(status='1', nosy='2')
629 got.sort()
630 ids.append(oddid)
631 ids.sort()
632 self.assertEqual(got, ids)
634 # none
635 self.assertEqual(self.db.issue.find(status='4', nosy='3'), [])
637 def testStringFind(self):
638 ids = []
639 ids.append(self.db.issue.create(title="spam"))
640 self.db.issue.create(title="not spam")
641 ids.append(self.db.issue.create(title="spam"))
642 ids.sort()
643 got = self.db.issue.stringFind(title='spam')
644 got.sort()
645 self.assertEqual(got, ids)
646 self.assertEqual(self.db.issue.stringFind(title='fubar'), [])
648 def filteringSetup(self):
649 for user in (
650 {'username': 'bleep'},
651 {'username': 'blop'},
652 {'username': 'blorp'}):
653 self.db.user.create(**user)
654 iss = self.db.issue
655 for issue in (
656 {'title': 'issue one', 'status': '2',
657 'foo': date.Interval('1:10'),
658 'deadline': date.Date('2003-01-01.00:00')},
659 {'title': 'issue two', 'status': '1',
660 'foo': date.Interval('1d'),
661 'deadline': date.Date('2003-02-16.22:50')},
662 {'title': 'issue three', 'status': '1',
663 'nosy': ['1','2'], 'deadline': date.Date('2003-02-18')},
664 {'title': 'non four', 'status': '3',
665 'foo': date.Interval('0:10'),
666 'nosy': ['1'], 'deadline': date.Date('2004-03-08')}):
667 self.db.issue.create(**issue)
668 self.db.commit()
669 return self.assertEqual, self.db.issue.filter
671 def testFilteringID(self):
672 ae, filt = self.filteringSetup()
673 ae(filt(None, {'id': '1'}, ('+','id'), (None,None)), ['1'])
675 def testFilteringString(self):
676 ae, filt = self.filteringSetup()
677 ae(filt(None, {'title': 'issue one'}, ('+','id'), (None,None)), ['1'])
678 ae(filt(None, {'title': 'issue'}, ('+','id'), (None,None)),
679 ['1','2','3'])
681 def testFilteringLink(self):
682 ae, filt = self.filteringSetup()
683 ae(filt(None, {'status': '1'}, ('+','id'), (None,None)), ['2','3'])
685 def testFilteringMultilink(self):
686 ae, filt = self.filteringSetup()
687 ae(filt(None, {'nosy': '2'}, ('+','id'), (None,None)), ['3'])
688 ae(filt(None, {'nosy': '-1'}, ('+','id'), (None,None)), ['1', '2'])
690 def testFilteringMany(self):
691 ae, filt = self.filteringSetup()
692 ae(filt(None, {'nosy': '2', 'status': '1'}, ('+','id'), (None,None)),
693 ['3'])
695 def testFilteringRange(self):
696 ae, filt = self.filteringSetup()
697 # Date ranges
698 ae(filt(None, {'deadline': 'from 2003-02-10 to 2003-02-23'}), ['2','3'])
699 ae(filt(None, {'deadline': '2003-02-10; 2003-02-23'}), ['2','3'])
700 ae(filt(None, {'deadline': '; 2003-02-16'}), ['1'])
701 # Lets assume people won't invent a time machine, otherwise this test
702 # may fail :)
703 ae(filt(None, {'deadline': 'from 2003-02-16'}), ['2', '3', '4'])
704 ae(filt(None, {'deadline': '2003-02-16;'}), ['2', '3', '4'])
705 # year and month granularity
706 ae(filt(None, {'deadline': '2002'}), [])
707 ae(filt(None, {'deadline': '2003'}), ['1', '2', '3'])
708 ae(filt(None, {'deadline': '2004'}), ['4'])
709 ae(filt(None, {'deadline': '2003-02'}), ['2', '3'])
710 ae(filt(None, {'deadline': '2003-03'}), [])
711 ae(filt(None, {'deadline': '2003-02-16'}), ['2'])
712 ae(filt(None, {'deadline': '2003-02-17'}), [])
713 # Interval ranges
714 ae(filt(None, {'foo': 'from 0:50 to 2:00'}), ['1'])
715 ae(filt(None, {'foo': 'from 0:50 to 1d 2:00'}), ['1', '2'])
716 ae(filt(None, {'foo': 'from 5:50'}), ['2'])
717 ae(filt(None, {'foo': 'to 0:05'}), [])
719 def testFilteringIntervalSort(self):
720 ae, filt = self.filteringSetup()
721 # ascending should sort None, 1:10, 1d
722 ae(filt(None, {}, ('+','foo'), (None,None)), ['3', '4', '1', '2'])
723 # descending should sort 1d, 1:10, None
724 ae(filt(None, {}, ('-','foo'), (None,None)), ['2', '1', '4', '3'])
726 # XXX add sorting tests for other types
727 # XXX test auditors and reactors
729 class anydbmReadOnlyDBTestCase(MyTestCase):
730 def setUp(self):
731 from roundup.backends import anydbm
732 # remove previous test, ignore errors
733 if os.path.exists(config.DATABASE):
734 shutil.rmtree(config.DATABASE)
735 os.makedirs(config.DATABASE + '/files')
736 db = anydbm.Database(config, 'admin')
737 setupSchema(db, 1, anydbm)
738 db.close()
739 self.db = anydbm.Database(config)
740 setupSchema(self.db, 0, anydbm)
742 def testExceptions(self):
743 # this tests the exceptions that should be raised
744 ar = self.assertRaises
746 # this tests the exceptions that should be raised
747 ar(DatabaseError, self.db.status.create, name="foo")
748 ar(DatabaseError, self.db.status.set, '1', name="foo")
749 ar(DatabaseError, self.db.status.retire, '1')
752 class bsddbDBTestCase(anydbmDBTestCase):
753 def setUp(self):
754 from roundup.backends import bsddb
755 # remove previous test, ignore errors
756 if os.path.exists(config.DATABASE):
757 shutil.rmtree(config.DATABASE)
758 os.makedirs(config.DATABASE + '/files')
759 self.db = bsddb.Database(config, 'admin')
760 setupSchema(self.db, 1, bsddb)
762 class bsddbReadOnlyDBTestCase(anydbmReadOnlyDBTestCase):
763 def setUp(self):
764 from roundup.backends import bsddb
765 # remove previous test, ignore errors
766 if os.path.exists(config.DATABASE):
767 shutil.rmtree(config.DATABASE)
768 os.makedirs(config.DATABASE + '/files')
769 db = bsddb.Database(config, 'admin')
770 setupSchema(db, 1, bsddb)
771 db.close()
772 self.db = bsddb.Database(config)
773 setupSchema(self.db, 0, bsddb)
776 class bsddb3DBTestCase(anydbmDBTestCase):
777 def setUp(self):
778 from roundup.backends import bsddb3
779 # remove previous test, ignore errors
780 if os.path.exists(config.DATABASE):
781 shutil.rmtree(config.DATABASE)
782 os.makedirs(config.DATABASE + '/files')
783 self.db = bsddb3.Database(config, 'admin')
784 setupSchema(self.db, 1, bsddb3)
786 class bsddb3ReadOnlyDBTestCase(anydbmReadOnlyDBTestCase):
787 def setUp(self):
788 from roundup.backends import bsddb3
789 # remove previous test, ignore errors
790 if os.path.exists(config.DATABASE):
791 shutil.rmtree(config.DATABASE)
792 os.makedirs(config.DATABASE + '/files')
793 db = bsddb3.Database(config, 'admin')
794 setupSchema(db, 1, bsddb3)
795 db.close()
796 self.db = bsddb3.Database(config)
797 setupSchema(self.db, 0, bsddb3)
800 class mysqlDBTestCase(anydbmDBTestCase):
801 def setUp(self):
802 from roundup.backends import mysql
803 # remove previous test, ignore errors
804 if os.path.exists(config.DATABASE):
805 shutil.rmtree(config.DATABASE)
806 os.makedirs(config.DATABASE + '/files')
807 # open database for testing
808 self.db = mysql.Database(config, 'admin')
809 setupSchema(self.db, 1, mysql)
811 def tearDown(self):
812 from roundup.backends import mysql
813 self.db.close()
814 mysql.Database.nuke(config)
816 class mysqlReadOnlyDBTestCase(anydbmReadOnlyDBTestCase):
817 def setUp(self):
818 from roundup.backends import mysql
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 = mysql.Database(config)
824 setupSchema(self.db, 0, mysql)
826 def tearDown(self):
827 from roundup.backends import mysql
828 self.db.close()
829 mysql.Database.nuke(config)
831 class sqliteDBTestCase(anydbmDBTestCase):
832 def setUp(self):
833 from roundup.backends import sqlite
834 # remove previous test, ignore errors
835 if os.path.exists(config.DATABASE):
836 shutil.rmtree(config.DATABASE)
837 os.makedirs(config.DATABASE + '/files')
838 self.db = sqlite.Database(config, 'admin')
839 setupSchema(self.db, 1, sqlite)
841 class sqliteReadOnlyDBTestCase(anydbmReadOnlyDBTestCase):
842 def setUp(self):
843 from roundup.backends import sqlite
844 # remove previous test, ignore errors
845 if os.path.exists(config.DATABASE):
846 shutil.rmtree(config.DATABASE)
847 os.makedirs(config.DATABASE + '/files')
848 db = sqlite.Database(config, 'admin')
849 setupSchema(db, 1, sqlite)
850 db.close()
851 self.db = sqlite.Database(config)
852 setupSchema(self.db, 0, sqlite)
855 class metakitDBTestCase(anydbmDBTestCase):
856 def setUp(self):
857 from roundup.backends import metakit
858 import weakref
859 metakit._instances = weakref.WeakValueDictionary()
860 # remove previous test, ignore errors
861 if os.path.exists(config.DATABASE):
862 shutil.rmtree(config.DATABASE)
863 os.makedirs(config.DATABASE + '/files')
864 self.db = metakit.Database(config, 'admin')
865 setupSchema(self.db, 1, metakit)
867 def testTransactions(self):
868 # remember the number of items we started
869 num_issues = len(self.db.issue.list())
870 self.db.issue.create(title="don't commit me!", status='1')
871 self.assertNotEqual(num_issues, len(self.db.issue.list()))
872 self.db.rollback()
873 self.assertEqual(num_issues, len(self.db.issue.list()))
874 self.db.issue.create(title="please commit me!", status='1')
875 self.assertNotEqual(num_issues, len(self.db.issue.list()))
876 self.db.commit()
877 self.assertNotEqual(num_issues, len(self.db.issue.list()))
878 self.db.rollback()
879 self.assertNotEqual(num_issues, len(self.db.issue.list()))
880 self.db.file.create(name="test", type="text/plain", content="hi")
881 self.db.rollback()
882 num_files = len(self.db.file.list())
883 for i in range(10):
884 self.db.file.create(name="test", type="text/plain",
885 content="hi %d"%(i))
886 self.db.commit()
887 # TODO: would be good to be able to ensure the file is not on disk after
888 # a rollback...
889 num_files2 = len(self.db.file.list())
890 self.assertNotEqual(num_files, num_files2)
891 self.db.file.create(name="test", type="text/plain", content="hi")
892 num_rfiles = len(os.listdir(self.db.config.DATABASE + '/files/file/0'))
893 self.db.rollback()
894 num_rfiles2 = len(os.listdir(self.db.config.DATABASE + '/files/file/0'))
895 self.assertEqual(num_files2, len(self.db.file.list()))
896 self.assertEqual(num_rfiles2, num_rfiles-1)
898 def testBooleanUnset(self):
899 # XXX: metakit can't unset Booleans :(
900 nid = self.db.user.create(username='foo', assignable=1)
901 self.db.user.set(nid, assignable=None)
902 self.assertEqual(self.db.user.get(nid, "assignable"), 0)
904 def testNumberUnset(self):
905 # XXX: metakit can't unset Numbers :(
906 nid = self.db.user.create(username='foo', age=1)
907 self.db.user.set(nid, age=None)
908 self.assertEqual(self.db.user.get(nid, "age"), 0)
910 class metakitReadOnlyDBTestCase(anydbmReadOnlyDBTestCase):
911 def setUp(self):
912 from roundup.backends import metakit
913 import weakref
914 metakit._instances = weakref.WeakValueDictionary()
915 # remove previous test, ignore errors
916 if os.path.exists(config.DATABASE):
917 shutil.rmtree(config.DATABASE)
918 os.makedirs(config.DATABASE + '/files')
919 db = metakit.Database(config, 'admin')
920 setupSchema(db, 1, metakit)
921 db.close()
922 self.db = metakit.Database(config)
923 setupSchema(self.db, 0, metakit)
925 def suite():
926 p = []
928 l = [
929 unittest.makeSuite(anydbmDBTestCase, 'test'),
930 unittest.makeSuite(anydbmReadOnlyDBTestCase, 'test')
931 ]
932 p.append('anydbm')
933 # return unittest.TestSuite(l)
935 from roundup import backends
936 if hasattr(backends, 'mysql'):
937 from roundup.backends import mysql
938 try:
939 # Check if we can run mysql tests
940 import MySQLdb
941 db = mysql.Database(nodbconfig, 'admin')
942 db.conn.select_db(config.MYSQL_DBNAME)
943 db.sql("SHOW TABLES");
944 tables = db.sql_fetchall()
945 if tables:
946 # Database should be empty. We don't dare to delete any data
947 raise DatabaseError, "(Database %s contains tables)" % config.MYSQL_DBNAME
948 db.sql("DROP DATABASE %s" % config.MYSQL_DBNAME)
949 db.sql("CREATE DATABASE %s" % config.MYSQL_DBNAME)
950 db.close()
951 except (MySQLdb.ProgrammingError, DatabaseError), msg:
952 print "Warning! Mysql tests will not be performed", msg
953 print "See doc/mysql.txt for more details."
954 else:
955 p.append('mysql')
956 l.append(unittest.makeSuite(mysqlDBTestCase, 'test'))
957 l.append(unittest.makeSuite(mysqlReadOnlyDBTestCase, 'test'))
958 #return unittest.TestSuite(l)
960 if hasattr(backends, 'sqlite'):
961 p.append('sqlite')
962 l.append(unittest.makeSuite(sqliteDBTestCase, 'test'))
963 l.append(unittest.makeSuite(sqliteReadOnlyDBTestCase, 'test'))
965 if hasattr(backends, 'bsddb'):
966 p.append('bsddb')
967 l.append(unittest.makeSuite(bsddbDBTestCase, 'test'))
968 l.append(unittest.makeSuite(bsddbReadOnlyDBTestCase, 'test'))
970 if hasattr(backends, 'bsddb3'):
971 p.append('bsddb3')
972 l.append(unittest.makeSuite(bsddb3DBTestCase, 'test'))
973 l.append(unittest.makeSuite(bsddb3ReadOnlyDBTestCase, 'test'))
975 if hasattr(backends, 'metakit'):
976 p.append('metakit')
977 l.append(unittest.makeSuite(metakitDBTestCase, 'test'))
978 l.append(unittest.makeSuite(metakitReadOnlyDBTestCase, 'test'))
980 print 'running %s backend tests'%(', '.join(p))
981 return unittest.TestSuite(l)
983 # vim: set filetype=python ts=4 sw=4 et si