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