[Checkins] SVN: relstorage/trunk/ Implemented the storage iterator
protocol, making it possible to copy
Shane Hathaway
shane at hathawaymix.org
Sun Feb 17 01:49:07 EST 2008
Log message for revision 83973:
Implemented the storage iterator protocol, making it possible to copy
transactions to and from FileStorage and other RelStorage instances.
Added tests of this capability.
Changed:
U relstorage/trunk/CHANGELOG.txt
U relstorage/trunk/relstorage/adapters/common.py
U relstorage/trunk/relstorage/adapters/mysql.py
U relstorage/trunk/relstorage/adapters/oracle.py
U relstorage/trunk/relstorage/adapters/postgresql.py
U relstorage/trunk/relstorage/relstorage.py
U relstorage/trunk/relstorage/tests/reltestbase.py
U relstorage/trunk/relstorage/tests/testmysql.py
U relstorage/trunk/relstorage/tests/testoracle.py
U relstorage/trunk/relstorage/tests/testpostgresql.py
-=-
Modified: relstorage/trunk/CHANGELOG.txt
===================================================================
--- relstorage/trunk/CHANGELOG.txt 2008-02-17 02:05:07 UTC (rev 83972)
+++ relstorage/trunk/CHANGELOG.txt 2008-02-17 06:49:05 UTC (rev 83973)
@@ -24,7 +24,7 @@
- Stored objects are now buffered in a database table rather than a file.
- Stopped using the LISTEN and NOTIFY statements in PostgreSQL since
- they are not reliable enough.
+ they are not strictly transactional in the sense we require.
- Started using a prepared statement in PostgreSQL for getting the
newest transaction ID quickly.
@@ -38,7 +38,10 @@
polls, but it also increases the potential for conflict errors on
servers with high write volume.
+- Implemented the storage iterator protocol, making it possible to copy
+ transactions to and from FileStorage and other RelStorage instances.
+
PGStorage 0.4
- Began using the PostgreSQL LISTEN and NOTIFY statements as a shortcut
Modified: relstorage/trunk/relstorage/adapters/common.py
===================================================================
--- relstorage/trunk/relstorage/adapters/common.py 2008-02-17 02:05:07 UTC (rev 83972)
+++ relstorage/trunk/relstorage/adapters/common.py 2008-02-17 06:49:05 UTC (rev 83973)
@@ -38,6 +38,8 @@
'pack_tid': '%(pack_tid)s',
'undo_tid': '%(undo_tid)s',
'self_tid': '%(self_tid)s',
+ 'min_tid': '%(min_tid)s',
+ 'max_tid': '%(max_tid)s',
}
_scripts = {
@@ -121,8 +123,9 @@
def iter_transactions(self, cursor):
- """Iterate over the transaction log.
+ """Iterate over the transaction log, newest first.
+ Skips packed transactions.
Yields (tid, username, description, extension) for each transaction.
"""
stmt = """
@@ -136,6 +139,30 @@
return iter(cursor)
+ def iter_transactions_range(self, cursor, start=None, stop=None):
+ """Iterate over the transactions in the given range, oldest first.
+
+ Includes packed transactions.
+ Yields (tid, packed, username, description, extension)
+ for each transaction.
+ """
+ stmt = """
+ SELECT tid,
+ CASE WHEN packed = %(TRUE)s THEN 1 ELSE 0 END,
+ username, description, extension
+ FROM transaction
+ WHERE tid >= 0
+ """
+ if start is not None:
+ stmt += " AND tid >= %(min_tid)s"
+ if stop is not None:
+ stmt += " AND tid <= %(max_tid)s"
+ stmt += " ORDER BY tid"
+ self._run_script_stmt(cursor, stmt,
+ {'min_tid': start, 'max_tid': stop})
+ return iter(cursor)
+
+
def iter_object_history(self, cursor, oid):
"""Iterate over an object's history.
@@ -162,6 +189,25 @@
return iter(cursor)
+ def iter_objects(self, cursor, tid):
+ """Iterate over object states in a transaction.
+
+ Yields (oid, prev_tid, state) for each object state.
+ """
+ stmt = """
+ SELECT zoid, state
+ FROM object_state
+ WHERE tid = %(tid)s
+ ORDER BY zoid
+ """
+ self._run_script_stmt(cursor, stmt, {'tid': tid})
+ for oid, state in cursor:
+ if hasattr(state, 'read'):
+ # Oracle
+ state = state.read()
+ yield oid, state
+
+
def verify_undoable(self, cursor, undo_tid):
"""Raise UndoError if it is not safe to undo the specified txn."""
stmt = """
@@ -255,11 +301,11 @@
AND prev.tid = temp_undo.prev_tid);
-- List the changed OIDs.
- SELECT zoid FROM object_state WHERE tid = %(undo_tid)s
+ SELECT zoid FROM temp_undo
"""
self._run_script(cursor, stmt,
{'undo_tid': undo_tid, 'self_tid': self_tid})
- res = [oid_int for (oid_int,) in cursor]
+ res = [oid for (oid,) in cursor]
stmt = self._scripts['reset_temp_undo']
if stmt:
@@ -495,7 +541,7 @@
add_rows = [] # [(from_oid, tid, to_oid)]
for from_oid, state in cursor:
if hasattr(state, 'read'):
- # cx_Oracle detail
+ # Oracle
state = state.read()
if state:
to_oids = get_references(str(state))
Modified: relstorage/trunk/relstorage/adapters/mysql.py
===================================================================
--- relstorage/trunk/relstorage/adapters/mysql.py 2008-02-17 02:05:07 UTC (rev 83972)
+++ relstorage/trunk/relstorage/adapters/mysql.py 2008-02-17 06:49:05 UTC (rev 83973)
@@ -379,6 +379,21 @@
"""
cursor.execute(stmt, (prev_tid, md5sum, MySQLdb.Binary(data), oid))
+ def restore(self, cursor, oid, tid, md5sum, data):
+ """Store an object directly, without conflict detection.
+
+ Used for copying transactions into this database.
+ """
+ stmt = """
+ INSERT INTO object_state (zoid, tid, prev_tid, md5, state)
+ VALUES (%s, %s,
+ COALESCE((SELECT tid FROM current_object WHERE zoid = %s), 0),
+ %s, %s)
+ """
+ if data is not None:
+ data = MySQLdb.Binary(data)
+ cursor.execute(stmt, (oid, tid, oid, md5sum, data))
+
def start_commit(self, cursor):
"""Prepare to commit."""
# Hold commit_lock to prevent concurrent commits.
@@ -411,15 +426,16 @@
timestamp = now
return tid, timestamp
- def add_transaction(self, cursor, tid, username, description, extension):
+ def add_transaction(self, cursor, tid, username, description, extension,
+ packed=False):
"""Add a transaction."""
stmt = """
INSERT INTO transaction
- (tid, username, description, extension)
- VALUES (%s, %s, %s, %s)
+ (tid, packed, username, description, extension)
+ VALUES (%s, %s, %s, %s, %s)
"""
cursor.execute(stmt, (
- tid, username, description, MySQLdb.Binary(extension)))
+ tid, packed, username, description, MySQLdb.Binary(extension)))
def detect_conflict(self, cursor):
"""Find one conflict in the data about to be committed.
Modified: relstorage/trunk/relstorage/adapters/oracle.py
===================================================================
--- relstorage/trunk/relstorage/adapters/oracle.py 2008-02-17 02:05:07 UTC (rev 83972)
+++ relstorage/trunk/relstorage/adapters/oracle.py 2008-02-17 06:49:05 UTC (rev 83973)
@@ -37,6 +37,8 @@
'pack_tid': ':pack_tid',
'undo_tid': ':undo_tid',
'self_tid': ':self_tid',
+ 'min_tid': ':min_tid',
+ 'max_tid': ':max_tid',
}
_scripts = {
@@ -103,8 +105,8 @@
CREATE TABLE transaction (
tid NUMBER(20) NOT NULL PRIMARY KEY,
packed CHAR DEFAULT 'N' CHECK (packed IN ('N', 'Y')),
- username VARCHAR2(255) NOT NULL,
- description VARCHAR2(4000) NOT NULL,
+ username VARCHAR2(255),
+ description VARCHAR2(4000),
extension RAW(2000)
);
@@ -458,6 +460,22 @@
cursor.execute(stmt, oid=oid, prev_tid=prev_tid,
md5sum=md5sum, data=cx_Oracle.Binary(data))
+ def restore(self, cursor, oid, tid, md5sum, data):
+ """Store an object directly, without conflict detection.
+
+ Used for copying transactions into this database.
+ """
+ cursor.setinputsizes(data=cx_Oracle.BLOB)
+ stmt = """
+ INSERT INTO object_state (zoid, tid, prev_tid, md5, state)
+ VALUES (:oid, :tid,
+ COALESCE((SELECT tid FROM current_object WHERE zoid = :oid), 0),
+ :md5sum, :data)
+ """
+ if data is not None:
+ data = cx_Oracle.Binary(data)
+ cursor.execute(stmt, oid=oid, tid=tid, md5sum=md5sum, data=data)
+
def start_commit(self, cursor):
"""Prepare to commit."""
# Hold commit_lock to prevent concurrent commits
@@ -489,15 +507,16 @@
tid, now = cursor.fetchone()
return tid, self._parse_dsinterval(now)
- def add_transaction(self, cursor, tid, username, description, extension):
+ def add_transaction(self, cursor, tid, username, description, extension,
+ packed=False):
"""Add a transaction."""
stmt = """
INSERT INTO transaction
- (tid, username, description, extension)
- VALUES (:1, :2, :3, :4)
+ (tid, packed, username, description, extension)
+ VALUES (:1, :2, :3, :4, :5)
"""
cursor.execute(stmt, (
- tid, username or '-', description or '-',
+ tid, packed and 'Y' or 'N', username, description,
cx_Oracle.Binary(extension)))
def detect_conflict(self, cursor):
Modified: relstorage/trunk/relstorage/adapters/postgresql.py
===================================================================
--- relstorage/trunk/relstorage/adapters/postgresql.py 2008-02-17 02:05:07 UTC (rev 83972)
+++ relstorage/trunk/relstorage/adapters/postgresql.py 2008-02-17 06:49:05 UTC (rev 83973)
@@ -397,6 +397,21 @@
"""
cursor.execute(stmt, (prev_tid, md5sum, encodestring(data), oid))
+ def restore(self, cursor, oid, tid, md5sum, data):
+ """Store an object directly, without conflict detection.
+
+ Used for copying transactions into this database.
+ """
+ stmt = """
+ INSERT INTO object_state (zoid, tid, prev_tid, md5, state)
+ VALUES (%s, %s,
+ COALESCE((SELECT tid FROM current_object WHERE zoid = %s), 0),
+ %s, decode(%s, 'base64'))
+ """
+ if data is not None:
+ data = encodestring(data)
+ cursor.execute(stmt, (oid, tid, oid, md5sum, data))
+
def start_commit(self, cursor):
"""Prepare to commit."""
# Hold commit_lock to prevent concurrent commits
@@ -423,15 +438,16 @@
assert cursor.rowcount == 1
return cursor.fetchone()
- def add_transaction(self, cursor, tid, username, description, extension):
+ def add_transaction(self, cursor, tid, username, description, extension,
+ packed=False):
"""Add a transaction."""
stmt = """
INSERT INTO transaction
- (tid, username, description, extension)
- VALUES (%s, %s, %s, decode(%s, 'base64'))
+ (tid, packed, username, description, extension)
+ VALUES (%s, %s, %s, %s, decode(%s, 'base64'))
"""
cursor.execute(stmt, (
- tid, username, description, encodestring(extension)))
+ tid, packed, username, description, encodestring(extension)))
def detect_conflict(self, cursor):
"""Find one conflict in the data about to be committed.
Modified: relstorage/trunk/relstorage/relstorage.py
===================================================================
--- relstorage/trunk/relstorage/relstorage.py 2008-02-17 02:05:07 UTC (rev 83972)
+++ relstorage/trunk/relstorage/relstorage.py 2008-02-17 06:49:05 UTC (rev 83973)
@@ -270,6 +270,39 @@
self._lock_release()
+ def restore(self, oid, serial, data, version, prev_txn, transaction):
+ # Like store(), but used for importing transactions. See the
+ # comments in FileStorage.restore(). The prev_txn optimization
+ # is not used.
+ if self._is_read_only:
+ raise POSException.ReadOnlyError()
+ if transaction is not self._transaction:
+ raise POSException.StorageTransactionError(self, transaction)
+ if version:
+ raise POSException.Unsupported("Versions aren't supported")
+
+ assert self._tid is not None
+ assert self._prepared_txn is None
+ if data is not None:
+ md5sum = md5.new(data).hexdigest()
+ else:
+ # George Bailey object
+ md5sum = None
+
+ adapter = self._adapter
+ cursor = self._store_cursor
+ assert cursor is not None
+ oid_int = u64(oid)
+ tid_int = u64(serial)
+
+ self._lock_acquire()
+ try:
+ # save the data. Note that md5sum and data can be None.
+ adapter.restore(cursor, oid_int, tid_int, md5sum, data)
+ finally:
+ self._lock_release()
+
+
def tpc_begin(self, transaction, tid=None, status=' '):
if self._is_read_only:
raise POSException.ReadOnlyError()
@@ -309,10 +342,12 @@
if tid is not None:
# get the commit lock and add the transaction now
+ packed = (status == 'p')
adapter.start_commit(cursor)
tid_int = u64(tid)
try:
- adapter.add_transaction(cursor, tid_int, user, desc, ext)
+ adapter.add_transaction(
+ cursor, tid_int, user, desc, ext, packed)
except:
self._drop_store_connection()
raise
@@ -518,8 +553,8 @@
tid = p64(tid_int)
d = {'id': base64.encodestring(tid)[:-1],
'time': TimeStamp(tid).timeTime(),
- 'user_name': user,
- 'description': desc}
+ 'user_name': user or '',
+ 'description': desc or ''}
if ext:
ext = str(ext)
if ext:
@@ -553,8 +588,8 @@
else:
d = {}
d.update({"time": TimeStamp(tid).timeTime(),
- "user_name": username,
- "description": description,
+ "user_name": username or '',
+ "description": description or '',
"tid": tid,
"version": '',
"size": length,
@@ -666,7 +701,10 @@
# The tests depend on this.
self._rollback_load_connection()
+ def iterator(self, start=None, stop=None):
+ return TransactionIterator(self._adapter, start, stop)
+
class BoundRelStorage(RelStorage):
"""Storage to a database, bound to a particular ZODB.Connection."""
@@ -769,3 +807,93 @@
# wants to see the new state, it should call sync().
pass
+
+class TransactionIterator(object):
+ """Iterate over the transactions in a RelStorage instance."""
+
+ def __init__(self, adapter, start, stop):
+ self._adapter = adapter
+ self._conn, self._cursor = self._adapter.open_for_load()
+ self._closed = False
+
+ if start is not None:
+ start_int = u64(start)
+ else:
+ start_int = 1
+ if stop is not None:
+ stop_int = u64(stop)
+ else:
+ stop_int = None
+
+ # _transactions: [(tid, packed, username, description, extension)]
+ self._transactions = list(adapter.iter_transactions_range(
+ self._cursor, start_int, stop_int))
+ self._index = 0
+
+ def close(self):
+ self._adapter.close(self._conn, self._cursor)
+ self._closed = True
+
+ def iterator(self):
+ return self
+
+ def __len__(self):
+ return len(self._transactions)
+
+ def __getitem__(self, n):
+ self._index = n
+ return self.next()
+
+ def next(self):
+ if self._closed:
+ raise IOError("TransactionIterator already closed")
+ params = self._transactions[self._index]
+ res = RecordIterator(self, *params)
+ self._index += 1
+ return res
+
+
+class RecordIterator(object):
+ """Iterate over the objects in a transaction."""
+ def __init__(self, trans_iter, tid_int, packed, user, desc, ext):
+ self.tid = p64(tid_int)
+ self.status = packed and 'p' or ' '
+ self.user = user or ''
+ self.description = desc or ''
+ if ext:
+ self._extension = cPickle.loads(ext)
+ else:
+ self._extension = {}
+
+ cursor = trans_iter._cursor
+ adapter = trans_iter._adapter
+ self._records = list(adapter.iter_objects(cursor, tid_int))
+ self._index = 0
+
+ def __len__(self):
+ return len(self._records)
+
+ def __getitem__(self, n):
+ self._index = n
+ return self.next()
+
+ def next(self):
+ params = self._records[self._index]
+ res = Record(self.tid, *params)
+ self._index += 1
+ return res
+
+
+class Record(object):
+ """An object state in a transaction"""
+ version = ''
+ data_txn = None
+
+ def __init__(self, tid, oid_int, data):
+ self.tid = tid
+ self.oid = p64(oid_int)
+ if data is not None:
+ self.data = str(data)
+ else:
+ self.data = None
+
Modified: relstorage/trunk/relstorage/tests/reltestbase.py
===================================================================
--- relstorage/trunk/relstorage/tests/reltestbase.py 2008-02-17 02:05:07 UTC (rev 83972)
+++ relstorage/trunk/relstorage/tests/reltestbase.py 2008-02-17 06:49:05 UTC (rev 83973)
@@ -17,15 +17,18 @@
from relstorage.relstorage import RelStorage
from ZODB.DB import DB
+from ZODB.utils import p64
+from ZODB.FileStorage import FileStorage
from persistent.mapping import PersistentMapping
import transaction
from ZODB.tests import StorageTestBase, BasicStorage, \
TransactionalUndoStorage, PackableStorage, \
Synchronization, ConflictResolution, HistoryStorage, \
- RevisionStorage, PersistentStorage, \
- MTStorage, ReadOnlyStorage
+ IteratorStorage, RevisionStorage, PersistentStorage, \
+ MTStorage, ReadOnlyStorage, RecoveryStorage
+from ZODB.tests.MinPO import MinPO
from ZODB.tests.StorageTestBase import zodb_unpickle, zodb_pickle
@@ -58,6 +61,8 @@
Synchronization.SynchronizedStorage,
ConflictResolution.ConflictResolvingStorage,
HistoryStorage.HistoryStorage,
+ IteratorStorage.IteratorStorage,
+ IteratorStorage.ExtendedIteratorStorage,
PersistentStorage.PersistentStorage,
MTStorage.MTStorage,
ReadOnlyStorage.ReadOnlyStorage
@@ -260,3 +265,178 @@
finally:
db.close()
+
+
+ def checkTransactionalUndoIterator(self):
+ # this test overrides the broken version in TransactionalUndoStorage.
+
+ s = self._storage
+
+ BATCHES = 4
+ OBJECTS = 4
+
+ orig = []
+ for i in range(BATCHES):
+ t = transaction.Transaction()
+ tid = p64(i + 1)
+ s.tpc_begin(t, tid)
+ txn_orig = []
+ for j in range(OBJECTS):
+ oid = s.new_oid()
+ obj = MinPO(i * OBJECTS + j)
+ revid = s.store(oid, None, zodb_pickle(obj), '', t)
+ txn_orig.append((tid, oid, revid))
+ serials = s.tpc_vote(t)
+ if not serials:
+ orig.extend(txn_orig)
+ else:
+ # The storage provided revision IDs after the vote
+ serials = dict(serials)
+ for tid, oid, revid in txn_orig:
+ self.assertEqual(revid, None)
+ orig.append((tid, oid, serials[oid]))
+ s.tpc_finish(t)
+
+ i = 0
+ for tid, oid, revid in orig:
+ self._dostore(oid, revid=revid, data=MinPO(revid),
+ description="update %s" % i)
+
+ # Undo the OBJECTS transactions that modified objects created
+ # in the ith original transaction.
+
+ def undo(i):
+ info = s.undoInfo()
+ t = transaction.Transaction()
+ s.tpc_begin(t)
+ base = i * OBJECTS + i
+ for j in range(OBJECTS):
+ tid = info[base + j]['id']
+ s.undo(tid, t)
+ s.tpc_vote(t)
+ s.tpc_finish(t)
+
+ for i in range(BATCHES):
+ undo(i)
+
+ # There are now (2 + OBJECTS) * BATCHES transactions:
+ # BATCHES original transactions, followed by
+ # OBJECTS * BATCHES modifications, followed by
+ # BATCHES undos
+
+ iter = s.iterator()
+ offset = 0
+
+ eq = self.assertEqual
+
+ for i in range(BATCHES):
+ txn = iter[offset]
+ offset += 1
+
+ tid = p64(i + 1)
+ eq(txn.tid, tid)
+
+ L1 = [(rec.oid, rec.tid, rec.data_txn) for rec in txn]
+ L2 = [(oid, revid, None) for _tid, oid, revid in orig
+ if _tid == tid]
+
+ eq(L1, L2)
+
+ for i in range(BATCHES * OBJECTS):
+ txn = iter[offset]
+ offset += 1
+ eq(len([rec for rec in txn if rec.data_txn is None]), 1)
+
+ for i in range(BATCHES):
+ txn = iter[offset]
+ offset += 1
+
+ # The undos are performed in reverse order.
+ otid = p64(BATCHES - i)
+ L1 = [rec.oid for rec in txn]
+ L2 = [oid for _tid, oid, revid in orig if _tid == otid]
+ L1.sort()
+ L2.sort()
+ eq(L1, L2)
+
+ self.assertRaises(IndexError, iter.__getitem__, offset)
+
+
+class IteratorDeepCompareUnordered:
+ # Like IteratorDeepCompare, but compensates for OID order
+ # differences in transactions.
+ def compare(self, storage1, storage2):
+ eq = self.assertEqual
+ iter1 = storage1.iterator()
+ iter2 = storage2.iterator()
+ for txn1, txn2 in zip(iter1, iter2):
+ eq(txn1.tid, txn2.tid)
+ eq(txn1.status, txn2.status)
+ eq(txn1.user, txn2.user)
+ eq(txn1.description, txn2.description)
+ eq(txn1._extension, txn2._extension)
+ recs1 = [(r.oid, r) for r in txn1]
+ recs1.sort()
+ recs2 = [(r.oid, r) for r in txn2]
+ recs2.sort()
+ for (oid1, rec1), (oid2, rec2) in zip(recs1, recs2):
+ eq(rec1.oid, rec2.oid)
+ eq(rec1.tid, rec2.tid)
+ eq(rec1.version, rec2.version)
+ eq(rec1.data, rec2.data)
+ # Make sure there are no more records left in rec1 and rec2,
+ # meaning they were the same length.
+ self.assertRaises(IndexError, txn1.next)
+ self.assertRaises(IndexError, txn2.next)
+ # Make sure ther are no more records left in txn1 and txn2, meaning
+ # they were the same length
+ self.assertRaises(IndexError, iter1.next)
+ self.assertRaises(IndexError, iter2.next)
+ iter1.close()
+ iter2.close()
+
+
+
+class RecoveryStorageSubset(IteratorDeepCompareUnordered):
+ # The subset of RecoveryStorage tests that do not rely on version
+ # support.
+ pass
+
+for name, attr in RecoveryStorage.RecoveryStorage.__dict__.items():
+ if 'check' in name and 'Version' not in name:
+ setattr(RecoveryStorageSubset, name, attr)
+
+
+class ToFileStorage(BaseRelStorageTests, RecoveryStorageSubset):
+ def setUp(self):
+ self.open(create=1)
+ self._storage._zap()
+ self._dst = FileStorage("Dest.fs", create=True)
+
+ def tearDown(self):
+ self._storage.close()
+ self._dst.close()
+ self._storage.cleanup()
+ self._dst.cleanup()
+
+ def new_dest(self):
+ return FileStorage('Dest.fs')
+
+
+class FromFileStorage(BaseRelStorageTests, RecoveryStorageSubset):
+ def setUp(self):
+ self.open(create=1)
+ self._storage._zap()
+ self._dst = self._storage
+ self._storage = FileStorage("Source.fs", create=True)
+
+ def tearDown(self):
+ self._storage.close()
+ self._dst.close()
+ self._storage.cleanup()
+ self._dst.cleanup()
+
+ def new_dest(self):
+ return self._dst
+
+
Modified: relstorage/trunk/relstorage/tests/testmysql.py
===================================================================
--- relstorage/trunk/relstorage/tests/testmysql.py 2008-02-17 02:05:07 UTC (rev 83972)
+++ relstorage/trunk/relstorage/tests/testmysql.py 2008-02-17 06:49:05 UTC (rev 83973)
@@ -16,17 +16,28 @@
import logging
import unittest
-from reltestbase import RelStorageTests
+import reltestbase
from relstorage.adapters.mysql import MySQLAdapter
-class MySQLTests(RelStorageTests):
+class UseMySQLAdapter:
def make_adapter(self):
return MySQLAdapter(db='relstoragetest')
+class MySQLTests(UseMySQLAdapter, reltestbase.RelStorageTests):
+ pass
+
+class MySQLToFile(UseMySQLAdapter, reltestbase.ToFileStorage):
+ pass
+
+class FileToMySQL(UseMySQLAdapter, reltestbase.FromFileStorage):
+ pass
+
+
def test_suite():
suite = unittest.TestSuite()
- suite.addTest(unittest.makeSuite(MySQLTests, "check"))
+ for klass in [MySQLTests, MySQLToFile, FileToMySQL]:
+ suite.addTest(unittest.makeSuite(klass, "check"))
return suite
if __name__=='__main__':
Modified: relstorage/trunk/relstorage/tests/testoracle.py
===================================================================
--- relstorage/trunk/relstorage/tests/testoracle.py 2008-02-17 02:05:07 UTC (rev 83972)
+++ relstorage/trunk/relstorage/tests/testoracle.py 2008-02-17 06:49:05 UTC (rev 83973)
@@ -18,7 +18,7 @@
import re
import unittest
-from reltestbase import RelStorageTests
+import reltestbase
from relstorage.adapters.oracle import OracleAdapter
@@ -38,17 +38,27 @@
return user, password, dsn
-class OracleTests(RelStorageTests):
+class UseOracleAdapter:
def make_adapter(self):
user, password, dsn = getOracleParams()
return OracleAdapter(user, password, dsn)
+class OracleTests(UseOracleAdapter, reltestbase.RelStorageTests):
+ pass
+
+class OracleToFile(UseOracleAdapter, reltestbase.ToFileStorage):
+ pass
+
+class FileToOracle(UseOracleAdapter, reltestbase.FromFileStorage):
+ pass
+
+
def test_suite():
suite = unittest.TestSuite()
- suite.addTest(unittest.makeSuite(OracleTests, "check"))
+ for klass in [OracleTests, OracleToFile, FileToOracle]:
+ suite.addTest(unittest.makeSuite(klass, "check"))
return suite
if __name__=='__main__':
logging.basicConfig()
unittest.main(defaultTest="test_suite")
-
Modified: relstorage/trunk/relstorage/tests/testpostgresql.py
===================================================================
--- relstorage/trunk/relstorage/tests/testpostgresql.py 2008-02-17 02:05:07 UTC (rev 83972)
+++ relstorage/trunk/relstorage/tests/testpostgresql.py 2008-02-17 06:49:05 UTC (rev 83973)
@@ -16,17 +16,28 @@
import logging
import unittest
-from reltestbase import RelStorageTests
+import reltestbase
from relstorage.adapters.postgresql import PostgreSQLAdapter
-class PostgreSQLTests(RelStorageTests):
+class UsePostgreSQLAdapter:
def make_adapter(self):
return PostgreSQLAdapter('dbname=relstoragetest')
+class PostgreSQLTests(UsePostgreSQLAdapter, reltestbase.RelStorageTests):
+ pass
+
+class PGToFile(UsePostgreSQLAdapter, reltestbase.ToFileStorage):
+ pass
+
+class FileToPG(UsePostgreSQLAdapter, reltestbase.FromFileStorage):
+ pass
+
+
def test_suite():
suite = unittest.TestSuite()
- suite.addTest(unittest.makeSuite(PostgreSQLTests, "check"))
+ for klass in [PostgreSQLTests, PGToFile, FileToPG]:
+ suite.addTest(unittest.makeSuite(klass, "check"))
return suite
if __name__=='__main__':
More information about the Checkins
mailing list