X-Git-Url: https://git.notmuchmail.org/git?p=notmuch;a=blobdiff_plain;f=test%2Ftest-lib.sh;h=815832843cd3a3a400bca439965e9a68856975a2;hp=7422bba47e75e00073779e4b2d7a162862cdb426;hb=260975e8aff635b6b99db835ee8d40ec33ee916c;hpb=0083854b1204f077e98b1d9c4ecfa2a4844ee716 diff --git a/test/test-lib.sh b/test/test-lib.sh index 7422bba4..81583284 100644 --- a/test/test-lib.sh +++ b/test/test-lib.sh @@ -1,4 +1,3 @@ -#!/bin/sh # # Copyright (c) 2005 Junio C Hamano # @@ -15,6 +14,14 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see http://www.gnu.org/licenses/ . +if [ ${BASH_VERSINFO[0]} -lt 4 ]; then + echo "Error: The notmuch test suite requires a bash version >= 4.0" + echo "due to use of associative arrays within the test suite." + echo "Please try again with a newer bash (or help us fix the" + echo "test suite to be more portable). Thanks." + exit 1 +fi + # if --tee was passed, write the output not only to the terminal, but # additionally to the file test-results/$BASENAME.out, too. case "$GIT_TEST_TEE_STARTED, $* " in @@ -31,7 +38,7 @@ done,*) ;; esac -# Keep the original TERM for say_color +# Keep the original TERM for say_color and test_emacs ORIGINAL_TERM=$TERM # For repeatability, reset the environment to known value. @@ -41,41 +48,8 @@ PAGER=cat TZ=UTC TERM=dumb export LANG LC_ALL PAGER TERM TZ -EDITOR=: -unset VISUAL -unset GIT_EDITOR -unset AUTHOR_DATE -unset AUTHOR_EMAIL -unset AUTHOR_NAME -unset COMMIT_AUTHOR_EMAIL -unset COMMIT_AUTHOR_NAME -unset EMAIL -unset GIT_ALTERNATE_OBJECT_DIRECTORIES -unset GIT_AUTHOR_DATE -GIT_AUTHOR_EMAIL=author@example.com -GIT_AUTHOR_NAME='A U Thor' -unset GIT_COMMITTER_DATE -GIT_COMMITTER_EMAIL=committer@example.com -GIT_COMMITTER_NAME='C O Mitter' -unset GIT_DIFF_OPTS -unset GIT_DIR -unset GIT_WORK_TREE -unset GIT_EXTERNAL_DIFF -unset GIT_INDEX_FILE -unset GIT_OBJECT_DIRECTORY -unset GIT_CEILING_DIRECTORIES -unset SHA1_FILE_DIRECTORIES -unset SHA1_FILE_DIRECTORY -unset GIT_NOTES_REF -unset GIT_NOTES_DISPLAY_REF -unset GIT_NOTES_REWRITE_REF -unset GIT_NOTES_REWRITE_MODE -GIT_MERGE_VERBOSITY=5 -export GIT_MERGE_VERBOSITY -export GIT_AUTHOR_EMAIL GIT_AUTHOR_NAME -export GIT_COMMITTER_EMAIL GIT_COMMITTER_NAME -export EDITOR GIT_TEST_CMP=${GIT_TEST_CMP:-diff -u} +TEST_EMACS=${TEST_EMACS:-${EMACS:-emacs}} # Protect ourselves from common misconfiguration to export # CDPATH into the environment @@ -83,21 +57,15 @@ unset CDPATH unset GREP_OPTIONS -case $(echo $GIT_TRACE |tr "[A-Z]" "[a-z]") in - 1|2|true) - echo "* warning: Some tests will not work if GIT_TRACE" \ - "is set as to trace on STDERR ! *" - echo "* warning: Please set GIT_TRACE to something" \ - "other than 1, 2 or true ! *" - ;; -esac - # Convenience # # A regexp to match 5 and 40 hexdigits _x05='[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f]' _x40="$_x05$_x05$_x05$_x05$_x05$_x05$_x05$_x05" +_x04='[0-9a-f][0-9a-f][0-9a-f][0-9a-f]' +_x32="$_x04$_x04$_x04$_x04$_x04$_x04$_x04$_x04" + # Each test should start with something like this, after copyright notices: # # test_description='Description of this test... @@ -148,6 +116,16 @@ do esac done +if test -n "$debug"; then + print_subtest () { + printf " %-4s" "[$((test_count - 1))]" + } +else + print_subtest () { + true + } +fi + if test -n "$color"; then say_color () { ( @@ -161,21 +139,24 @@ if test -n "$color"; then *) test -n "$quiet" && return;; esac shift - printf "* %s" "$*" + printf " " + printf "$@" tput sgr0 - echo + print_subtest ) } else say_color() { test -z "$1" && test -n "$quiet" && return shift - echo "* $*" + printf " " + printf "$@" + print_subtest } fi error () { - say_color error "error: $*" + say_color error "error: $*\n" GIT_EXIT_OK=t exit 1 } @@ -189,17 +170,13 @@ error "Test script did not set test_description." if test "$help" = "t" then - echo "$test_description" + echo "Tests ${test_description}" exit 0 fi +echo $(basename "$0"): "Testing ${test_description}" + exec 5>&1 -if test "$verbose" = "t" -then - exec 4>&2 3>&1 -else - exec 4>/dev/null 3>/dev/null -fi test_failure=0 test_count=0 @@ -209,6 +186,7 @@ test_success=0 die () { code=$? + rm -rf "$TEST_TMPDIR" if test -n "$GIT_EXIT_OK" then exit $code @@ -219,24 +197,10 @@ die () { } GIT_EXIT_OK= +# Note: TEST_TMPDIR *NOT* exported! +TEST_TMPDIR=$(mktemp -d "${TMPDIR:-/tmp}/notmuch-test-$$.XXXXXX") trap 'die' EXIT -# The semantics of the editor variables are that of invoking -# sh -c "$EDITOR \"$@\"" files ... -# -# If our trash directory contains shell metacharacters, they will be -# interpreted if we just set $EDITOR directly, so do a little dance with -# environment variables to work around this. -# -# In particular, quoting isn't enough, as the path may contain the same quote -# that we're using. -test_set_editor () { - FAKE_EDITOR="$1" - export FAKE_EDITOR - EDITOR='"$FAKE_EDITOR"' - export EDITOR -} - test_decode_color () { sed -e 's/.\[1m//g' \ -e 's/.\[31m//g' \ @@ -264,52 +228,342 @@ remove_cr () { tr '\015' Q | sed -e 's/Q$//' } -test_tick () { - if test -z "${test_tick+set}" - then - test_tick=1112911993 - else - test_tick=$(($test_tick + 60)) - fi - GIT_COMMITTER_DATE="$test_tick -0700" - GIT_AUTHOR_DATE="$test_tick -0700" - export GIT_COMMITTER_DATE GIT_AUTHOR_DATE +# Generate a new message in the mail directory, with a unique message +# ID and subject. The message is not added to the index. +# +# After this function returns, the filename of the generated message +# is available as $gen_msg_filename and the message ID is available as +# $gen_msg_id . +# +# This function supports named parameters with the bash syntax for +# assigning a value to an associative array ([name]=value). The +# supported parameters are: +# +# [dir]=directory/of/choice +# +# Generate the message in directory 'directory/of/choice' within +# the mail store. The directory will be created if necessary. +# +# [filename]=name +# +# Store the message in file 'name'. The default is to store it +# in 'msg-', where is three-digit number of the +# message. +# +# [body]=text +# +# Text to use as the body of the email message +# +# '[from]="Some User "' +# '[to]="Some User "' +# '[subject]="Subject of email message"' +# '[date]="RFC 822 Date"' +# +# Values for email headers. If not provided, default values will +# be generated instead. +# +# '[cc]="Some User "' +# [reply-to]=some-address +# [in-reply-to]= +# [references]= +# [content-type]=content-type-specification +# '[header]=full header line, including keyword' +# +# Additional values for email headers. If these are not provided +# then the relevant headers will simply not appear in the +# message. +# +# '[id]=message-id' +# +# Controls the message-id of the created message. +gen_msg_cnt=0 +gen_msg_filename="" +gen_msg_id="" +generate_message () +{ + # This is our (bash-specific) magic for doing named parameters + local -A template="($@)" + local additional_headers + + gen_msg_cnt=$((gen_msg_cnt + 1)) + if [ -z "${template[filename]}" ]; then + gen_msg_name="msg-$(printf "%03d" $gen_msg_cnt)" + else + gen_msg_name=${template[filename]} + fi + + if [ -z "${template[id]}" ]; then + gen_msg_id="${gen_msg_name%:2,*}@notmuch-test-suite" + else + gen_msg_id="${template[id]}" + fi + + if [ -z "${template[dir]}" ]; then + gen_msg_filename="${MAIL_DIR}/$gen_msg_name" + else + gen_msg_filename="${MAIL_DIR}/${template[dir]}/$gen_msg_name" + mkdir -p "$(dirname "$gen_msg_filename")" + fi + + if [ -z "${template[body]}" ]; then + template[body]="This is just a test message (#${gen_msg_cnt})" + fi + + if [ -z "${template[from]}" ]; then + template[from]="Notmuch Test Suite " + fi + + if [ -z "${template[to]}" ]; then + template[to]="Notmuch Test Suite " + fi + + if [ -z "${template[subject]}" ]; then + template[subject]="Test message #${gen_msg_cnt}" + fi + + if [ -z "${template[date]}" ]; then + template[date]="Fri, 05 Jan 2001 15:43:57 +0000" + fi + + additional_headers="" + if [ ! -z "${template[header]}" ]; then + additional_headers="${template[header]} +${additional_headers}" + fi + + if [ ! -z "${template[reply-to]}" ]; then + additional_headers="Reply-To: ${template[reply-to]} +${additional_headers}" + fi + + if [ ! -z "${template[in-reply-to]}" ]; then + additional_headers="In-Reply-To: ${template[in-reply-to]} +${additional_headers}" + fi + + if [ ! -z "${template[cc]}" ]; then + additional_headers="Cc: ${template[cc]} +${additional_headers}" + fi + + if [ ! -z "${template[references]}" ]; then + additional_headers="References: ${template[references]} +${additional_headers}" + fi + + if [ ! -z "${template[content-type]}" ]; then + additional_headers="Content-Type: ${template[content-type]} +${additional_headers}" + fi + + # Note that in the way we're setting it above and using it below, + # `additional_headers' will also serve as the header / body separator + # (empty line in between). + + cat <"$gen_msg_filename" +From: ${template[from]} +To: ${template[to]} +Message-Id: <${gen_msg_id}> +Subject: ${template[subject]} +Date: ${template[date]} +${additional_headers} +${template[body]} +EOF } -# Call test_commit with the arguments " [ []]" +# Generate a new message and add it to the database. # -# This will commit a file with the given contents and the given commit -# message. It will also add a tag with as name. +# All of the arguments and return values supported by generate_message +# are also supported here, so see that function for details. +add_message () +{ + generate_message "$@" && + notmuch new > /dev/null +} + +# Deliver a message with emacs and add it to the database # -# Both and default to . +# Uses emacs to generate and deliver a message to the mail store. +# Accepts arbitrary extra emacs/elisp functions to modify the message +# before sending, which is useful to doing things like attaching files +# to the message and encrypting/signing. +emacs_deliver_message () +{ + local subject="$1" + local body="$2" + shift 2 + # before we can send a message, we have to prepare the FCC maildir + mkdir -p "$MAIL_DIR"/sent/{cur,new,tmp} + $TEST_DIRECTORY/smtp-dummy sent_message & + smtp_dummy_pid=$! + test_emacs \ + "(let ((message-send-mail-function 'message-smtpmail-send-it) + (smtpmail-smtp-server \"localhost\") + (smtpmail-smtp-service \"25025\")) + (notmuch-hello) + (notmuch-mua-mail) + (message-goto-to) + (insert \"test_suite@notmuchmail.org\nDate: 01 Jan 2000 12:00:00 -0000\") + (message-goto-subject) + (insert \"${subject}\") + (message-goto-body) + (insert \"${body}\") + $@ + (message-send-and-exit))" + # opportunistically quit smtp-dummy in case above fails. + { echo QUIT > /dev/tcp/localhost/25025; } 2>/dev/null + wait ${smtp_dummy_pid} + notmuch new >/dev/null +} -test_commit () { - file=${2:-"$1.t"} - echo "${3-$1}" > "$file" && - git add "$file" && - test_tick && - git commit -m "$1" && - git tag "$1" +# Generate a corpus of email and add it to the database. +# +# This corpus is fixed, (it happens to be 50 messages from early in +# the history of the notmuch mailing list), which allows for reliably +# testing commands that need to operate on a not-totally-trivial +# number of messages. +add_email_corpus () +{ + rm -rf ${MAIL_DIR} + if [ -d $TEST_DIRECTORY/corpus.mail ]; then + cp -a $TEST_DIRECTORY/corpus.mail ${MAIL_DIR} + else + cp -a $TEST_DIRECTORY/corpus ${MAIL_DIR} + notmuch new >/dev/null + cp -a ${MAIL_DIR} $TEST_DIRECTORY/corpus.mail + fi } -# Call test_merge with the arguments " ", where -# can be a tag pointing to the commit-to-merge. +test_begin_subtest () +{ + if [ -n "$inside_subtest" ]; then + exec 1>&6 2>&7 # Restore stdout and stderr + error "bug in test script: Missing test_expect_equal in ${BASH_SOURCE[1]}:${BASH_LINENO[0]}" + fi + test_subtest_name="$1" + test_reset_state_ + # Remember stdout and stderr file descriptors and redirect test + # output to the previously prepared file descriptors 3 and 4 (see + # below) + if test "$verbose" != "t"; then exec 4>test.output 3>&4; fi + exec 6>&1 7>&2 >&3 2>&4 + inside_subtest=t +} -test_merge () { - test_tick && - git merge -m "$1" "$2" && - git tag "$1" +# Pass test if two arguments match +# +# Note: Unlike all other test_expect_* functions, this function does +# not accept a test name. Instead, the caller should call +# test_begin_subtest before calling this function in order to set the +# name. +test_expect_equal () +{ + exec 1>&6 2>&7 # Restore stdout and stderr + inside_subtest= + test "$#" = 3 && { prereq=$1; shift; } || prereq= + test "$#" = 2 || + error "bug in the test script: not 2 or 3 parameters to test_expect_equal" + + output="$1" + expected="$2" + if ! test_skip "$test_subtest_name" + then + if [ "$output" = "$expected" ]; then + test_ok_ "$test_subtest_name" + else + testname=$this_test.$test_count + echo "$expected" > $testname.expected + echo "$output" > $testname.output + test_failure_ "$test_subtest_name" "$(diff -u $testname.expected $testname.output)" + fi + fi +} + +# Like test_expect_equal, but takes two filenames. +test_expect_equal_file () +{ + exec 1>&6 2>&7 # Restore stdout and stderr + inside_subtest= + test "$#" = 3 && { prereq=$1; shift; } || prereq= + test "$#" = 2 || + error "bug in the test script: not 2 or 3 parameters to test_expect_equal" + + output="$1" + expected="$2" + if ! test_skip "$test_subtest_name" + then + if diff -q "$expected" "$output" >/dev/null ; then + test_ok_ "$test_subtest_name" + else + testname=$this_test.$test_count + cp "$output" $testname.output + cp "$expected" $testname.expected + test_failure_ "$test_subtest_name" "$(diff -u $testname.expected $testname.output)" + fi + fi +} + +test_emacs_expect_t () { + test "$#" = 2 && { prereq=$1; shift; } || prereq= + test "$#" = 1 || + error "bug in the test script: not 1 or 2 parameters to test_emacs_expect_t" + + # Run the test. + if ! test_skip "$test_subtest_name" + then + test_emacs "(notmuch-test-run $1)" >/dev/null + + # Restore state after the test. + exec 1>&6 2>&7 # Restore stdout and stderr + inside_subtest= + + # Report success/failure. + result=$(cat OUTPUT) + if [ "$result" = t ] + then + test_ok_ "$test_subtest_name" + else + test_failure_ "$test_subtest_name" "${result}" + fi + else + # Restore state after the (non) test. + exec 1>&6 2>&7 # Restore stdout and stderr + inside_subtest= + fi +} + +NOTMUCH_NEW () +{ + notmuch new | grep -v -E -e '^Processed [0-9]*( total)? file|Found [0-9]* total file' } -# This function helps systems where core.filemode=false is set. -# Use it instead of plain 'chmod +x' to set or unset the executable bit -# of a file in the working directory and add it to the index. +notmuch_search_sanitize () +{ + sed -r -e 's/("?thread"?: ?)("?)................("?)/\1\2XXX\3/' +} -test_chmod () { - chmod "$@" && - git update-index --add "--chmod=$@" +NOTMUCH_SHOW_FILENAME_SQUELCH='s,filename:.*/mail,filename:/XXX/mail,' +notmuch_show_sanitize () +{ + sed -e "$NOTMUCH_SHOW_FILENAME_SQUELCH" +} +notmuch_show_sanitize_all () +{ + sed \ + -e 's| filename:.*| filename:XXXXX|' \ + -e 's| id:[^ ]* | id:XXXXX |' } +notmuch_json_show_sanitize () +{ + sed -e 's|, |,\n |g' | \ + sed \ + -e 's|"id": "[^"]*",|"id": "XXXXX",|' \ + -e 's|"filename": "[^"]*",|"filename": "YYYYY",|' +} + +# End of notmuch helper functions + # Use test_set_prereq to tell that a particular prerequisite is available. # The prerequisite can later be checked for in two ways: # @@ -335,30 +589,77 @@ test_have_prereq () { esac } +# declare prerequisite for the given external binary +test_declare_external_prereq () { + binary="$1" + test "$#" = 2 && name=$2 || name="$binary(1)" + + hash $binary 2>/dev/null || eval " + test_missing_external_prereq_${binary}_=t +$binary () { + echo -n \"\$test_subtest_missing_external_prereqs_ \" | grep -qe \" $name \" || + test_subtest_missing_external_prereqs_=\"\$test_subtest_missing_external_prereqs_ $name\" + false +}" +} + +# Explicitly require external prerequisite. Useful when binary is +# called indirectly (e.g. from emacs). +# Returns success if dependency is available, failure otherwise. +test_require_external_prereq () { + binary="$1" + if [ "$(eval echo -n \$test_missing_external_prereq_${binary}_)" = t ]; then + # dependency is missing, call the replacement function to note it + eval "$binary" + else + true + fi +} + # You are not expected to call test_ok_ and test_failure_ directly, use # the text_expect_* functions instead. test_ok_ () { + if test "$test_subtest_known_broken_" = "t"; then + test_known_broken_ok_ "$@" + return + fi test_success=$(($test_success + 1)) - say_color "" " ok $test_count: $@" + say_color pass "%-6s" "PASS" + echo " $@" } test_failure_ () { + if test "$test_subtest_known_broken_" = "t"; then + test_known_broken_failure_ "$@" + return + fi test_failure=$(($test_failure + 1)) - say_color error "FAIL $test_count: $1" - shift - echo "$@" | sed -e 's/^/ /' + test_failure_message_ "FAIL" "$@" test "$immediate" = "" || { GIT_EXIT_OK=t; exit 1; } + return 1 +} + +test_failure_message_ () { + say_color error "%-6s" "$1" + echo " $2" + shift 2 + echo "$@" | sed -e 's/^/ /' + if test "$verbose" != "t"; then cat test.output; fi } test_known_broken_ok_ () { + test_reset_state_ test_fixed=$(($test_fixed+1)) - say_color "" " FIXED $test_count: $@" + say_color pass "%-6s" "FIXED" + echo " $@" } test_known_broken_failure_ () { + test_reset_state_ test_broken=$(($test_broken+1)) - say_color skip " still broken $test_count: $@" + test_failure_message_ "BROKEN" "$@" + return 1 } test_debug () { @@ -367,6 +668,7 @@ test_debug () { test_run_ () { test_cleanup=: + if test "$verbose" != "t"; then exec 4>test.output 3>&4; fi eval >&3 2>&4 "$1" eval_ret=$? eval >&3 2>&4 "$test_cleanup" @@ -376,7 +678,7 @@ test_run_ () { test_skip () { test_count=$(($test_count+1)) to_skip= - for skp in $GIT_SKIP_TESTS + for skp in $NOTMUCH_SKIP_TESTS do case $this_test.$test_count in $skp) @@ -390,68 +692,74 @@ test_skip () { fi case "$to_skip" in t) - say_color skip >&3 "skipping test: $@" - say_color skip "skip $test_count: $1" - : true + test_report_skip_ "$@" ;; *) - false + test_check_missing_external_prereqs_ "$@" ;; esac } -test_expect_failure () { - test "$#" = 3 && { prereq=$1; shift; } || prereq= - test "$#" = 2 || - error "bug in the test script: not 2 or 3 parameters to test-expect-failure" - if ! test_skip "$@" - then - say >&3 "checking known breakage: $2" - test_run_ "$2" - if [ "$?" = 0 -a "$eval_ret" = 0 ] - then - test_known_broken_ok_ "$1" - else - test_known_broken_failure_ "$1" - fi +test_check_missing_external_prereqs_ () { + if test -n "$test_subtest_missing_external_prereqs_"; then + say_color skip >&3 "missing prerequisites:" + echo "$test_subtest_missing_external_prereqs_" >&3 + test_report_skip_ "$@" + else + false fi - echo >&3 "" +} + +test_report_skip_ () { + test_reset_state_ + say_color skip >&3 "skipping test:" + echo " $@" >&3 + say_color skip "%-6s" "SKIP" + echo " $1" +} + +test_subtest_known_broken () { + test_subtest_known_broken_=t } test_expect_success () { test "$#" = 3 && { prereq=$1; shift; } || prereq= test "$#" = 2 || error "bug in the test script: not 2 or 3 parameters to test-expect-success" + test_reset_state_ if ! test_skip "$@" then - say >&3 "expecting success: $2" test_run_ "$2" - if [ "$?" = 0 -a "$eval_ret" = 0 ] + run_ret="$?" + # test_run_ may update missing external prerequisites + test_check_missing_external_prereqs_ "$@" || + if [ "$run_ret" = 0 -a "$eval_ret" = 0 ] then test_ok_ "$1" else test_failure_ "$@" fi fi - echo >&3 "" } test_expect_code () { test "$#" = 4 && { prereq=$1; shift; } || prereq= test "$#" = 3 || error "bug in the test script: not 3 or 4 parameters to test-expect-code" + test_reset_state_ if ! test_skip "$@" then - say >&3 "expecting exit code $1: $3" test_run_ "$3" - if [ "$?" = 0 -a "$eval_ret" = "$1" ] + run_ret="$?" + # test_run_ may update missing external prerequisites, + test_check_missing_external_prereqs_ "$@" || + if [ "$run_ret" = 0 -a "$eval_ret" = "$1" ] then test_ok_ "$2" else test_failure_ "$@" fi fi - echo >&3 "" } # test_external runs external test scripts that provide continuous @@ -468,6 +776,7 @@ test_external () { error >&5 "bug in the test script: not 3 or 4 parameters to test_external" descr="$1" shift + test_reset_state_ if ! test_skip "$descr" "$@" then # Announce the script to reduce confusion about the @@ -497,7 +806,6 @@ test_external_without_stderr () { [ -f "$stderr" ] || error "Internal error: $stderr disappeared." descr="no stderr: $1" shift - say >&3 "expecting no stderr from previous command" if [ ! -s "$stderr" ]; then rm "$stderr" test_ok_ "$descr" @@ -513,7 +821,7 @@ test_external_without_stderr () { fi } -# This is not among top-level (test_expect_success | test_expect_failure) +# This is not among top-level (test_expect_success) # but is a prefix that can be used in the test script, like: # # test_expect_success 'complain and die' ' @@ -572,21 +880,6 @@ test_when_finished () { } && (exit \"\$eval_ret\"); eval_ret=\$?; $test_cleanup" } -# Most tests can use the created repository, but some may need to create more. -# Usage: test_create_repo -test_create_repo () { - test "$#" = 1 || - error "bug in the test script: not 1 parameter to test-create-repo" - owd=`pwd` - repo="$1" - mkdir -p "$repo" - cd "$repo" || error "Cannot setup test environment" - "$GIT_EXEC_PATH/git-init" "--template=$TEST_DIRECTORY/../templates/blt/" >&3 2>&4 || - error "cannot run git init -- have you built things yet?" - mv .git/hooks .git/hooks-disabled - cd "$owd" -} - test_done () { GIT_EXIT_OK=t test_results_dir="$TEST_DIRECTORY/test-results" @@ -600,36 +893,157 @@ test_done () { echo "failed $test_failure" >> $test_results_path echo "" >> $test_results_path - if test "$test_fixed" != 0 - then - say_color pass "fixed $test_fixed known breakage(s)" + echo + + [ -n "$EMACS_SERVER" ] && test_emacs '(kill-emacs)' + + if [ "$test_failure" = "0" ]; then + if [ "$test_broken" = "0" ]; then + rm -rf "$remove_tmp" + fi + exit 0 + else + exit 1 fi - if test "$test_broken" != 0 - then - say_color error "still have $test_broken known breakage(s)" - msg="remaining $(($test_count-$test_broken)) test(s)" +} + +emacs_generate_script () { + # Construct a little test script here for the benefit of the user, + # (who can easily run "run_emacs" to get the same emacs environment + # for investigating any failures). + cat <"$TMP_DIRECTORY/run_emacs" +#!/bin/sh +export PATH=$PATH +export NOTMUCH_CONFIG=$NOTMUCH_CONFIG + +# Here's what we are using here: +# +# --no-init-file Don't load users ~/.emacs +# +# --no-site-file Don't load the site-wide startup stuff +# +# --directory Ensure that the local elisp sources are found +# +# --load Force loading of notmuch.el and test-lib.el + +exec ${TEST_EMACS} --no-init-file --no-site-file \ + --directory "$TEST_DIRECTORY/../emacs" --load notmuch.el \ + --directory "$TEST_DIRECTORY" --load test-lib.el \ + "\$@" +EOF + chmod a+x "$TMP_DIRECTORY/run_emacs" +} + +test_emacs () { + # test dependencies beforehand to avoid the waiting loop below + missing_dependencies= + test_require_external_prereq dtach || missing_dependencies=1 + test_require_external_prereq emacs || missing_dependencies=1 + test_require_external_prereq emacsclient || missing_dependencies=1 + test -z "$missing_dependencies" || return + + if [ -z "$EMACS_SERVER" ]; then + server_name="notmuch-test-suite-$$" + # start a detached session with an emacs server + # user's TERM is given to dtach which assumes a minimally + # VT100-compatible terminal -- and emacs inherits that + TERM=$ORIGINAL_TERM dtach -n "$TEST_TMPDIR/emacs-dtach-socket.$$" \ + sh -c "stty rows 24 cols 80; exec '$TMP_DIRECTORY/run_emacs' \ + --no-window-system \ + --eval '(setq server-name \"$server_name\")' \ + --eval '(server-start)' \ + --eval '(orphan-watchdog $$)'" || return + EMACS_SERVER="$server_name" + # wait until the emacs server is up + until test_emacs '()' >/dev/null 2>/dev/null; do + sleep 1 + done + fi + + emacsclient --socket-name="$EMACS_SERVER" --eval "(progn $@)" +} + +test_python() { + export LD_LIBRARY_PATH=$TEST_DIRECTORY/../lib + export PYTHONPATH=$TEST_DIRECTORY/../bindings/python + + # Some distros (e.g. Arch Linux) ship Python 2.* as /usr/bin/python2, + # most others as /usr/bin/python. So first try python2, and fallback to + # python if python2 doesn't exist. + cmd=python2 + [[ "$test_missing_external_prereq_python2_" = t ]] && cmd=python + + (echo "import sys; _orig_stdout=sys.stdout; sys.stdout=open('OUTPUT', 'w')"; cat) \ + | $cmd - +} + +# Creates a script that counts how much time it is executed and calls +# notmuch. $notmuch_counter_command is set to the path to the +# generated script. Use notmuch_counter_value() function to get the +# current counter value. +notmuch_counter_reset () { + notmuch_counter_command="$TMP_DIRECTORY/notmuch_counter" + if [ ! -x "$notmuch_counter_command" ]; then + notmuch_counter_state_path="$TMP_DIRECTORY/notmuch_counter.state" + cat >"$notmuch_counter_command" < "$notmuch_counter_state_path" + +exec notmuch "\$@" +EOF + chmod +x "$notmuch_counter_command" || return + fi + + echo 0 > "$notmuch_counter_state_path" +} + +# Returns the current notmuch counter value. +notmuch_counter_value () { + if [ -r "$notmuch_counter_state_path" ]; then + read count < "$notmuch_counter_state_path" else - msg="$test_count test(s)" + count=0 fi - case "$test_failure" in - 0) - say_color pass "passed all $msg" + echo $count +} - test -d "$remove_trash" && - cd "$(dirname "$remove_trash")" && - rm -rf "$(basename "$remove_trash")" +test_reset_state_ () { + test -z "$test_init_done_" && test_init_ - exit 0 ;; + test_subtest_known_broken_= + test_subtest_missing_external_prereqs_= +} - *) - say_color error "failed $test_failure among $msg" - exit 1 ;; +# called once before the first subtest +test_init_ () { + test_init_done_=t - esac + # skip all tests if there were external prerequisites missing during init + test_check_missing_external_prereqs_ "all tests in $this_test" && test_done +} + + +find_notmuch_path () +{ + dir="$1" + + while [ -n "$dir" ]; do + bin="$dir/notmuch" + if [ -x "$bin" ]; then + echo "$dir" + return + fi + dir="$(dirname "$dir")" + if [ "$dir" = "/" ]; then + break + fi + done } # Test the binaries we have just built. The tests are kept in -# t/ subdirectory and are run in 'trash directory' subdirectory. +# test/ subdirectory and are run in 'trash directory' subdirectory. TEST_DIRECTORY=$(pwd) if test -n "$valgrind" then @@ -663,28 +1077,25 @@ then test ! -d "$symlink_target" && test "#!" != "$(head -c 2 < "$symlink_target")" then - symlink_target=../valgrind.sh + symlink_target=$TEST_DIRECTORY/valgrind.sh fi case "$base" in *.sh|*.perl) - symlink_target=../unprocessed-script + symlink_target=$TEST_DIRECTORY/unprocessed-script esac # create the link, or replace it if it is out of date make_symlink "$symlink_target" "$GIT_VALGRIND/bin/$base" || exit } - # override all git executables in TEST_DIRECTORY/.. + # override notmuch executable in TEST_DIRECTORY/.. GIT_VALGRIND=$TEST_DIRECTORY/valgrind mkdir -p "$GIT_VALGRIND"/bin - for file in $TEST_DIRECTORY/../git* $TEST_DIRECTORY/../test-* - do - make_valgrind_symlink $file - done + make_valgrind_symlink $TEST_DIRECTORY/../notmuch OLDIFS=$IFS IFS=: for path in $PATH do - ls "$path"/git-* 2> /dev/null | + ls "$path"/notmuch 2> /dev/null | while read file do make_valgrind_symlink "$file" @@ -694,79 +1105,69 @@ then PATH=$GIT_VALGRIND/bin:$PATH GIT_EXEC_PATH=$GIT_VALGRIND/bin export GIT_VALGRIND -elif test -n "$GIT_TEST_INSTALLED" ; then - GIT_EXEC_PATH=$($GIT_TEST_INSTALLED/git --exec-path) || - error "Cannot run git from $GIT_TEST_INSTALLED." - PATH=$GIT_TEST_INSTALLED:$TEST_DIRECTORY/..:$PATH - GIT_EXEC_PATH=${GIT_TEST_EXEC_PATH:-$GIT_EXEC_PATH} -else # normal case, use ../bin-wrappers only unless $with_dashes: - git_bin_dir="$TEST_DIRECTORY/../bin-wrappers" - if ! test -x "$git_bin_dir/git" ; then - if test -z "$with_dashes" ; then - say "$git_bin_dir/git is not executable; using GIT_EXEC_PATH" - fi - with_dashes=t - fi - PATH="$git_bin_dir:$PATH" - GIT_EXEC_PATH=$TEST_DIRECTORY/.. - if test -n "$with_dashes" ; then - PATH="$TEST_DIRECTORY/..:$PATH" - fi -fi -GIT_TEMPLATE_DIR=$(pwd)/../templates/blt -unset GIT_CONFIG -GIT_CONFIG_NOSYSTEM=1 -GIT_CONFIG_NOGLOBAL=1 -export PATH GIT_EXEC_PATH GIT_TEMPLATE_DIR GIT_CONFIG_NOSYSTEM GIT_CONFIG_NOGLOBAL - -. ../GIT-BUILD-OPTIONS - -GITPERLLIB=$(pwd)/../perl/blib/lib:$(pwd)/../perl/blib/arch/auto/Git -export GITPERLLIB -test -d ../templates/blt || { - error "You haven't built things yet, have you?" -} - -if test -z "$GIT_TEST_INSTALLED" && test -z "$NO_PYTHON" -then - GITPYTHONLIB="$(pwd)/../git_remote_helpers/build/lib" - export GITPYTHONLIB - test -d ../git_remote_helpers/build || { - error "You haven't built git_remote_helpers yet, have you?" - } -fi - -if ! test -x ../test-chmtime; then - echo >&2 'You need to build test-chmtime:' - echo >&2 'Run "make test-chmtime" in the source (toplevel) directory' - exit 1 +else # normal case + notmuch_path=`find_notmuch_path "$TEST_DIRECTORY"` + test -n "$notmuch_path" && PATH="$notmuch_path:$PATH" fi +export PATH # Test repository -test="trash directory.$(basename "$0" .sh)" +test="tmp.$(basename "$0" .sh)" test -n "$root" && test="$root/$test" case "$test" in -/*) TRASH_DIRECTORY="$test" ;; - *) TRASH_DIRECTORY="$TEST_DIRECTORY/$test" ;; +/*) TMP_DIRECTORY="$test" ;; + *) TMP_DIRECTORY="$TEST_DIRECTORY/$test" ;; esac -test ! -z "$debug" || remove_trash=$TRASH_DIRECTORY +test ! -z "$debug" || remove_tmp=$TMP_DIRECTORY rm -fr "$test" || { GIT_EXIT_OK=t echo >&5 "FATAL: Cannot prepare test area" exit 1 } -test_create_repo "$test" +# A temporary home directory is needed by at least: +# - emacs/"Sending a message via (fake) SMTP" +# - emacs/"Reply within emacs" +# - crypto/emacs_deliver_message +export HOME="${TMP_DIRECTORY}/home" +mkdir -p "${HOME}" + +MAIL_DIR="${TMP_DIRECTORY}/mail" +export GNUPGHOME="${TMP_DIRECTORY}/gnupg" +export NOTMUCH_CONFIG="${TMP_DIRECTORY}/notmuch-config" + +mkdir -p "${test}" +mkdir -p "${MAIL_DIR}" + +cat <"${NOTMUCH_CONFIG}" +[database] +path=${MAIL_DIR} + +[user] +name=Notmuch Test Suite +primary_email=test_suite@notmuchmail.org +other_email=test_suite_other@notmuchmail.org;test_suite@otherdomain.org +EOF + +emacs_generate_script + + # Use -P to resolve symlinks in our working directory so that the cwd # in subprocesses like git equals our $PWD (for pathname comparisons). -cd -P "$test" || exit 1 +cd -P "$test" || error "Cannot setup test environment" + +if test "$verbose" = "t" +then + exec 4>&2 3>&1 +else + exec 4>test.output 3>&4 +fi this_test=${0##*/} -this_test=${this_test%%-*} -for skp in $GIT_SKIP_TESTS +for skp in $NOTMUCH_SKIP_TESTS do to_skip= - for skp in $GIT_SKIP_TESTS + for skp in $NOTMUCH_SKIP_TESTS do case "$this_test" in $skp) @@ -830,3 +1231,12 @@ test -z "$NO_PYTHON" && test_set_prereq PYTHON # test whether the filesystem supports symbolic links ln -s x y 2>/dev/null && test -h y 2>/dev/null && test_set_prereq SYMLINKS rm -f y + +# declare prerequisites for external binaries used in tests +test_declare_external_prereq dtach +test_declare_external_prereq emacs +test_declare_external_prereq emacsclient +test_declare_external_prereq gdb +test_declare_external_prereq gpg +test_declare_external_prereq python +test_declare_external_prereq python2