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:
Austin Clements 2011-01-28 13:03:24 -05:00 committed by David Bremner
parent 62445dd023
commit 9ade8160a6
4 changed files with 152 additions and 0 deletions

100
test/atomicity Executable file
View 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
View 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

View file

@ -60,6 +60,7 @@ available=$(ls -1 $TEST_DIRECTORY/ | \
-e "/^(test.expected-output|.*~)/d" \ -e "/^(test.expected-output|.*~)/d" \
-e "/^(gnupg-secret-key.asc)/d" \ -e "/^(gnupg-secret-key.asc)/d" \
-e "/^(gnupg-secret-key.NOTE)/d" \ -e "/^(gnupg-secret-key.NOTE)/d" \
-e "/^(atomicity.gdb)/d" \
| sort) | sort)
test_expect_equal "$tests_in_suite" "$available" test_expect_equal "$tests_in_suite" "$available"

View file

@ -42,6 +42,7 @@ TESTS="
crypto crypto
symbol-hiding symbol-hiding
search-folder-coherence search-folder-coherence
atomicity
" "
TESTS=${NOTMUCH_TESTS:=$TESTS} TESTS=${NOTMUCH_TESTS:=$TESTS}