Mercurial > p > roundup > code
diff test/db_test_base.py @ 1873:f63aa57386b0
Backend improvements.
- using Zope3's test runner now, allowing GC checks, nicer controls and
coverage analysis
- all RDMBS backends now have indexes on several columns
- added testing of schema mutation, fixed rdbms backends handling of a
couple of cases
- !BETA! added postgresql backend, needs work !BETA!
| author | Richard Jones <richard@users.sourceforge.net> |
|---|---|
| date | Sat, 25 Oct 2003 22:53:26 +0000 |
| parents | |
| children | 043e1b699047 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/db_test_base.py Sat Oct 25 22:53:26 2003 +0000 @@ -0,0 +1,988 @@ +# +# Copyright (c) 2001 Bizar Software Pty Ltd (http://www.bizarsoftware.com.au/) +# This module is free software, and you may redistribute it and/or modify +# under the same terms as Python, so long as this copyright message and +# disclaimer are retained in their original form. +# +# IN NO EVENT SHALL BIZAR SOFTWARE PTY LTD BE LIABLE TO ANY PARTY FOR +# DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING +# OUT OF THE USE OF THIS CODE, EVEN IF THE AUTHOR HAS BEEN ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. +# +# BIZAR SOFTWARE PTY LTD SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, +# BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +# FOR A PARTICULAR PURPOSE. THE CODE PROVIDED HEREUNDER IS ON AN "AS IS" +# BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE, +# SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. +# +# $Id: db_test_base.py,v 1.1 2003-10-25 22:53:26 richard Exp $ + +import unittest, os, shutil, errno, imp, sys, time + +from roundup.hyperdb import String, Password, Link, Multilink, Date, \ + Interval, DatabaseError, Boolean, Number, Node +from roundup import date, password +from roundup import init +from roundup.indexer import Indexer + +def setupSchema(db, create, module): + status = module.Class(db, "status", name=String()) + status.setkey("name") + user = module.Class(db, "user", username=String(), password=Password(), + assignable=Boolean(), age=Number(), roles=String()) + user.setkey("username") + file = module.FileClass(db, "file", name=String(), type=String(), + comment=String(indexme="yes"), fooz=Password()) + issue = module.IssueClass(db, "issue", title=String(indexme="yes"), + status=Link("status"), nosy=Multilink("user"), deadline=Date(), + foo=Interval(), files=Multilink("file"), assignedto=Link('user')) + session = module.Class(db, 'session', title=String()) + session.disableJournalling() + db.post_init() + if create: + user.create(username="admin", roles='Admin') + status.create(name="unread") + status.create(name="in-progress") + status.create(name="testing") + status.create(name="resolved") + db.commit() + +class MyTestCase(unittest.TestCase): + def tearDown(self): + if hasattr(self, 'db'): + self.db.close() + if os.path.exists('_test_dir'): + shutil.rmtree('_test_dir') + +class config: + DATABASE='_test_dir' + MAILHOST = 'localhost' + MAIL_DOMAIN = 'fill.me.in.' + TRACKER_NAME = 'Roundup issue tracker' + TRACKER_EMAIL = 'issue_tracker@%s'%MAIL_DOMAIN + TRACKER_WEB = 'http://some.useful.url/' + ADMIN_EMAIL = 'roundup-admin@%s'%MAIL_DOMAIN + FILTER_POSITION = 'bottom' # one of 'top', 'bottom', 'top and bottom' + ANONYMOUS_ACCESS = 'deny' # either 'deny' or 'allow' + ANONYMOUS_REGISTER = 'deny' # either 'deny' or 'allow' + MESSAGES_TO_AUTHOR = 'no' # either 'yes' or 'no' + EMAIL_SIGNATURE_POSITION = 'bottom' + + # Mysql connection data + MYSQL_DBHOST = 'localhost' + MYSQL_DBUSER = 'rounduptest' + MYSQL_DBPASSWORD = 'rounduptest' + MYSQL_DBNAME = 'rounduptest' + MYSQL_DATABASE = (MYSQL_DBHOST, MYSQL_DBUSER, MYSQL_DBPASSWORD, MYSQL_DBNAME) + + # Postgresql connection data + POSTGRESQL_DBHOST = 'localhost' + POSTGRESQL_DBUSER = 'rounduptest' + POSTGRESQL_DBPASSWORD = 'rounduptest' + POSTGRESQL_DBNAME = 'rounduptest' + POSTGRESQL_PORT = 5432 + POSTGRESQL_DATABASE = {'host': POSTGRESQL_DBHOST, 'port': POSTGRESQL_PORT, + 'user': POSTGRESQL_DBUSER, 'password': POSTGRESQL_DBPASSWORD, + 'database': POSTGRESQL_DBNAME} + +class nodbconfig(config): + MYSQL_DATABASE = (config.MYSQL_DBHOST, config.MYSQL_DBUSER, config.MYSQL_DBPASSWORD) + +class DBTest(MyTestCase): + def setUp(self): + # remove previous test, ignore errors + if os.path.exists(config.DATABASE): + shutil.rmtree(config.DATABASE) + os.makedirs(config.DATABASE + '/files') + self.db = self.module.Database(config, 'admin') + setupSchema(self.db, 1, self.module) + + # + # schema mutation + # + def testAddProperty(self): + self.db.issue.create(title="spam", status='1') + self.db.commit() + + self.db.issue.addprop(fixer=Link("user")) + # force any post-init stuff to happen + self.db.post_init() + props = self.db.issue.getprops() + keys = props.keys() + keys.sort() + self.assertEqual(keys, ['activity', 'assignedto', 'creation', + 'creator', 'deadline', 'files', 'fixer', 'foo', 'id', 'messages', + 'nosy', 'status', 'superseder', 'title']) + self.assertEqual(self.db.issue.get('1', "fixer"), None) + + def testRemoveProperty(self): + self.db.issue.create(title="spam", status='1') + self.db.commit() + + del self.db.issue.properties['title'] + self.db.post_init() + props = self.db.issue.getprops() + keys = props.keys() + keys.sort() + self.assertEqual(keys, ['activity', 'assignedto', 'creation', + 'creator', 'deadline', 'files', 'foo', 'id', 'messages', + 'nosy', 'status', 'superseder']) + self.assertEqual(self.db.issue.list(), ['1']) + + def testAddRemoveProperty(self): + self.db.issue.create(title="spam", status='1') + self.db.commit() + + self.db.issue.addprop(fixer=Link("user")) + del self.db.issue.properties['title'] + self.db.post_init() + props = self.db.issue.getprops() + keys = props.keys() + keys.sort() + self.assertEqual(keys, ['activity', 'assignedto', 'creation', + 'creator', 'deadline', 'files', 'fixer', 'foo', 'id', 'messages', + 'nosy', 'status', 'superseder']) + self.assertEqual(self.db.issue.list(), ['1']) + + # + # basic operations + # + def testIDGeneration(self): + id1 = self.db.issue.create(title="spam", status='1') + id2 = self.db.issue.create(title="eggs", status='2') + self.assertNotEqual(id1, id2) + + def testStringChange(self): + for commit in (0,1): + # test set & retrieve + nid = self.db.issue.create(title="spam", status='1') + self.assertEqual(self.db.issue.get(nid, 'title'), 'spam') + + # change and make sure we retrieve the correct value + self.db.issue.set(nid, title='eggs') + if commit: self.db.commit() + self.assertEqual(self.db.issue.get(nid, 'title'), 'eggs') + + def testStringUnset(self): + for commit in (0,1): + nid = self.db.issue.create(title="spam", status='1') + if commit: self.db.commit() + self.assertEqual(self.db.issue.get(nid, 'title'), 'spam') + # make sure we can unset + self.db.issue.set(nid, title=None) + if commit: self.db.commit() + self.assertEqual(self.db.issue.get(nid, "title"), None) + + def testLinkChange(self): + for commit in (0,1): + nid = self.db.issue.create(title="spam", status='1') + if commit: self.db.commit() + self.assertEqual(self.db.issue.get(nid, "status"), '1') + self.db.issue.set(nid, status='2') + if commit: self.db.commit() + self.assertEqual(self.db.issue.get(nid, "status"), '2') + + def testLinkUnset(self): + for commit in (0,1): + nid = self.db.issue.create(title="spam", status='1') + if commit: self.db.commit() + self.db.issue.set(nid, status=None) + if commit: self.db.commit() + self.assertEqual(self.db.issue.get(nid, "status"), None) + + def testMultilinkChange(self): + for commit in (0,1): + u1 = self.db.user.create(username='foo%s'%commit) + u2 = self.db.user.create(username='bar%s'%commit) + nid = self.db.issue.create(title="spam", nosy=[u1]) + if commit: self.db.commit() + self.assertEqual(self.db.issue.get(nid, "nosy"), [u1]) + self.db.issue.set(nid, nosy=[]) + if commit: self.db.commit() + self.assertEqual(self.db.issue.get(nid, "nosy"), []) + self.db.issue.set(nid, nosy=[u1,u2]) + if commit: self.db.commit() + self.assertEqual(self.db.issue.get(nid, "nosy"), [u1,u2]) + + def testDateChange(self): + for commit in (0,1): + nid = self.db.issue.create(title="spam", status='1') + a = self.db.issue.get(nid, "deadline") + if commit: self.db.commit() + self.db.issue.set(nid, deadline=date.Date()) + b = self.db.issue.get(nid, "deadline") + if commit: self.db.commit() + self.assertNotEqual(a, b) + self.assertNotEqual(b, date.Date('1970-1-1 00:00:00')) + + def testDateUnset(self): + for commit in (0,1): + nid = self.db.issue.create(title="spam", status='1') + self.db.issue.set(nid, deadline=date.Date()) + if commit: self.db.commit() + self.assertNotEqual(self.db.issue.get(nid, "deadline"), None) + self.db.issue.set(nid, deadline=None) + if commit: self.db.commit() + self.assertEqual(self.db.issue.get(nid, "deadline"), None) + + def testIntervalChange(self): + for commit in (0,1): + nid = self.db.issue.create(title="spam", status='1') + if commit: self.db.commit() + a = self.db.issue.get(nid, "foo") + i = date.Interval('-1d') + self.db.issue.set(nid, foo=i) + if commit: self.db.commit() + self.assertNotEqual(self.db.issue.get(nid, "foo"), a) + self.assertEqual(i, self.db.issue.get(nid, "foo")) + j = date.Interval('1y') + self.db.issue.set(nid, foo=j) + if commit: self.db.commit() + self.assertNotEqual(self.db.issue.get(nid, "foo"), i) + self.assertEqual(j, self.db.issue.get(nid, "foo")) + + def testIntervalUnset(self): + for commit in (0,1): + nid = self.db.issue.create(title="spam", status='1') + self.db.issue.set(nid, foo=date.Interval('-1d')) + if commit: self.db.commit() + self.assertNotEqual(self.db.issue.get(nid, "foo"), None) + self.db.issue.set(nid, foo=None) + if commit: self.db.commit() + self.assertEqual(self.db.issue.get(nid, "foo"), None) + + def testBooleanChange(self): + userid = self.db.user.create(username='foo', assignable=1) + self.assertEqual(1, self.db.user.get(userid, 'assignable')) + self.db.user.set(userid, assignable=0) + self.assertEqual(self.db.user.get(userid, 'assignable'), 0) + + def testBooleanUnset(self): + nid = self.db.user.create(username='foo', assignable=1) + self.db.user.set(nid, assignable=None) + self.assertEqual(self.db.user.get(nid, "assignable"), None) + + def testNumberChange(self): + nid = self.db.user.create(username='foo', age=1) + self.assertEqual(1, self.db.user.get(nid, 'age')) + self.db.user.set(nid, age=3) + self.assertNotEqual(self.db.user.get(nid, 'age'), 1) + self.db.user.set(nid, age=1.0) + self.assertEqual(self.db.user.get(nid, 'age'), 1) + self.db.user.set(nid, age=0) + self.assertEqual(self.db.user.get(nid, 'age'), 0) + + nid = self.db.user.create(username='bar', age=0) + self.assertEqual(self.db.user.get(nid, 'age'), 0) + + def testNumberUnset(self): + nid = self.db.user.create(username='foo', age=1) + self.db.user.set(nid, age=None) + self.assertEqual(self.db.user.get(nid, "age"), None) + + def testKeyValue(self): + newid = self.db.user.create(username="spam") + self.assertEqual(self.db.user.lookup('spam'), newid) + self.db.commit() + self.assertEqual(self.db.user.lookup('spam'), newid) + self.db.user.retire(newid) + self.assertRaises(KeyError, self.db.user.lookup, 'spam') + + # use the key again now that the old is retired + newid2 = self.db.user.create(username="spam") + self.assertNotEqual(newid, newid2) + # try to restore old node. this shouldn't succeed! + self.assertRaises(KeyError, self.db.user.restore, newid) + + def testRetire(self): + self.db.issue.create(title="spam", status='1') + b = self.db.status.get('1', 'name') + a = self.db.status.list() + self.db.status.retire('1') + # make sure the list is different + self.assertNotEqual(a, self.db.status.list()) + # can still access the node if necessary + self.assertEqual(self.db.status.get('1', 'name'), b) + self.db.commit() + self.assertEqual(self.db.status.get('1', 'name'), b) + self.assertNotEqual(a, self.db.status.list()) + # try to restore retired node + self.db.status.restore('1') + + def testCacheCreateSet(self): + self.db.issue.create(title="spam", status='1') + a = self.db.issue.get('1', 'title') + self.assertEqual(a, 'spam') + self.db.issue.set('1', title='ham') + b = self.db.issue.get('1', 'title') + self.assertEqual(b, 'ham') + + def testSerialisation(self): + nid = self.db.issue.create(title="spam", status='1', + deadline=date.Date(), foo=date.Interval('-1d')) + self.db.commit() + assert isinstance(self.db.issue.get(nid, 'deadline'), date.Date) + assert isinstance(self.db.issue.get(nid, 'foo'), date.Interval) + uid = self.db.user.create(username="fozzy", + password=password.Password('t. bear')) + self.db.commit() + assert isinstance(self.db.user.get(uid, 'password'), password.Password) + + def testTransactions(self): + # remember the number of items we started + num_issues = len(self.db.issue.list()) + num_files = self.db.numfiles() + self.db.issue.create(title="don't commit me!", status='1') + self.assertNotEqual(num_issues, len(self.db.issue.list())) + self.db.rollback() + self.assertEqual(num_issues, len(self.db.issue.list())) + self.db.issue.create(title="please commit me!", status='1') + self.assertNotEqual(num_issues, len(self.db.issue.list())) + self.db.commit() + self.assertNotEqual(num_issues, len(self.db.issue.list())) + self.db.rollback() + self.assertNotEqual(num_issues, len(self.db.issue.list())) + self.db.file.create(name="test", type="text/plain", content="hi") + self.db.rollback() + self.assertEqual(num_files, self.db.numfiles()) + for i in range(10): + self.db.file.create(name="test", type="text/plain", + content="hi %d"%(i)) + self.db.commit() + num_files2 = self.db.numfiles() + self.assertNotEqual(num_files, num_files2) + self.db.file.create(name="test", type="text/plain", content="hi") + self.db.rollback() + self.assertNotEqual(num_files, self.db.numfiles()) + self.assertEqual(num_files2, self.db.numfiles()) + + # rollback / cache interaction + name1 = self.db.user.get('1', 'username') + self.db.user.set('1', username = name1+name1) + # get the prop so the info's forced into the cache (if there is one) + self.db.user.get('1', 'username') + self.db.rollback() + name2 = self.db.user.get('1', 'username') + self.assertEqual(name1, name2) + + def testDestroyNoJournalling(self): + self.innerTestDestroy(klass=self.db.session) + + def testDestroyJournalling(self): + self.innerTestDestroy(klass=self.db.issue) + + def innerTestDestroy(self, klass): + newid = klass.create(title='Mr Friendly') + n = len(klass.list()) + self.assertEqual(klass.get(newid, 'title'), 'Mr Friendly') + klass.destroy(newid) + self.assertRaises(IndexError, klass.get, newid, 'title') + self.assertNotEqual(len(klass.list()), n) + if klass.do_journal: + self.assertRaises(IndexError, klass.history, newid) + + # now with a commit + newid = klass.create(title='Mr Friendly') + n = len(klass.list()) + self.assertEqual(klass.get(newid, 'title'), 'Mr Friendly') + self.db.commit() + klass.destroy(newid) + self.assertRaises(IndexError, klass.get, newid, 'title') + self.db.commit() + self.assertRaises(IndexError, klass.get, newid, 'title') + self.assertNotEqual(len(klass.list()), n) + if klass.do_journal: + self.assertRaises(IndexError, klass.history, newid) + + # now with a rollback + newid = klass.create(title='Mr Friendly') + n = len(klass.list()) + self.assertEqual(klass.get(newid, 'title'), 'Mr Friendly') + self.db.commit() + klass.destroy(newid) + self.assertNotEqual(len(klass.list()), n) + self.assertRaises(IndexError, klass.get, newid, 'title') + self.db.rollback() + self.assertEqual(klass.get(newid, 'title'), 'Mr Friendly') + self.assertEqual(len(klass.list()), n) + if klass.do_journal: + self.assertNotEqual(klass.history(newid), []) + + def testExceptions(self): + # this tests the exceptions that should be raised + ar = self.assertRaises + + # + # class create + # + # string property + ar(TypeError, self.db.status.create, name=1) + # invalid property name + ar(KeyError, self.db.status.create, foo='foo') + # key name clash + ar(ValueError, self.db.status.create, name='unread') + # invalid link index + ar(IndexError, self.db.issue.create, title='foo', status='bar') + # invalid link value + ar(ValueError, self.db.issue.create, title='foo', status=1) + # invalid multilink type + ar(TypeError, self.db.issue.create, title='foo', status='1', + nosy='hello') + # invalid multilink index type + ar(ValueError, self.db.issue.create, title='foo', status='1', + nosy=[1]) + # invalid multilink index + ar(IndexError, self.db.issue.create, title='foo', status='1', + nosy=['10']) + + # + # key property + # + # key must be a String + ar(TypeError, self.db.file.setkey, 'fooz') + # key must exist + ar(KeyError, self.db.file.setkey, 'fubar') + + # + # class get + # + # invalid node id + ar(IndexError, self.db.issue.get, '99', 'title') + # invalid property name + ar(KeyError, self.db.status.get, '2', 'foo') + + # + # class set + # + # invalid node id + ar(IndexError, self.db.issue.set, '99', title='foo') + # invalid property name + ar(KeyError, self.db.status.set, '1', foo='foo') + # string property + ar(TypeError, self.db.status.set, '1', name=1) + # key name clash + ar(ValueError, self.db.status.set, '2', name='unread') + # set up a valid issue for me to work on + id = self.db.issue.create(title="spam", status='1') + # invalid link index + ar(IndexError, self.db.issue.set, id, title='foo', status='bar') + # invalid link value + ar(ValueError, self.db.issue.set, id, title='foo', status=1) + # invalid multilink type + ar(TypeError, self.db.issue.set, id, title='foo', status='1', + nosy='hello') + # invalid multilink index type + ar(ValueError, self.db.issue.set, id, title='foo', status='1', + nosy=[1]) + # invalid multilink index + ar(IndexError, self.db.issue.set, id, title='foo', status='1', + nosy=['10']) + # NOTE: the following increment the username to avoid problems + # within metakit's backend (it creates the node, and then sets the + # info, so the create (and by a fluke the username set) go through + # before the age/assignable/etc. set, which raises the exception) + # invalid number value + ar(TypeError, self.db.user.create, username='foo', age='a') + # invalid boolean value + ar(TypeError, self.db.user.create, username='foo2', assignable='true') + nid = self.db.user.create(username='foo3') + # invalid number value + ar(TypeError, self.db.user.set, nid, age='a') + # invalid boolean value + ar(TypeError, self.db.user.set, nid, assignable='true') + + def testJournals(self): + self.db.user.create(username="mary") + self.db.user.create(username="pete") + self.db.issue.create(title="spam", status='1') + self.db.commit() + + # journal entry for issue create + journal = self.db.getjournal('issue', '1') + self.assertEqual(1, len(journal)) + (nodeid, date_stamp, journaltag, action, params) = journal[0] + self.assertEqual(nodeid, '1') + self.assertEqual(journaltag, self.db.user.lookup('admin')) + self.assertEqual(action, 'create') + keys = params.keys() + keys.sort() + self.assertEqual(keys, []) + + # journal entry for link + journal = self.db.getjournal('user', '1') + self.assertEqual(1, len(journal)) + self.db.issue.set('1', assignedto='1') + self.db.commit() + journal = self.db.getjournal('user', '1') + self.assertEqual(2, len(journal)) + (nodeid, date_stamp, journaltag, action, params) = journal[1] + self.assertEqual('1', nodeid) + self.assertEqual('1', journaltag) + self.assertEqual('link', action) + self.assertEqual(('issue', '1', 'assignedto'), params) + + # journal entry for unlink + self.db.issue.set('1', assignedto='2') + self.db.commit() + journal = self.db.getjournal('user', '1') + self.assertEqual(3, len(journal)) + (nodeid, date_stamp, journaltag, action, params) = journal[2] + self.assertEqual('1', nodeid) + self.assertEqual('1', journaltag) + self.assertEqual('unlink', action) + self.assertEqual(('issue', '1', 'assignedto'), params) + + # test disabling journalling + # ... get the last entry + time.sleep(1) + entry = self.db.getjournal('issue', '1')[-1] + (x, date_stamp, x, x, x) = entry + self.db.issue.disableJournalling() + self.db.issue.set('1', title='hello world') + self.db.commit() + entry = self.db.getjournal('issue', '1')[-1] + (x, date_stamp2, x, x, x) = entry + # see if the change was journalled when it shouldn't have been + self.assertEqual(date_stamp, date_stamp2) + time.sleep(1) + self.db.issue.enableJournalling() + self.db.issue.set('1', title='hello world 2') + self.db.commit() + entry = self.db.getjournal('issue', '1')[-1] + (x, date_stamp2, x, x, x) = entry + # see if the change was journalled + self.assertNotEqual(date_stamp, date_stamp2) + + def testJournalPreCommit(self): + id = self.db.user.create(username="mary") + self.assertEqual(len(self.db.getjournal('user', id)), 1) + self.db.commit() + + def testPack(self): + id = self.db.issue.create(title="spam", status='1') + self.db.commit() + self.db.issue.set(id, status='2') + self.db.commit() + + # sleep for at least a second, then get a date to pack at + time.sleep(1) + pack_before = date.Date('.') + + # wait another second and add one more entry + time.sleep(1) + self.db.issue.set(id, status='3') + self.db.commit() + jlen = len(self.db.getjournal('issue', id)) + + # pack + self.db.pack(pack_before) + + # we should have the create and last set entries now + self.assertEqual(jlen-1, len(self.db.getjournal('issue', id))) + + def testSearching(self): + self.db.file.create(content='hello', type="text/plain") + self.db.file.create(content='world', type="text/frozz", + comment='blah blah') + self.db.issue.create(files=['1', '2'], title="flebble plop") + self.db.issue.create(title="flebble frooz") + self.db.commit() + self.assertEquals(self.db.indexer.search(['hello'], self.db.issue), + {'1': {'files': ['1']}}) + self.assertEquals(self.db.indexer.search(['world'], self.db.issue), {}) + self.assertEquals(self.db.indexer.search(['frooz'], self.db.issue), + {'2': {}}) + self.assertEquals(self.db.indexer.search(['flebble'], self.db.issue), + {'2': {}, '1': {}}) + + def testReindexing(self): + self.db.issue.create(title="frooz") + self.db.commit() + self.assertEquals(self.db.indexer.search(['frooz'], self.db.issue), + {'1': {}}) + self.db.issue.set('1', title="dooble") + self.db.commit() + self.assertEquals(self.db.indexer.search(['dooble'], self.db.issue), + {'1': {}}) + self.assertEquals(self.db.indexer.search(['frooz'], self.db.issue), {}) + + def testForcedReindexing(self): + self.db.issue.create(title="flebble frooz") + self.db.commit() + self.assertEquals(self.db.indexer.search(['flebble'], self.db.issue), + {'1': {}}) + self.db.indexer.quiet = 1 + self.db.indexer.force_reindex() + self.db.post_init() + self.db.indexer.quiet = 9 + self.assertEquals(self.db.indexer.search(['flebble'], self.db.issue), + {'1': {}}) + + # + # searching tests follow + # + def testFind(self): + self.db.user.create(username='test') + ids = [] + ids.append(self.db.issue.create(status="1", nosy=['1'])) + oddid = self.db.issue.create(status="2", nosy=['2'], assignedto='2') + ids.append(self.db.issue.create(status="1", nosy=['1','2'])) + self.db.issue.create(status="3", nosy=['1'], assignedto='1') + ids.sort() + + # should match first and third + got = self.db.issue.find(status='1') + got.sort() + self.assertEqual(got, ids) + + # none + self.assertEqual(self.db.issue.find(status='4'), []) + + # should match first and third + got = self.db.issue.find(assignedto=None) + got.sort() + self.assertEqual(got, ids) + + # should match first three + got = self.db.issue.find(status='1', nosy='2') + got.sort() + ids.append(oddid) + ids.sort() + self.assertEqual(got, ids) + + # none + self.assertEqual(self.db.issue.find(status='4', nosy='3'), []) + + def testStringFind(self): + ids = [] + ids.append(self.db.issue.create(title="spam")) + self.db.issue.create(title="not spam") + ids.append(self.db.issue.create(title="spam")) + ids.sort() + got = self.db.issue.stringFind(title='spam') + got.sort() + self.assertEqual(got, ids) + self.assertEqual(self.db.issue.stringFind(title='fubar'), []) + + def filteringSetup(self): + for user in ( + {'username': 'bleep'}, + {'username': 'blop'}, + {'username': 'blorp'}): + self.db.user.create(**user) + iss = self.db.issue + for issue in ( + {'title': 'issue one', 'status': '2', + 'foo': date.Interval('1:10'), + 'deadline': date.Date('2003-01-01.00:00')}, + {'title': 'issue two', 'status': '1', + 'foo': date.Interval('1d'), + 'deadline': date.Date('2003-02-16.22:50')}, + {'title': 'issue three', 'status': '1', + 'nosy': ['1','2'], 'deadline': date.Date('2003-02-18')}, + {'title': 'non four', 'status': '3', + 'foo': date.Interval('0:10'), + 'nosy': ['1'], 'deadline': date.Date('2004-03-08')}): + self.db.issue.create(**issue) + self.db.commit() + return self.assertEqual, self.db.issue.filter + + def testFilteringID(self): + ae, filt = self.filteringSetup() + ae(filt(None, {'id': '1'}, ('+','id'), (None,None)), ['1']) + + def testFilteringString(self): + ae, filt = self.filteringSetup() + ae(filt(None, {'title': ['one']}, ('+','id'), (None,None)), ['1']) + ae(filt(None, {'title': ['issue']}, ('+','id'), (None,None)), + ['1','2','3']) + ae(filt(None, {'title': ['one', 'two']}, ('+','id'), (None,None)), + ['1', '2']) + + def testFilteringLink(self): + ae, filt = self.filteringSetup() + ae(filt(None, {'status': '1'}, ('+','id'), (None,None)), ['2','3']) + + def testFilteringRetired(self): + ae, filt = self.filteringSetup() + self.db.issue.retire('2') + ae(filt(None, {'status': '1'}, ('+','id'), (None,None)), ['3']) + + def testFilteringMultilink(self): + ae, filt = self.filteringSetup() + ae(filt(None, {'nosy': '2'}, ('+','id'), (None,None)), ['3']) + ae(filt(None, {'nosy': '-1'}, ('+','id'), (None,None)), ['1', '2']) + + def testFilteringMany(self): + ae, filt = self.filteringSetup() + ae(filt(None, {'nosy': '2', 'status': '1'}, ('+','id'), (None,None)), + ['3']) + + def testFilteringRange(self): + ae, filt = self.filteringSetup() + # Date ranges + ae(filt(None, {'deadline': 'from 2003-02-10 to 2003-02-23'}), ['2','3']) + ae(filt(None, {'deadline': '2003-02-10; 2003-02-23'}), ['2','3']) + ae(filt(None, {'deadline': '; 2003-02-16'}), ['1']) + # Lets assume people won't invent a time machine, otherwise this test + # may fail :) + ae(filt(None, {'deadline': 'from 2003-02-16'}), ['2', '3', '4']) + ae(filt(None, {'deadline': '2003-02-16;'}), ['2', '3', '4']) + # year and month granularity + ae(filt(None, {'deadline': '2002'}), []) + ae(filt(None, {'deadline': '2003'}), ['1', '2', '3']) + ae(filt(None, {'deadline': '2004'}), ['4']) + ae(filt(None, {'deadline': '2003-02'}), ['2', '3']) + ae(filt(None, {'deadline': '2003-03'}), []) + ae(filt(None, {'deadline': '2003-02-16'}), ['2']) + ae(filt(None, {'deadline': '2003-02-17'}), []) + # Interval ranges + ae(filt(None, {'foo': 'from 0:50 to 2:00'}), ['1']) + ae(filt(None, {'foo': 'from 0:50 to 1d 2:00'}), ['1', '2']) + ae(filt(None, {'foo': 'from 5:50'}), ['2']) + ae(filt(None, {'foo': 'to 0:05'}), []) + + def testFilteringIntervalSort(self): + ae, filt = self.filteringSetup() + # ascending should sort None, 1:10, 1d + ae(filt(None, {}, ('+','foo'), (None,None)), ['3', '4', '1', '2']) + # descending should sort 1d, 1:10, None + ae(filt(None, {}, ('-','foo'), (None,None)), ['2', '1', '4', '3']) + +# XXX add sorting tests for other types +# XXX test auditors and reactors + + +class ROTest(MyTestCase): + def setUp(self): + # remove previous test, ignore errors + if os.path.exists(config.DATABASE): + shutil.rmtree(config.DATABASE) + os.makedirs(config.DATABASE + '/files') + self.db = self.module.Database(config, 'admin') + setupSchema(self.db, 1, self.module) + self.db.close() + + self.db = self.module.Database(config) + setupSchema(self.db, 0, self.module) + + def testExceptions(self): + # this tests the exceptions that should be raised + ar = self.assertRaises + + # this tests the exceptions that should be raised + ar(DatabaseError, self.db.status.create, name="foo") + ar(DatabaseError, self.db.status.set, '1', name="foo") + ar(DatabaseError, self.db.status.retire, '1') + + +class SchemaTest(MyTestCase): + def setUp(self): + # remove previous test, ignore errors + if os.path.exists(config.DATABASE): + shutil.rmtree(config.DATABASE) + os.makedirs(config.DATABASE + '/files') + + def init_a(self): + self.db = self.module.Database(config, 'admin') + a = self.module.Class(self.db, "a", name=String()) + a.setkey("name") + self.db.post_init() + + def init_ab(self): + self.db = self.module.Database(config, 'admin') + a = self.module.Class(self.db, "a", name=String()) + a.setkey("name") + b = self.module.Class(self.db, "b", name=String()) + b.setkey("name") + self.db.post_init() + + def test_addNewClass(self): + self.init_a() + aid = self.db.a.create(name='apple') + self.db.commit(); self.db.close() + + # add a new class to the schema and check creation of new items + # (and existence of old ones) + self.init_ab() + bid = self.db.b.create(name='bear') + self.assertEqual(self.db.a.get(aid, 'name'), 'apple') + self.db.commit() + self.db.close() + + # now check we can recall the added class' items + self.init_ab() + self.assertEqual(self.db.a.get(aid, 'name'), 'apple') + self.assertEqual(self.db.a.lookup('apple'), aid) + self.assertEqual(self.db.b.get(bid, 'name'), 'bear') + self.assertEqual(self.db.b.lookup('bear'), bid) + + def init_amod(self): + self.db = self.module.Database(config, 'admin') + a = self.module.Class(self.db, "a", name=String(), fooz=String()) + a.setkey("name") + b = self.module.Class(self.db, "b", name=String()) + b.setkey("name") + self.db.post_init() + + def test_modifyClass(self): + self.init_ab() + + # add item to user and issue class + aid = self.db.a.create(name='apple') + bid = self.db.b.create(name='bear') + self.db.commit(); self.db.close() + + # modify "a" schema + self.init_amod() + self.assertEqual(self.db.a.get(aid, 'name'), 'apple') + self.assertEqual(self.db.a.get(aid, 'fooz'), None) + self.assertEqual(self.db.b.get(aid, 'name'), 'bear') + aid2 = self.db.a.create(name='aardvark', fooz='booz') + self.db.commit(); self.db.close() + + # test + self.init_amod() + self.assertEqual(self.db.a.get(aid, 'name'), 'apple') + self.assertEqual(self.db.a.get(aid, 'fooz'), None) + self.assertEqual(self.db.b.get(aid, 'name'), 'bear') + self.assertEqual(self.db.a.get(aid2, 'name'), 'aardvark') + self.assertEqual(self.db.a.get(aid2, 'fooz'), 'booz') + + def init_amodkey(self): + self.db = self.module.Database(config, 'admin') + a = self.module.Class(self.db, "a", name=String(), fooz=String()) + a.setkey("fooz") + b = self.module.Class(self.db, "b", name=String()) + b.setkey("name") + self.db.post_init() + + def test_changeClassKey(self): + self.init_amod() + aid = self.db.a.create(name='apple') + self.assertEqual(self.db.a.lookup('apple'), aid) + self.db.commit(); self.db.close() + + # change the key to fooz + self.init_amodkey() + self.assertEqual(self.db.a.get(aid, 'name'), 'apple') + self.assertEqual(self.db.a.get(aid, 'fooz'), None) + self.assertRaises(KeyError, self.db.a.lookup, 'apple') + aid2 = self.db.a.create(name='aardvark', fooz='booz') + self.db.commit(); self.db.close() + + # check + self.init_amodkey() + self.assertEqual(self.db.a.lookup('booz'), aid2) + + def init_ml(self): + self.db = self.module.Database(config, 'admin') + a = self.module.Class(self.db, "a", name=String()) + a.setkey('name') + b = self.module.Class(self.db, "b", name=String(), + fooz=Multilink('a')) + b.setkey("name") + self.db.post_init() + + def test_makeNewMultilink(self): + self.init_a() + aid = self.db.a.create(name='apple') + self.assertEqual(self.db.a.lookup('apple'), aid) + self.db.commit(); self.db.close() + + # add a multilink prop + self.init_ml() + bid = self.db.b.create(name='bear', fooz=[aid]) + self.assertEqual(self.db.b.find(fooz=aid), [bid]) + self.assertEqual(self.db.a.lookup('apple'), aid) + self.db.commit(); self.db.close() + + # check + self.init_ml() + self.assertEqual(self.db.b.find(fooz=aid), [bid]) + self.assertEqual(self.db.a.lookup('apple'), aid) + self.assertEqual(self.db.b.lookup('bear'), bid) + + def test_removeMultilink(self): + # add a multilink prop + self.init_ml() + aid = self.db.a.create(name='apple') + bid = self.db.b.create(name='bear', fooz=[aid]) + self.assertEqual(self.db.b.find(fooz=aid), [bid]) + self.assertEqual(self.db.a.lookup('apple'), aid) + self.assertEqual(self.db.b.lookup('bear'), bid) + self.db.commit(); self.db.close() + + # remove the multilink + self.init_ab() + self.assertEqual(self.db.a.lookup('apple'), aid) + self.assertEqual(self.db.b.lookup('bear'), bid) + + def test_removeClass(self): + self.init_ml() + aid = self.db.a.create(name='apple') + bid = self.db.b.create(name='bear', fooz=[aid]) + self.db.commit(); self.db.close() + + # drop the b class + self.init_a() + self.assertEqual(self.db.a.get(aid, 'name'), 'apple') + self.assertEqual(self.db.a.lookup('apple'), aid) + self.db.commit(); self.db.close() + + # now check we can recall the added class' items + self.init_a() + self.assertEqual(self.db.a.get(aid, 'name'), 'apple') + self.assertEqual(self.db.a.lookup('apple'), aid) + + +class ClassicInitTest(unittest.TestCase): + count = 0 + db = None + + def setUp(self): + ClassicInitTest.count = ClassicInitTest.count + 1 + self.dirname = '_test_init_%s'%self.count + try: + shutil.rmtree(self.dirname) + except OSError, error: + if error.errno not in (errno.ENOENT, errno.ESRCH): raise + + def testCreation(self): + ae = self.assertEqual + + # create the instance + init.install(self.dirname, 'templates/classic') + init.write_select_db(self.dirname, self.backend) + init.initialise(self.dirname, 'sekrit') + + # check we can load the package + instance = imp.load_package(self.dirname, self.dirname) + + # and open the database + db = self.db = instance.open() + + # check the basics of the schema and initial data set + l = db.priority.list() + ae(l, ['1', '2', '3', '4', '5']) + l = db.status.list() + ae(l, ['1', '2', '3', '4', '5', '6', '7', '8']) + l = db.keyword.list() + ae(l, []) + l = db.user.list() + ae(l, ['1', '2']) + l = db.msg.list() + ae(l, []) + l = db.file.list() + ae(l, []) + l = db.issue.list() + ae(l, []) + + def tearDown(self): + if self.db is not None: + self.db.close() + try: + shutil.rmtree(self.dirname) + except OSError, error: + if error.errno not in (errno.ENOENT, errno.ESRCH): raise +
