mirror of
https://git.notmuchmail.org/git/notmuch
synced 2024-12-22 17:34:54 +01:00
test: Test atomicity of notmuch new.
This tests notmuch new's ability to recover from arbitrary stopping failures. It interrupts notmuch new after every database commit and, on every resulting database snapshot, re-runs notmuch new to completion and checks that the final database state is invariant.
This commit is contained in:
parent
62445dd023
commit
9ade8160a6
4 changed files with 152 additions and 0 deletions
100
test/atomicity
Executable file
100
test/atomicity
Executable file
|
@ -0,0 +1,100 @@
|
|||
#!/bin/bash
|
||||
test_description='atomicity'
|
||||
. ./test-lib.sh
|
||||
|
||||
# This script tests the effects of killing and restarting "notmuch
|
||||
# new" at arbitrary points. If notmuch new is properly atomic, the
|
||||
# final database contents should be the same regardless of when (or
|
||||
# if) it is killed and restarted.
|
||||
|
||||
if test_expect_success "prereq: GDB is present" "which gdb"; then
|
||||
test_set_prereq GDB
|
||||
fi
|
||||
|
||||
# Create a maildir structure to also stress flag synchronization
|
||||
mkdir $MAIL_DIR/cur
|
||||
mkdir $MAIL_DIR/new
|
||||
mkdir $MAIL_DIR/tmp
|
||||
mkdir $MAIL_DIR/.remove-dir
|
||||
|
||||
# Prepare the initial database
|
||||
generate_message [subject]='Duplicate' [filename]='duplicate:2,' [dir]=cur
|
||||
generate_message [subject]='Remove' [filename]='remove:2,' [dir]=cur
|
||||
generate_message [subject]='"Remove duplicate"' [filename]='remove-duplicate:2,' [dir]=cur
|
||||
cp $MAIL_DIR/cur/remove-duplicate:2, $MAIL_DIR/cur/remove-duplicate-copy:2,
|
||||
generate_message [subject]='Rename' [filename]='rename:2,' [dir]=cur
|
||||
generate_message [subject]='"Rename duplicate"' [filename]='rename-duplicate:2,' [dir]=cur
|
||||
generate_message [subject]='"Move 1"' [filename]='move1:2,' [dir]=cur
|
||||
generate_message [subject]='"Move 2"' [filename]='move2:2,' [dir]=new
|
||||
generate_message [subject]='Flag' [filename]='flag:2,' [dir]=cur
|
||||
generate_message [subject]='"Flag duplicate"' [filename]='flag-duplicate:2,' [dir]=cur
|
||||
cp $MAIL_DIR/cur/flag-duplicate:2, $MAIL_DIR/cur/flag-duplicate-copy:2,F
|
||||
generate_message [subject]='"Remove directory"' [filename]='remove-directory:2,' [dir]=.remove-dir
|
||||
generate_message [subject]='"Remove directory duplicate"' [filename]='remove-directory-duplicate:2,' [dir]=.remove-dir
|
||||
cp $MAIL_DIR/.remove-dir/remove-directory-duplicate:2, $MAIL_DIR/cur/
|
||||
notmuch new > /dev/null
|
||||
|
||||
# Make all maildir changes, but *don't* update the database
|
||||
generate_message [subject]='Added' [filename]='added:2,' [dir]=cur
|
||||
cp $MAIL_DIR/cur/duplicate:2, $MAIL_DIR/cur/duplicate-copy:2,
|
||||
generate_message [subject]='"Add duplicate"' [filename]='add-duplicate:2,' [dir]=cur
|
||||
generate_message [subject]='"Add duplicate copy"' [filename]='add-duplicate-copy:2,' [dir]=cur
|
||||
rm $MAIL_DIR/cur/remove:2,
|
||||
rm $MAIL_DIR/cur/remove-duplicate-copy:2,
|
||||
mv $MAIL_DIR/cur/rename:2, $MAIL_DIR/cur/renamed:2,
|
||||
mv $MAIL_DIR/cur/rename-duplicate:2, $MAIL_DIR/cur/renamed-duplicate:2,
|
||||
mv $MAIL_DIR/cur/move1:2, $MAIL_DIR/new/move1:2,
|
||||
mv $MAIL_DIR/new/move2:2, $MAIL_DIR/cur/move2:2,
|
||||
mv $MAIL_DIR/cur/flag:2, $MAIL_DIR/cur/flag:2,F
|
||||
rm $MAIL_DIR/cur/flag-duplicate-copy:2,F
|
||||
rm $MAIL_DIR/.remove-dir/remove-directory:2,
|
||||
rm $MAIL_DIR/.remove-dir/remove-directory-duplicate:2,
|
||||
rmdir $MAIL_DIR/.remove-dir
|
||||
|
||||
# Prepare a snapshot of the updated maildir. The gdb script will
|
||||
# update the database in this snapshot as it goes.
|
||||
cp -ra $MAIL_DIR $MAIL_DIR.snap
|
||||
cp ${NOTMUCH_CONFIG} ${NOTMUCH_CONFIG}.snap
|
||||
NOTMUCH_CONFIG=${NOTMUCH_CONFIG}.snap notmuch config set database.path $MAIL_DIR.snap
|
||||
|
||||
|
||||
test_begin_subtest '"notmuch new" is idempotent under arbitrary aborts'
|
||||
|
||||
# Execute notmuch new and, at every call to rename, snapshot the
|
||||
# database, run notmuch new again on the snapshot, and capture the
|
||||
# results of search.
|
||||
#
|
||||
# -tty /dev/null works around a conflict between the 'timeout' wrapper
|
||||
# and gdb's attempt to control the TTY.
|
||||
export MAIL_DIR
|
||||
gdb -tty /dev/null -batch -x $TEST_DIRECTORY/atomicity.gdb notmuch >/dev/null 2>/dev/null
|
||||
|
||||
# Get the final, golden output
|
||||
notmuch search '*' > expected
|
||||
|
||||
# Check output against golden output
|
||||
outcount=$(cat outcount)
|
||||
echo -n > searchall
|
||||
echo -n > expectall
|
||||
for ((i = 0; i < $outcount; i++)); do
|
||||
if ! cmp -s search.$i expected; then
|
||||
# Find the range of interruptions that match this output
|
||||
for ((end = $i + 1 ; end < $outcount; end++)); do
|
||||
if ! cmp -s search.$i search.$end; then
|
||||
break
|
||||
fi
|
||||
done
|
||||
echo "When interrupted after $test/backtrace.$(expr $i - 1) (abort points $i-$(expr $end - 1))" >> searchall
|
||||
cat search.$i >> searchall
|
||||
cat expected >> expectall
|
||||
echo >> searchall
|
||||
echo >> expectall
|
||||
|
||||
i=$(expr $end - 1)
|
||||
fi
|
||||
done
|
||||
test_expect_equal_failure GDB "$(cat searchall)" "$(cat expectall)"
|
||||
|
||||
test_expect_success GDB "detected $outcount>10 abort points" "test $outcount -gt 10"
|
||||
|
||||
test_done
|
50
test/atomicity.gdb
Normal file
50
test/atomicity.gdb
Normal file
|
@ -0,0 +1,50 @@
|
|||
# This gdb 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.
|
||||
|
||||
set args new
|
||||
|
||||
# Make Xapian commit after every operation instead of batching
|
||||
set environment XAPIAN_FLUSH_THRESHOLD = 1
|
||||
|
||||
# gdb can't keep track of a simple integer. This is me weeping.
|
||||
shell echo 0 > outcount
|
||||
|
||||
shell touch inodes
|
||||
|
||||
break rename
|
||||
commands
|
||||
# As an optimization, only consider snapshots after a Xapian commit.
|
||||
# Xapian overwrites record.base? as the last step in the commit.
|
||||
shell echo > gdbcmd
|
||||
shell stat -c %i $MAIL_DIR/.notmuch/xapian/record.base* > inodes.new
|
||||
shell if cmp inodes inodes.new; then echo cont > gdbcmd; fi
|
||||
shell mv inodes.new inodes
|
||||
source gdbcmd
|
||||
|
||||
# Save a backtrace in case the test does fail
|
||||
set logging file backtrace
|
||||
set logging on
|
||||
backtrace
|
||||
set logging off
|
||||
shell mv backtrace backtrace.`cat outcount`
|
||||
|
||||
# Snapshot the database
|
||||
shell rm -r $MAIL_DIR.snap/.notmuch
|
||||
shell cp -r $MAIL_DIR/.notmuch $MAIL_DIR.snap/.notmuch
|
||||
# Restore the mtime of $MAIL_DIR.snap, which we just changed
|
||||
shell touch -r $MAIL_DIR $MAIL_DIR.snap
|
||||
# Run notmuch new to completion on the snapshot
|
||||
shell NOTMUCH_CONFIG=${NOTMUCH_CONFIG}.snap XAPIAN_FLUSH_THRESHOLD=1000 notmuch new > /dev/null
|
||||
shell NOTMUCH_CONFIG=${NOTMUCH_CONFIG}.snap notmuch search '*' > search.`cat outcount` 2>&1
|
||||
shell echo $(expr $(cat outcount) + 1) > outcount
|
||||
cont
|
||||
end
|
||||
|
||||
run
|
|
@ -60,6 +60,7 @@ available=$(ls -1 $TEST_DIRECTORY/ | \
|
|||
-e "/^(test.expected-output|.*~)/d" \
|
||||
-e "/^(gnupg-secret-key.asc)/d" \
|
||||
-e "/^(gnupg-secret-key.NOTE)/d" \
|
||||
-e "/^(atomicity.gdb)/d" \
|
||||
| sort)
|
||||
test_expect_equal "$tests_in_suite" "$available"
|
||||
|
||||
|
|
|
@ -42,6 +42,7 @@ TESTS="
|
|||
crypto
|
||||
symbol-hiding
|
||||
search-folder-coherence
|
||||
atomicity
|
||||
"
|
||||
TESTS=${NOTMUCH_TESTS:=$TESTS}
|
||||
|
||||
|
|
Loading…
Reference in a new issue