Came up with a handy decorator for asserting that a given method does not modify the ZODB.
It can be used in tests to ensure that a given test does not change the ZODB in any way (for example views).
I came up with it because a regression was discovered where a read-only view was modifying some things in ZODB causing ConflictErrors.To ensure it would not come up again I had to come up with a failing test.
def readonly(fun):
"""
requires that self is at least a PortalTestCase
Usage hint: Use a savepoint to reset the changed object listing.
"""
def decorated(self, *args, **kwargs):
# this resets registered objects
transaction.savepoint()
before = list(self.portal._p_jar._registered_objects)
result = fun(self, *args, **kwargs)
after = self.portal._p_jar._registered_objects
if before != after:
s = SequenceMatcher(a=before, b=after)
for tag, i1, i2, j1, j2 in s.get_opcodes():
print ("%7s a[%d:%d] (%s) b[%d:%d] (%s)" % (tag, i1, i2, before[i1:i2], j1, j2, after[j1:j2]))
raise RuntimeError("ZODB was changed")
return result
return decorated
An example of usage:
class ReadonlyTests(YourTestCase):
@readonly
# This one will fail
def test_isnt_readonly(self):
self.portal.invokeFactory('Document', 'test')
self.portal.test.setTitle('foo')
@readonly
def test_is_readonly(self):
pass
No comments:
Post a Comment