]> rtime.felk.cvut.cz Git - notmuch.git/blobdiff - test/atomicity.py
test: Port atomicity test to Python
[notmuch.git] / test / atomicity.py
diff --git a/test/atomicity.py b/test/atomicity.py
new file mode 100644 (file)
index 0000000..01a4205
--- /dev/null
@@ -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')