diff options
Diffstat (limited to 'test/atomicity.py')
-rw-r--r-- | test/atomicity.py | 71 |
1 files changed, 71 insertions, 0 deletions
diff --git a/test/atomicity.py b/test/atomicity.py new file mode 100644 index 00000000..01a42051 --- /dev/null +++ b/test/atomicity.py @@ -0,0 +1,71 @@ +# This gdb Python script runs notmuch new and simulates killing and +# restarting notmuch new after every Xapian commit. To simulate this +# more efficiently, this script runs notmuch new and, immediately +# after every Xapian commit, it *pauses* the running notmuch new, +# copies the entire database and maildir to a snapshot directory, and +# executes a full notmuch new on that snapshot, comparing the final +# results with the expected output. It can then resume the paused +# notmuch new, which is still running on the original maildir, and +# repeat this process. + +import gdb +import os +import glob +import shutil +import subprocess + +gdb.execute('set args new') + +# Make Xapian commit after every operation instead of batching +gdb.execute('set environment XAPIAN_FLUSH_THRESHOLD = 1') + +maildir = os.environ['MAIL_DIR'] + +# Trap calls to rename, which happens just before Xapian commits +class RenameBreakpoint(gdb.Breakpoint): + def __init__(self, *args, **kwargs): + super(RenameBreakpoint, self).__init__(*args, **kwargs) + self.last_inodes = {} + self.n = 0 + + def stop(self): + # As an optimization, only consider snapshots after a Xapian + # has really committed. Xapian overwrites record.base? as the + # last step in the commit, so keep an eye on their inumbers. + inodes = {} + for path in glob.glob('%s/.notmuch/xapian/record.base*' % maildir): + inodes[path] = os.stat(path).st_ino + if inodes == self.last_inodes: + # Continue + return False + self.last_inodes = inodes + + # Save a backtrace in case the test does fail + backtrace = gdb.execute('backtrace', to_string=True) + open('backtrace.%d' % self.n, 'w').write(backtrace) + + # Snapshot the database + shutil.rmtree('%s.snap/.notmuch' % maildir) + shutil.copytree('%s/.notmuch' % maildir, '%s.snap/.notmuch' % maildir) + # Restore the mtime of $MAIL_DIR.snap/ + shutil.copystat('%s/.notmuch' % maildir, '%s.snap/.notmuch' % maildir) + + # Run notmuch new to completion on the snapshot + env = os.environ.copy() + env.update(NOTMUCH_CONFIG=os.environ['NOTMUCH_CONFIG'] + '.snap', + XAPIAN_FLUSH_THRESHOLD='1000') + subprocess.check_call( + ['notmuch', 'new'], env=env, stdout=open('/dev/null', 'w')) + subprocess.check_call( + ['notmuch', 'search', '*'], env=env, + stdout=open('search.%d' % self.n, 'w')) + + # Tell the shell how far we've gotten + open('outcount', 'w').write(str(self.n + 1)) + + # Continue + self.n += 1 + return False +RenameBreakpoint('rename') + +gdb.execute('run') |