From 5e876db70384eafa745750ac9ddf5cc506f23287 Mon Sep 17 00:00:00 2001 From: richard Date: Thu, 18 Mar 2004 01:58:46 +0000 Subject: [PATCH] Finished implementation of session and one-time-key stores for RDBMS backends. Refactored the API of sessions and their interaction with the backend database a fair bit too. Added some session tests. Nothing testing ageing yet, 'cos that's a pain inna ass to test :) Note: metakit backend still uses the *dbm implementation. It might want to implement its own session store some day, as it'll be faster than the *dbm one. git-svn-id: http://svn.roundup-tracker.org/svnroot/roundup/trunk@2151 57a73879-2fb5-44c3-a270-3262357dd7e2 --- CHANGES.txt | 1 + roundup/backends/back_anydbm.py | 12 ++- roundup/backends/back_metakit.py | 12 ++- roundup/backends/back_mysql.py | 30 ++++--- roundup/backends/back_postgresql.py | 26 +++--- roundup/backends/back_sqlite.py | 35 ++++++-- roundup/backends/rdbms_common.py | 21 +++-- .../backends/{sessions.py => sessions_dbm.py} | 28 +++--- roundup/backends/sessions_rdbms.py | 90 +++++++++++++++++++ roundup/cgi/actions.py | 24 ++--- roundup/cgi/client.py | 23 +++-- templates/classic/html/style.css | 2 - test/db_test_base.py | 6 +- test/session_common.py | 46 ++++++++++ test/test_anydbm.py | 7 +- test/test_bsddb.py | 7 +- test/test_bsddb3.py | 7 +- test/test_metakit.py | 7 +- test/test_mysql.py | 12 ++- test/test_postgresql.py | 12 ++- test/test_sqlite.py | 7 +- 21 files changed, 318 insertions(+), 97 deletions(-) rename roundup/backends/{sessions.py => sessions_dbm.py} (84%) create mode 100644 roundup/backends/sessions_rdbms.py create mode 100644 test/session_common.py diff --git a/CHANGES.txt b/CHANGES.txt index 710b104..f0c79f6 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -13,6 +13,7 @@ Feature: - added postgresql backend (originally from sf patch 761740, many changes since) - all RDBMS backends now have indexes on several columns +- RDBMS backends implement their session and one-time-key stores - change nosymessage and send_message to accept msgid=None (RFE #707235). - handle Resent-From: headers (sf bug 841151) - always sort MultilinkHTMLProperty in the correct order, usually diff --git a/roundup/backends/back_anydbm.py b/roundup/backends/back_anydbm.py index 64c6236..7091d19 100644 --- a/roundup/backends/back_anydbm.py +++ b/roundup/backends/back_anydbm.py @@ -15,7 +15,7 @@ # BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE, # SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. # -#$Id: back_anydbm.py,v 1.137 2004-03-15 05:50:20 richard Exp $ +#$Id: back_anydbm.py,v 1.138 2004-03-18 01:58:45 richard Exp $ '''This module defines a backend that saves the hyperdatabase in a database chosen by anydbm. It is guaranteed to always be available in python versions >2.1.1 (the dumbdbm fallback in 2.1.1 and earlier has several @@ -36,7 +36,7 @@ except AssertionError: import whichdb, os, marshal, re, weakref, string, copy from roundup import hyperdb, date, password, roundupdb, security from blobfiles import FileStorage -from sessions import Sessions, OneTimeKeys +from sessions_dbm import Sessions, OneTimeKeys from roundup.indexer import Indexer from roundup.backends import locking from roundup.hyperdb import String, Password, Date, Interval, Link, \ @@ -79,8 +79,6 @@ class Database(FileStorage, hyperdb.Database, roundupdb.Database): self.destroyednodes = {}# keep track of the destroyed nodes by class self.transactions = [] self.indexer = Indexer(self.dir) - self.sessions = Sessions(self.config) - self.otks = OneTimeKeys(self.config) self.security = security.Security(self) # ensure files are group readable and writable os.umask(0002) @@ -103,6 +101,12 @@ class Database(FileStorage, hyperdb.Database, roundupdb.Database): """ self.reindex() + def getSessionManager(self): + return Sessions(self) + + def getOTKManager(self): + return OneTimeKeys(self) + def reindex(self): for klass in self.classes.values(): for nodeid in klass.list(): diff --git a/roundup/backends/back_metakit.py b/roundup/backends/back_metakit.py index 055782e..cf3f59f 100755 --- a/roundup/backends/back_metakit.py +++ b/roundup/backends/back_metakit.py @@ -1,4 +1,4 @@ -# $Id: back_metakit.py,v 1.61 2004-03-12 05:36:26 richard Exp $ +# $Id: back_metakit.py,v 1.62 2004-03-18 01:58:45 richard Exp $ '''Metakit backend for Roundup, originally by Gordon McMillan. Known Current Bugs: @@ -43,7 +43,7 @@ BACKWARDS_COMPATIBLE = True from roundup import hyperdb, date, password, roundupdb, security import metakit -from sessions import Sessions, OneTimeKeys +from sessions_dbm import Sessions, OneTimeKeys import re, marshal, os, sys, time, calendar from roundup import indexer import locking @@ -81,8 +81,6 @@ class _Database(hyperdb.Database, roundupdb.Database): self.lockfile = None self._db = self.__open() self.indexer = Indexer(self.config.DATABASE, self._db) - self.sessions = Sessions(self.config) - self.otks = OneTimeKeys(self.config) self.security = security.Security(self) os.umask(0002) @@ -101,6 +99,12 @@ class _Database(hyperdb.Database, roundupdb.Database): klass.index(nodeid) self.indexer.save_index() + def getSessionManager(self): + return Sessions(self) + + def getOTKManager(self): + return OneTimeKeys(self) + # --- defined in ping's spec def __getattr__(self, classname): if classname == 'transactions': diff --git a/roundup/backends/back_mysql.py b/roundup/backends/back_mysql.py index a02b445..3afd3d3 100644 --- a/roundup/backends/back_mysql.py +++ b/roundup/backends/back_mysql.py @@ -86,22 +86,25 @@ class Database(Database): # use BDB to pass all unit tests. mysql_backend = 'InnoDB' #mysql_backend = 'BDB' - - def sql_open_connection(self): - # make sure the database actually exists - if not db_exists(self.config): - db_create(self.config) + def sql_open_connection(self): db = getattr(self.config, 'MYSQL_DATABASE') try: - self.conn = MySQLdb.connect(*db) + conn = MySQLdb.connect(*db) except MySQLdb.OperationalError, message: raise DatabaseError, message + cursor = conn.cursor() + cursor.execute("SET AUTOCOMMIT=0") + cursor.execute("BEGIN") + return (conn, cursor) + + def open_connection(self): + # make sure the database actually exists + if not db_exists(self.config): + db_create(self.config) + + self.conn, self.cursor = self.sql_open_connection() - self.cursor = self.conn.cursor() - # start transaction - self.sql("SET AUTOCOMMIT=0") - self.sql("BEGIN") try: self.load_dbschema() except MySQLdb.OperationalError, message: @@ -124,9 +127,10 @@ class Database(Database): self.cursor.execute('CREATE TABLE otks (otk_key VARCHAR(255), ' 'otk_value VARCHAR(255), otk_time FLOAT(20))') self.cursor.execute('CREATE INDEX otks_key_idx ON otks(otk_key)') - self.cursor.execute('CREATE TABLE sessions (s_key VARCHAR(255), ' - 's_last_use FLOAT(20), s_user VARCHAR(255))') - self.cursor.execute('CREATE INDEX sessions_key_idx ON sessions(s_key)') + self.cursor.execute('CREATE TABLE sessions (session_key VARCHAR(255), ' + 'session_time FLOAT(20), session_value VARCHAR(255))') + self.cursor.execute('CREATE INDEX sessions_key_idx ON ' + 'sessions(session_key)') def add_actor_column(self): # update existing tables to have the new actor column diff --git a/roundup/backends/back_postgresql.py b/roundup/backends/back_postgresql.py index fa9ba09..a09086b 100644 --- a/roundup/backends/back_postgresql.py +++ b/roundup/backends/back_postgresql.py @@ -83,19 +83,24 @@ class Database(rdbms_common.Database): arg = '%s' def sql_open_connection(self): + db = getattr(self.config, 'POSTGRESQL_DATABASE') + try: + conn = psycopg.connect(**db) + except psycopg.OperationalError, message: + raise hyperdb.DatabaseError, message + + cursor = conn.cursor() + + return (conn, cursor) + + def open_connection(self): if not db_exists(self.config): db_create(self.config) if __debug__: print >>hyperdb.DEBUG, '+++ open database connection +++' - db = getattr(self.config, 'POSTGRESQL_DATABASE') - try: - self.conn = psycopg.connect(**db) - except psycopg.OperationalError, message: - raise hyperdb.DatabaseError, message - - self.cursor = self.conn.cursor() + self.conn, self.cursor = self.sql_open_connection() try: self.load_dbschema() @@ -111,9 +116,10 @@ class Database(rdbms_common.Database): self.cursor.execute('CREATE TABLE otks (otk_key VARCHAR(255), ' 'otk_value VARCHAR(255), otk_time FLOAT(20))') self.cursor.execute('CREATE INDEX otks_key_idx ON otks(otk_key)') - self.cursor.execute('CREATE TABLE sessions (s_key VARCHAR(255), ' - 's_last_use FLOAT(20), s_user VARCHAR(255))') - self.cursor.execute('CREATE INDEX sessions_key_idx ON sessions(s_key)') + self.cursor.execute('CREATE TABLE sessions (session_key VARCHAR(255), ' + 'session_time FLOAT(20), session_value VARCHAR(255))') + self.cursor.execute('CREATE INDEX sessions_key_idx ON ' + 'sessions(session_key)') def add_actor_column(self): # update existing tables to have the new actor column diff --git a/roundup/backends/back_sqlite.py b/roundup/backends/back_sqlite.py index 04100d0..e1105d3 100644 --- a/roundup/backends/back_sqlite.py +++ b/roundup/backends/back_sqlite.py @@ -1,4 +1,4 @@ -# $Id: back_sqlite.py,v 1.16 2004-03-15 05:50:20 richard Exp $ +# $Id: back_sqlite.py,v 1.17 2004-03-18 01:58:45 richard Exp $ '''Implements a backend for SQLite. See https://pysqlite.sourceforge.net/ for pysqlite info @@ -17,18 +17,24 @@ class Database(rdbms_common.Database): arg = '%s' def sql_open_connection(self): + db = os.path.join(self.config.DATABASE, 'db') + conn = sqlite.connect(db=db) + cursor = conn.cursor() + return (conn, cursor) + + def open_connection(self): # ensure files are group readable and writable os.umask(0002) - db = os.path.join(self.config.DATABASE, 'db') - # lock it + # lock the database + db = os.path.join(self.config.DATABASE, 'db') lockfilenm = db[:-3] + 'lck' self.lockfile = locking.acquire_lock(lockfilenm) self.lockfile.write(str(os.getpid())) self.lockfile.flush() - self.conn = sqlite.connect(db=db) - self.cursor = self.conn.cursor() + (self.conn, self.cursor) = self.sql_open_connection() + try: self.load_dbschema() except sqlite.DatabaseError, error: @@ -40,13 +46,24 @@ class Database(rdbms_common.Database): self.cursor.execute('create index ids_name_idx on ids(name)') self.create_version_2_tables() + def close(self): + ''' Close off the connection. + ''' + self.sql_close() + if self.lockfile is not None: + locking.release_lock(self.lockfile) + if self.lockfile is not None: + self.lockfile.close() + self.lockfile = None + def create_version_2_tables(self): self.cursor.execute('create table otks (otk_key varchar, ' - 'otk_value varchar, otk_time varchar)') + 'otk_value varchar, otk_time integer)') self.cursor.execute('create index otks_key_idx on otks(otk_key)') - self.cursor.execute('create table sessions (s_key varchar, ' - 's_last_use varchar, s_user varchar)') - self.cursor.execute('create index sessions_key_idx on sessions(s_key)') + self.cursor.execute('create table sessions (session_key varchar, ' + 'session_time integer, session_value varchar)') + self.cursor.execute('create index sessions_key_idx on ' + 'sessions(session_key)') def add_actor_column(self): # update existing tables to have the new actor column diff --git a/roundup/backends/rdbms_common.py b/roundup/backends/rdbms_common.py index 2d03036..2894cc6 100644 --- a/roundup/backends/rdbms_common.py +++ b/roundup/backends/rdbms_common.py @@ -1,4 +1,4 @@ -# $Id: rdbms_common.py,v 1.80 2004-03-17 22:01:37 richard Exp $ +# $Id: rdbms_common.py,v 1.81 2004-03-18 01:58:45 richard Exp $ ''' Relational database (SQL) backend common code. Basics: @@ -40,7 +40,7 @@ from roundup.backends import locking # support from blobfiles import FileStorage from roundup.indexer import Indexer -from sessions import Sessions, OneTimeKeys +from sessions_rdbms import Sessions, OneTimeKeys from roundup.date import Range # number of rows to keep in memory @@ -60,8 +60,6 @@ class Database(FileStorage, hyperdb.Database, roundupdb.Database): self.dir = config.DATABASE self.classes = {} self.indexer = Indexer(self.dir) - self.sessions = Sessions(self.config) - self.otks = OneTimeKeys(self.config) self.security = security.Security(self) # additional transaction support for external files and the like @@ -76,13 +74,19 @@ class Database(FileStorage, hyperdb.Database, roundupdb.Database): self.lockfile = None # open a connection to the database, creating the "conn" attribute - self.sql_open_connection() + self.open_connection() def clearCache(self): self.cache = {} self.cache_lru = [] - def sql_open_connection(self): + def getSessionManager(self): + return Sessions(self) + + def getOTKManager(self): + return OneTimeKeys(self) + + def open_connection(self): ''' Open a connection to the database, creating it if necessary. Must call self.load_dbschema() @@ -1069,11 +1073,6 @@ class Database(FileStorage, hyperdb.Database, roundupdb.Database): ''' Close off the connection. ''' self.sql_close() - if self.lockfile is not None: - locking.release_lock(self.lockfile) - if self.lockfile is not None: - self.lockfile.close() - self.lockfile = None # # The base Class class diff --git a/roundup/backends/sessions.py b/roundup/backends/sessions_dbm.py similarity index 84% rename from roundup/backends/sessions.py rename to roundup/backends/sessions_dbm.py index 5f9a79b..f8f0222 100644 --- a/roundup/backends/sessions.py +++ b/roundup/backends/sessions_dbm.py @@ -1,4 +1,4 @@ -#$Id: sessions.py,v 1.10 2004-02-26 04:20:45 drkorg Exp $ +#$Id: sessions_dbm.py,v 1.1 2004-03-18 01:58:45 richard Exp $ """This module defines a very basic store that's used by the CGI interface to store session and one-time-key information. @@ -16,9 +16,9 @@ class BasicDatabase: ''' _db_type = None - def __init__(self, config): - self.config = config - self.dir = config.DATABASE + def __init__(self, db): + self.config = db.config + self.dir = db.config.DATABASE # ensure files are group readable and writable os.umask(0002) @@ -45,13 +45,16 @@ class BasicDatabase: db_type = 'dbm' self.__class__._db_type = db_type - def get(self, infoid, value): + _marker = [] + def get(self, infoid, value, default=_marker): db = self.opendb('c') try: if db.has_key(infoid): values = marshal.loads(db[infoid]) else: - return None + if default != self._marker: + return default + raise KeyError, 'No such %s "%s"'%(self.name, infoid) return values.get(value, None) finally: db.close() @@ -62,7 +65,7 @@ class BasicDatabase: try: return marshal.loads(db[infoid]) except KeyError: - raise KeyError, 'No such One Time Key "%s"'%infoid + raise KeyError, 'No such %s "%s"'%(self.name, infoid) finally: db.close() @@ -72,7 +75,7 @@ class BasicDatabase: if db.has_key(infoid): values = marshal.loads(db[infoid]) else: - values = {} + values = {'__timestamp': time.time()} values.update(newvalues) db[infoid] = marshal.dumps(values) finally: @@ -115,23 +118,24 @@ class BasicDatabase: def commit(self): pass + def close(self): + pass + def updateTimestamp(self, sessid): - self.set(sessid, **{self.timestamp: time.time()}) + self.set(sessid, __timestamp=time.time()) def clean(self, now): """Age sessions, remove when they haven't been used for a week. """ week = 60*60*24*7 for sessid in self.list(): - interval = now - self.get(sessid, self.timestamp) + interval = now - self.get(sessid, '__timestamp') if interval > week: self.destroy(sessid) class Sessions(BasicDatabase): name = 'sessions' - timestamp = 'last_use' class OneTimeKeys(BasicDatabase): name = 'otks' - timestamp = '__time' diff --git a/roundup/backends/sessions_rdbms.py b/roundup/backends/sessions_rdbms.py new file mode 100644 index 0000000..7ee3bc1 --- /dev/null +++ b/roundup/backends/sessions_rdbms.py @@ -0,0 +1,90 @@ +#$Id: sessions_rdbms.py,v 1.1 2004-03-18 01:58:45 richard Exp $ +"""This module defines a very basic store that's used by the CGI interface +to store session and one-time-key information. + +Yes, it's called "sessions" - because originally it only defined a session +class. It's now also used for One Time Key handling too. +""" +__docformat__ = 'restructuredtext' + +import os, time + +class BasicDatabase: + ''' Provide a nice encapsulation of an RDBMS table. + + Keys are id strings, values are automatically marshalled data. + ''' + def __init__(self, db): + self.db = db + self.cursor = self.db.cursor + + def clear(self): + self.cursor.execute('delete from %ss'%self.name) + + _marker = [] + def get(self, infoid, value, default=_marker): + n = self.name + self.cursor.execute('select %s_value from %ss where %s_key=%s'%(n, + n, n, self.db.arg), (infoid,)) + res = self.cursor.fetchone() + if not res: + if default != self._marker: + return default + raise KeyError, 'No such %s "%s"'%(self.name, infoid) + values = eval(res[0]) + return values.get(value, None) + + def getall(self, infoid): + n = self.name + self.cursor.execute('select %s_value from %ss where %s_key=%s'%(n, + n, n, self.db.arg), (infoid,)) + res = self.cursor.fetchone() + if not res: + raise KeyError, 'No such %s "%s"'%(self.name, infoid) + return eval(res[0]) + + def set(self, infoid, **newvalues): + c = self.cursor + n = self.name + a = self.db.arg + c.execute('select %s_value from %ss where %s_key=%s'%(n, n, n, a), + (infoid,)) + res = c.fetchone() + if res: + values = eval(res[0]) + else: + values = {} + values.update(newvalues) + + if res: + sql = 'update %ss set %s_value=%s where %s_key=%s'%(n, n, + a, n, a) + args = (repr(values), infoid) + else: + sql = 'insert into %ss (%s_key, %s_time, %s_value) '\ + 'values (%s, %s, %s)'%(n, n, n, n, a, a, a) + args = (infoid, time.time(), repr(values)) + c.execute(sql, args) + + def destroy(self, infoid): + self.cursor.execute('delete from %ss where %s_key=%s'%(self.name, + self.name, self.db.arg), (infoid,)) + + def updateTimestamp(self, infoid): + self.cursor.execute('update %ss set %s_time=%s where %s_key=%s'%( + self.name, self.name, self.db.arg, self.name, self.db.arg), + (time.time(), infoid)) + + def clean(self, now): + """Age sessions, remove when they haven't been used for a week. + """ + old = now - 60*60*24*7 + self.cursor.execute('delete from %ss where %s_time < %s'%(self.name, + self.name, self.db.arg), (old, )) + +class Sessions(BasicDatabase): + name = 'session' + +class OneTimeKeys(BasicDatabase): + name = 'otk' + diff --git a/roundup/cgi/actions.py b/roundup/cgi/actions.py index 46ff0e8..3859f96 100755 --- a/roundup/cgi/actions.py +++ b/roundup/cgi/actions.py @@ -533,7 +533,8 @@ class PassResetAction(Action): if self.form.has_key('otk'): # pull the rego information out of the otk database otk = self.form['otk'].value - uid = self.db.otks.get(otk, 'uid') + otks = self.db.getOTKManager() + uid = otks.get(otk, 'uid') if uid is None: self.client.error_message.append("""Invalid One Time Key! (a Mozilla bug may cause this message to show up erroneously, @@ -549,12 +550,12 @@ class PassResetAction(Action): newpw = password.generatePassword() cl = self.db.user -# XXX we need to make the "default" page be able to display errors! + # XXX we need to make the "default" page be able to display errors! try: # set the password cl.set(uid, password=password.Password(newpw)) # clear the props from the otk database - self.db.otks.destroy(otk) + otks.destroy(otk) self.db.commit() except (ValueError, KeyError), message: self.client.error_message.append(str(message)) @@ -575,8 +576,8 @@ Your password is now: %(password)s if not self.client.standard_message([address], subject, body): return - self.client.ok_message.append('Password reset and email sent to %s' % - address) + self.client.ok_message.append( + 'Password reset and email sent to %s'%address) return # no OTK, so now figure the user @@ -602,8 +603,10 @@ Your password is now: %(password)s # generate the one-time-key and store the props for later otk = ''.join([random.choice(chars) for x in range(32)]) - d = {'uid': uid, self.db.otks.timestamp: time.time()} - self.db.otks.set(otk, **d) + while otks.exists(otk): + otk = ''.join([random.choice(chars) for x in range(32)]) + otks.set(otk, uid=uid) + self.db.commit() # send the email tracker_name = self.db.config.TRACKER_NAME @@ -685,7 +688,6 @@ class RegisterAction(Action): pass # generate the one-time-key and store the props for later - otk = ''.join([random.choice(chars) for x in range(32)]) for propname, proptype in self.db.user.getprops().items(): value = props.get(propname, None) if value is None: @@ -696,8 +698,10 @@ class RegisterAction(Action): props[propname] = str(value) elif isinstance(proptype, hyperdb.Password): props[propname] = str(value) - props[self.db.otks.timestamp] = time.time() - self.db.otks.set(otk, **props) + otks = self.db.getOTKManager() + while otks.exists(otk): + otk = ''.join([random.choice(chars) for x in range(32)]) + otks.set(otk, **props) # send the email tracker_name = self.db.config.TRACKER_NAME diff --git a/roundup/cgi/client.py b/roundup/cgi/client.py index d96aa16..03cfa64 100644 --- a/roundup/cgi/client.py +++ b/roundup/cgi/client.py @@ -1,4 +1,4 @@ -# $Id: client.py,v 1.165 2004-02-25 23:27:54 richard Exp $ +# $Id: client.py,v 1.166 2004-03-18 01:58:46 richard Exp $ """WWW request handler (also used in the stand-alone server). """ @@ -250,8 +250,8 @@ class Client: Note: also cleans One Time Keys, and other "session" based stuff. """ - sessions = self.db.sessions - last_clean = self.db.sessions.get('last_clean', 'last_use') or 0 + sessions = self.db.getSessionManager() + last_clean = sessions.get('last_clean', 'last_use', 0) # time to clean? week = 60*60*24*7 @@ -260,9 +260,10 @@ class Client: if now - last_clean < hour: return - self.db.sessions.clean(now) - self.db.otks.clean(now) - self.db.sessions.set('last_clean', last_use=time.time()) + sessions.clean(now) + self.db.getOTKManager().clean(now) + sessions.set('last_clean', last_use=time.time()) + self.db.commit() def determine_user(self): ''' Determine who the user is @@ -272,7 +273,7 @@ class Client: # make sure we have the session Class self.clean_sessions() - sessions = self.db.sessions + sessions = self.db.getSessionManager() # first up, try the REMOTE_USER var (from HTTP Basic Auth handled # by a front-end HTTP server) @@ -293,7 +294,6 @@ class Client: try: # update the lifetime datestamp sessions.updateTimestamp(self.session) - sessions.commit() user = sessions.get(self.session, 'user') except KeyError: # not valid, ignore id @@ -613,10 +613,9 @@ class Client: self.session = self.session[:-1] # insert the session in the sessiondb - self.db.sessions.set(self.session, user=user, last_use=time.time()) - - # and commit immediately - self.db.sessions.commit() + sessions = self.db.getSessionManager() + sessions.set(self.session, user=user) + self.db.commit() # expire us in a long, long time expire = Cookie._getdate(86400*365) diff --git a/templates/classic/html/style.css b/templates/classic/html/style.css index 7560e6a..b14f63b 100644 --- a/templates/classic/html/style.css +++ b/templates/classic/html/style.css @@ -6,12 +6,10 @@ body.body { margin: 0; } a[href]:hover { - background-color: white; color:blue; text-decoration: underline; } a[href], a[href]:link { - background-color: white; color:blue; text-decoration: none; } diff --git a/test/db_test_base.py b/test/db_test_base.py index b2ffcd1..2405c54 100644 --- a/test/db_test_base.py +++ b/test/db_test_base.py @@ -15,7 +15,7 @@ # BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE, # SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. # -# $Id: db_test_base.py,v 1.16 2004-03-15 05:50:20 richard Exp $ +# $Id: db_test_base.py,v 1.17 2004-03-18 01:58:46 richard Exp $ import unittest, os, shutil, errno, imp, sys, time, pprint @@ -55,8 +55,8 @@ class MyTestCase(unittest.TestCase): def tearDown(self): if hasattr(self, 'db'): self.db.close() - if os.path.exists('_test_dir'): - shutil.rmtree('_test_dir') + if os.path.exists(config.DATABASE): + shutil.rmtree(config.DATABASE) class config: DATABASE='_test_dir' diff --git a/test/session_common.py b/test/session_common.py new file mode 100644 index 0000000..7029528 --- /dev/null +++ b/test/session_common.py @@ -0,0 +1,46 @@ +import os, shutil, unittest + +from db_test_base import config + +class SessionTest(unittest.TestCase): + 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') + self.sessions = self.sessions_module.Sessions(self.db) + self.otks = self.sessions_module.OneTimeKeys(self.db) + + def tearDown(self): + del self.otks + del self.sessions + if hasattr(self, 'db'): + self.db.close() + if os.path.exists(config.DATABASE): + shutil.rmtree(config.DATABASE) + + def testSetSession(self): + self.sessions.set('random_key', text='hello, world!') + self.assertEqual(self.sessions.get('random_key', 'text'), + 'hello, world!') + + def testUpdateSession(self): + self.sessions.set('random_key', text='hello, world!') + self.assertEqual(self.sessions.get('random_key', 'text'), + 'hello, world!') + self.sessions.set('random_key', text='nope') + self.assertEqual(self.sessions.get('random_key', 'text'), 'nope') + + def testSetOTK(self): + assert 0, 'not implemented' + + def testExpiry(self): + assert 0, 'not implemented' + +class DBMTest(SessionTest): + import roundup.backends.sessions_dbm as sessions_module + +class RDBMSTest(SessionTest): + import roundup.backends.sessions_rdbms as sessions_module + diff --git a/test/test_anydbm.py b/test/test_anydbm.py index 77c57b8..cb91c97 100644 --- a/test/test_anydbm.py +++ b/test/test_anydbm.py @@ -15,7 +15,7 @@ # BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE, # SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. # -# $Id: test_anydbm.py,v 1.2 2003-11-14 00:11:19 richard Exp $ +# $Id: test_anydbm.py,v 1.3 2004-03-18 01:58:46 richard Exp $ import unittest, os, shutil, time @@ -39,6 +39,10 @@ class anydbmSchemaTest(anydbmOpener, SchemaTest): class anydbmClassicInitTest(ClassicInitTest): backend = 'anydbm' +from session_common import DBMTest +class anydbmSessionTest(anydbmOpener, DBMTest): + pass + def test_suite(): suite = unittest.TestSuite() print 'Including anydbm tests' @@ -46,6 +50,7 @@ def test_suite(): suite.addTest(unittest.makeSuite(anydbmROTest)) suite.addTest(unittest.makeSuite(anydbmSchemaTest)) suite.addTest(unittest.makeSuite(anydbmClassicInitTest)) + suite.addTest(unittest.makeSuite(anydbmSessionTest)) return suite if __name__ == '__main__': diff --git a/test/test_bsddb.py b/test/test_bsddb.py index 24cde20..ef81a3b 100644 --- a/test/test_bsddb.py +++ b/test/test_bsddb.py @@ -15,7 +15,7 @@ # BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE, # SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. # -# $Id: test_bsddb.py,v 1.2 2003-11-14 00:11:19 richard Exp $ +# $Id: test_bsddb.py,v 1.3 2004-03-18 01:58:46 richard Exp $ import unittest, os, shutil, time @@ -41,6 +41,10 @@ class bsddbSchemaTest(bsddbOpener, SchemaTest): class bsddbClassicInitTest(ClassicInitTest): backend = 'bsddb' +from session_common import DBMTest +class bsddbSessionTest(bsddbOpener, DBMTest): + pass + def test_suite(): suite = unittest.TestSuite() if not hasattr(backends, 'bsddb'): @@ -51,6 +55,7 @@ def test_suite(): suite.addTest(unittest.makeSuite(bsddbROTest)) suite.addTest(unittest.makeSuite(bsddbSchemaTest)) suite.addTest(unittest.makeSuite(bsddbClassicInitTest)) + suite.addTest(unittest.makeSuite(bsddbSessionTest)) return suite if __name__ == '__main__': diff --git a/test/test_bsddb3.py b/test/test_bsddb3.py index 2387cda..3469fdd 100644 --- a/test/test_bsddb3.py +++ b/test/test_bsddb3.py @@ -15,7 +15,7 @@ # BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE, # SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. # -# $Id: test_bsddb3.py,v 1.2 2003-11-14 00:11:19 richard Exp $ +# $Id: test_bsddb3.py,v 1.3 2004-03-18 01:58:46 richard Exp $ import unittest, os, shutil, time @@ -41,6 +41,10 @@ class bsddb3SchemaTest(bsddb3Opener, SchemaTest): class bsddb3ClassicInitTest(ClassicInitTest): backend = 'bsddb3' +from session_common import DBMTest +class bsddb3SessionTest(bsddb3Opener, DBMTest): + pass + def test_suite(): suite = unittest.TestSuite() if not hasattr(backends, 'bsddb3'): @@ -51,6 +55,7 @@ def test_suite(): suite.addTest(unittest.makeSuite(bsddb3ROTest)) suite.addTest(unittest.makeSuite(bsddb3SchemaTest)) suite.addTest(unittest.makeSuite(bsddb3ClassicInitTest)) + suite.addTest(unittest.makeSuite(bsddb3SessionTest)) return suite if __name__ == '__main__': diff --git a/test/test_metakit.py b/test/test_metakit.py index 0610b79..845f4c2 100644 --- a/test/test_metakit.py +++ b/test/test_metakit.py @@ -15,7 +15,7 @@ # BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE, # SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. # -# $Id: test_metakit.py,v 1.3 2004-01-27 18:16:50 wc2so1 Exp $ +# $Id: test_metakit.py,v 1.4 2004-03-18 01:58:46 richard Exp $ import unittest, os, shutil, time, weakref from db_test_base import DBTest, ROTest, SchemaTest, ClassicInitTest, config, password @@ -90,6 +90,10 @@ class metakitSchemaTest(metakitOpener, SchemaTest): class metakitClassicInitTest(ClassicInitTest): backend = 'metakit' +from session_common import DBMTest +class metakitSessionTest(metakitOpener, DBMTest): + pass + def test_suite(): suite = unittest.TestSuite() if not hasattr(backends, 'metakit'): @@ -100,6 +104,7 @@ def test_suite(): suite.addTest(unittest.makeSuite(metakitROTest)) suite.addTest(unittest.makeSuite(metakitSchemaTest)) suite.addTest(unittest.makeSuite(metakitClassicInitTest)) + suite.addTest(unittest.makeSuite(metakitSessionTest)) return suite if __name__ == '__main__': diff --git a/test/test_mysql.py b/test/test_mysql.py index 54f7dcf..1fdc941 100644 --- a/test/test_mysql.py +++ b/test/test_mysql.py @@ -15,7 +15,7 @@ # BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE, # SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. # -# $Id: test_mysql.py,v 1.7 2004-03-12 04:09:00 richard Exp $ +# $Id: test_mysql.py,v 1.8 2004-03-18 01:58:46 richard Exp $ import unittest, os, shutil, time, imp @@ -78,6 +78,15 @@ MYSQL_DATABASE = (MYSQL_DBHOST, MYSQL_DBUSER, MYSQL_DBPASSWORD, MYSQL_DBNAME) ClassicInitTest.tearDown(self) self.nuke_database() +from session_common import RDBMSTest +class mysqlSessionTest(mysqlOpener, RDBMSTest): + def setUp(self): + mysqlOpener.setUp(self) + RDBMSTest.setUp(self) + def tearDown(self): + RDBMSTest.tearDown(self) + mysqlOpener.tearDown(self) + def test_suite(): suite = unittest.TestSuite() if not hasattr(backends, 'mysql'): @@ -97,6 +106,7 @@ def test_suite(): suite.addTest(unittest.makeSuite(mysqlROTest)) suite.addTest(unittest.makeSuite(mysqlSchemaTest)) suite.addTest(unittest.makeSuite(mysqlClassicInitTest)) + suite.addTest(unittest.makeSuite(mysqlSessionTest)) return suite if __name__ == '__main__': diff --git a/test/test_postgresql.py b/test/test_postgresql.py index ac628be..81ce5dc 100644 --- a/test/test_postgresql.py +++ b/test/test_postgresql.py @@ -15,7 +15,7 @@ # BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE, # SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. # -# $Id: test_postgresql.py,v 1.5 2004-03-12 04:09:00 richard Exp $ +# $Id: test_postgresql.py,v 1.6 2004-03-18 01:58:46 richard Exp $ import unittest @@ -92,6 +92,15 @@ class postgresqlClassicInitTest(postgresqlOpener, ClassicInitTest): ClassicInitTest.tearDown(self) postgresqlOpener.tearDown(self) +from session_common import RDBMSTest +class postgresqlSessionTest(postgresqlOpener, RDBMSTest): + def setUp(self): + postgresqlOpener.setUp(self) + RDBMSTest.setUp(self) + def tearDown(self): + RDBMSTest.tearDown(self) + postgresqlOpener.tearDown(self) + def test_suite(): suite = unittest.TestSuite() if not hasattr(backends, 'postgresql'): @@ -106,5 +115,6 @@ def test_suite(): suite.addTest(unittest.makeSuite(postgresqlROTest)) suite.addTest(unittest.makeSuite(postgresqlSchemaTest)) suite.addTest(unittest.makeSuite(postgresqlClassicInitTest)) + suite.addTest(unittest.makeSuite(postgresqlSessionTest)) return suite diff --git a/test/test_sqlite.py b/test/test_sqlite.py index 19e4277..85f5184 100644 --- a/test/test_sqlite.py +++ b/test/test_sqlite.py @@ -15,7 +15,7 @@ # BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE, # SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. # -# $Id: test_sqlite.py,v 1.3 2003-11-14 00:11:19 richard Exp $ +# $Id: test_sqlite.py,v 1.4 2004-03-18 01:58:46 richard Exp $ import unittest, os, shutil, time @@ -41,6 +41,10 @@ class sqliteSchemaTest(sqliteOpener, SchemaTest): class sqliteClassicInitTest(ClassicInitTest): backend = 'sqlite' +from session_common import RDBMSTest +class sqliteSessionTest(sqliteOpener, RDBMSTest): + pass + def test_suite(): suite = unittest.TestSuite() from roundup import backends @@ -52,6 +56,7 @@ def test_suite(): suite.addTest(unittest.makeSuite(sqliteROTest)) suite.addTest(unittest.makeSuite(sqliteSchemaTest)) suite.addTest(unittest.makeSuite(sqliteClassicInitTest)) + suite.addTest(unittest.makeSuite(sqliteSessionTest)) return suite if __name__ == '__main__': -- 2.30.2