aboutsummaryrefslogtreecommitdiffhomepage
path: root/test/atomicity.py
diff options
context:
space:
mode:
Diffstat (limited to 'test/atomicity.py')
-rw-r--r--test/atomicity.py71
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')