]> git.notmuchmail.org Git - notmuch/blob - test/atomicity.py
Merge branch 'release'
[notmuch] / test / atomicity.py
1 # This gdb Python script runs notmuch new and simulates killing and
2 # restarting notmuch new after every Xapian commit.  To simulate this
3 # more efficiently, this script runs notmuch new and, immediately
4 # after every Xapian commit, it *pauses* the running notmuch new,
5 # copies the entire database and maildir to a snapshot directory, and
6 # executes a full notmuch new on that snapshot, comparing the final
7 # results with the expected output.  It can then resume the paused
8 # notmuch new, which is still running on the original maildir, and
9 # repeat this process.
10
11 import gdb
12 import os
13 import glob
14 import shutil
15 import subprocess
16
17 gdb.execute('set args new')
18
19 # Make Xapian commit after every operation instead of batching
20 gdb.execute('set environment XAPIAN_FLUSH_THRESHOLD = 1')
21
22 maildir = os.environ['MAIL_DIR']
23
24 # Trap calls to rename, which happens just before Xapian commits
25 class RenameBreakpoint(gdb.Breakpoint):
26     def __init__(self, *args, **kwargs):
27         super(RenameBreakpoint, self).__init__(*args, **kwargs)
28         self.last_inodes = {}
29         self.n = 0
30
31     def stop(self):
32         xapiandir = '%s/.notmuch/xapian' % maildir
33         if os.path.isfile('%s/iamchert' % xapiandir):
34             # As an optimization, only consider snapshots after a
35             # Xapian has really committed.  The chert backend
36             # overwrites record.base? as the last step in the commit,
37             # so keep an eye on their inumbers.
38             inodes = {}
39             for path in glob.glob('%s/record.base*' % xapiandir):
40                 inodes[path] = os.stat(path).st_ino
41             if inodes == self.last_inodes:
42                 # Continue
43                 return False
44             self.last_inodes = inodes
45
46         # Save a backtrace in case the test does fail
47         backtrace = gdb.execute('backtrace', to_string=True)
48         open('backtrace.%d' % self.n, 'w').write(backtrace)
49
50         # Snapshot the database
51         shutil.rmtree('%s.snap/.notmuch' % maildir)
52         shutil.copytree('%s/.notmuch' % maildir, '%s.snap/.notmuch' % maildir)
53         # Restore the mtime of $MAIL_DIR.snap/
54         shutil.copystat('%s/.notmuch' % maildir, '%s.snap/.notmuch' % maildir)
55
56         # Run notmuch new to completion on the snapshot
57         env = os.environ.copy()
58         env.update(NOTMUCH_CONFIG=os.environ['NOTMUCH_CONFIG'] + '.snap',
59                    XAPIAN_FLUSH_THRESHOLD='1000')
60         subprocess.check_call(
61             ['notmuch', 'new'], env=env, stdout=open('/dev/null', 'w'))
62         subprocess.check_call(
63             ['notmuch', 'search', '*'], env=env,
64             stdout=open('search.%d' % self.n, 'w'))
65
66         # Tell the shell how far we've gotten
67         open('outcount', 'w').write(str(self.n + 1))
68
69         # Continue
70         self.n += 1
71         return False
72 RenameBreakpoint('rename')
73
74 try:
75     gdb.execute('run')
76 except Exception:
77     import traceback
78     raise SystemExit(traceback.format_exc())