notmuch.git  about / heads / tags
Unnamed repository; edit this file 'description' to name the repository.
blob 389517e1571e0b6590d068ef9cc9c13a98b44a40 2942 bytes (raw)
name: test/atomicity.py 	 # note: path name is non-authoritative(*)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
 
# 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):
        xapiandir = '%s/.notmuch/xapian' % maildir
        if os.path.isfile('%s/iamchert' % xapiandir):
            # As an optimization, only consider snapshots after a
            # Xapian has really committed.  The chert backend
            # overwrites record.base? as the last step in the commit,
            # so keep an eye on their inumbers.
            inodes = {}
            for path in glob.glob('%s/record.base*' % xapiandir):
                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')

try:
    gdb.execute('run')
except Exception:
    import traceback
    raise SystemExit(traceback.format_exc())

(*) Git path names are given by the tree(s) the blob belongs to.
    Blobs themselves have no identifier aside from the hash of its contents.^

git clone https://yhetil.org/notmuch.git