From 8e379c62a48d68cd5ab2a32c6fc9244b1ae94084 Mon Sep 17 00:00:00 2001 From: Guilhem Moulin Date: Sun, 26 May 2019 23:28:04 +0200 Subject: Add test-suite (requires dovecot-imapd). --- tests/run | 336 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 336 insertions(+) create mode 100755 tests/run (limited to 'tests/run') diff --git a/tests/run b/tests/run new file mode 100755 index 0000000..31af03e --- /dev/null +++ b/tests/run @@ -0,0 +1,336 @@ +#!/bin/bash + +#---------------------------------------------------------------------- +# Test suite for InterIMAP +# Copyright © 2019 Guilhem Moulin +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +#---------------------------------------------------------------------- + +set -ue +PATH=/usr/bin:/bin +export PATH + +if [ $# -ne 1 ]; then + printf "Usage: %s TESTNAME\\n" "$0" >&2 + exit 1 +fi + +TEST="${1%/}" +TEST="${TEST##*/}" +NAME="${TEST#[0-9]*-}" +TESTDIR="$(dirname -- "$0")/$TEST" +if [ ! -d "$TESTDIR" ]; then + printf "ERROR: Not a directory: %s\\n" "$TESTDIR" >&2 + exit 1 +fi + +ROOTDIR="$(mktemp --tmpdir=/dev/shm --directory "$NAME.XXXXXXXXXX")" +trap 'rm -rf -- "$ROOTDIR"' EXIT INT TERM + +STDOUT="$ROOTDIR/stdout" +STDERR="$ROOTDIR/stderr" +TMPDIR="$ROOTDIR/tmp" +mkdir -- "$TMPDIR" "$ROOTDIR/home" + +# Set environment for the given user +environ_set() { + local user="$1" home + eval home="\$HOME_$user" + ENVIRON=( + PATH="$PATH" + USER="$user" + HOME="$home" + XDG_CONFIG_HOME="$home/.config" + XDG_DATA_HOME="$home/.local/share" + ) +} + +# Prepare the test harness +prepare() { + declare -a ENVIRON=() + local src cfg target u home + # copy dovecot config + for src in "$TESTDIR/local.conf" "$TESTDIR"/remote*.conf; do + [ -r "$src" ] || continue + u="${src#"$TESTDIR/"}" + u="${u%.conf}" + home="$ROOTDIR/home/$u" + export "HOME_$u"="$home" + mkdir -pm0755 -- "$home/.local/bin" + mkdir -pm0700 -- "$home/.config/dovecot" + cat >"$home/.config/dovecot/config" <<-EOF + log_path = /dev/null + mail_home = $ROOTDIR/home/%u + ssl = no + EOF + cat >>"$home/.config/dovecot/config" <"$src" + environ_set "$u" + cat >"$home/.local/bin/doveadm" <<-EOF + #!/bin/sh + exec env -i ${ENVIRON[@]@Q} \\ + doveadm -c ${home@Q}/.config/dovecot/config "\$@" + EOF + chmod +x -- "$home/.local/bin/doveadm" + done + + # copy interimap config + mkdir -pm0700 -- "$HOME_local/.local/share/interimap" + mkdir -pm0700 -- "$HOME_local/.config/interimap" + for cfg in "$TESTDIR"/remote*.conf; do + cfg="${cfg#"$TESTDIR/remote"}" + cfg="${cfg%.conf}" + u="remote$cfg" + eval home="\$HOME_$u" + if [ -f "$TESTDIR/interimap.conf" ]; then + cat <"$TESTDIR/interimap.conf" >>"$HOME_local/.config/interimap/config$cfg" + fi + cat >>"$HOME_local/.config/interimap/config$cfg" <<-EOF + database = $u.db + + [local] + type = tunnel + command = exec ${HOME_local@Q}/.local/bin/doveadm exec imap + null-stderr = YES + + [remote] + type = tunnel + command = exec ${home@Q}/.local/bin/doveadm exec imap + null-stderr = YES + EOF + done +} +prepare + +# Wrappers for interimap(1) and doveadm(1) +interimap() { + declare -a ENVIRON=() + environ_set "local" + env -i "${ENVIRON[@]}" perl -I./lib -T ./interimap "$@" +} +doveadm() { + if [ $# -le 2 ] || [ "$1" != "-u" ]; then + echo "Usage: doveadm -u USER ..." >&2 + exit 1 + fi + local u="$2" home + eval home="\$HOME_$u" + shift 2 + "$home/.local/bin/doveadm" "$@" +} + +# Sample (random) message +sample_message() { + cat <<-EOF + From: + To: + Date: $(date -R) + Message-ID: <$(< /proc/sys/kernel/random/uuid)@example.net> + + EOF + local len="$(shuf -i1-4096 -n1)" + xxd -ps -c30 -l"$len" /dev/urandom # 3 to 8329 bytes +} + +# Wrapper for dovecot-lda(1) +deliver() { + local -a argv + while [ $# -gt 0 ] && [ "$1" != "--" ]; do + argv+=( "$1" ) + shift + done + if [ $# -gt 0 ] && [ "$1" = "--" ]; then + shift + fi + doveadm "${argv[@]}" exec dovecot-lda -e "$@" +} + +# Dump test results +dump_test_result() { + local below=">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>" + local above="<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<" + local src u home + declare -a ENVIRON=() + for src in "$TESTDIR/local.conf" "$TESTDIR"/remote*.conf; do + u="${src#"$TESTDIR/"}" + u="${u%.conf}" + environ_set "$u" + eval home="\$HOME_$u" + printf "%s dovecot configuration:\\n%s\\n" "$u" "$below" + env -i "${ENVIRON[@]}" doveconf -c "$home/.config/dovecot/config" -n + printf "%s\\n\\n" "$above" + done + + printf "(local) interimap configuration:\\n%s\\n" "$below" + cat <"$HOME_local/.config/interimap/config" + printf "%s\\n\\n" "$above" + + printf "standard output was:\\n%s\\n" "$below" + cat <"$STDOUT" + printf "%s\\n\\n" "$above" + + printf "standard error was:\\n%s\\n" "$below" + cat <"$STDERR" + printf "%s\\n\\n" "$above" +} + +# Check mailbox consistency between the local/remote server and interimap's database +check_mailbox_status() { + local mailbox="$1" lns="inbox" lsep lprefix rns="inbox" rsep rprefix + lsep="$(doveconf -c "$HOME_local/.config/dovecot/config" -h "namespace/$lns/separator")" + lprefix="$(doveconf -c "$HOME_local/.config/dovecot/config" -h "namespace/$lns/prefix")" + rsep="$(doveconf -c "$HOME_remote/.config/dovecot/config" -h "namespace/$lns/separator")" + rprefix="$(doveconf -c "$HOME_remote/.config/dovecot/config" -h "namespace/$lns/prefix")" + + local blob="x'$(printf "%s" "$mailbox" | tr "$lsep" "\\0" | xxd -c256 -ps)'" + local rmailbox="$(printf "%s" "$mailbox" | tr "$lsep" "$rsep")" + check_mailbox_status2 "$blob" "$lprefix$mailbox" "remote" "$rprefix$rmailbox" +} +check_mailbox_status2() { + local blob="$1" lmailbox="$2" u="$3" rmailbox="$4" + local lUIDVALIDITY lUIDNEXT lHIGHESTMODSEQ rUIDVALIDITY rUIDNEXT rHIGHESTMODSEQ + read lUIDVALIDITY lUIDNEXT lHIGHESTMODSEQ rUIDVALIDITY rUIDNEXT rHIGHESTMODSEQ < <( + sqlite3 "$XDG_DATA_HOME/interimap/$u.db" <<-EOF + .mode csv + .separator " " "\\n" + SELECT l.UIDVALIDITY, l.UIDNEXT, l.HIGHESTMODSEQ, r.UIDVALIDITY, r.UIDNEXT, r.HIGHESTMODSEQ + FROM mailboxes m JOIN local l ON m.idx = l.idx JOIN remote r ON m.idx = r.idx + WHERE mailbox = $blob + EOF + ) + lHIGHESTMODSEQ="$(printf "%llu" "$lHIGHESTMODSEQ")" + rHIGHESTMODSEQ="$(printf "%llu" "$rHIGHESTMODSEQ")" + local MESSAGES + read MESSAGES < <( sqlite3 "$XDG_DATA_HOME/interimap/$u.db" <<-EOF + .mode csv + .separator " " "\\n" + SELECT COUNT(*) + FROM mailboxes a JOIN mapping b ON a.idx = b.idx + WHERE mailbox = $blob + EOF + ) + check_mailbox_status_values "local" "$lmailbox" $lUIDVALIDITY $lUIDNEXT $lHIGHESTMODSEQ $MESSAGES + check_mailbox_status_values "$u" "$rmailbox" $rUIDVALIDITY $rUIDNEXT $rHIGHESTMODSEQ $MESSAGES + + local a b + a="$(doveadm -u "local" -f "flow" mailbox status "messages unseen vsize" -- "$lmailbox" | \ + sed -nr '/.*\s+(\w+=[0-9]+\s+\w+=[0-9]+\s+\w+=[0-9]+)$/ {s//\1/p;q}')" + b="$(doveadm -u "$u" -f "flow" mailbox status "messages unseen vsize" -- "$rmailbox" | \ + sed -nr '/.*\s+(\w+=[0-9]+\s+\w+=[0-9]+\s+\w+=[0-9]+)$/ {s//\1/p;q}')" + if [ "$a" != "$b" ]; then + echo "Mailbox $lmailbox status differs: \"$a\" != \"$b\"" >&2 + exit 1 + fi +} +check_mailbox_status_values() { + local user="$1" mailbox="$2" UIDVALIDITY="$3" UIDNEXT="$4" HIGHESTMODSEQ="$5" MESSAGES="$6" x xs v k + xs="$(doveadm -u "$user" -f "flow" mailbox status "uidvalidity uidnext highestmodseq messages" -- "$mailbox" | \ + sed -nr '/.*\s+(\w+=[0-9]+\s+\w+=[0-9]+\s+\w+=[0-9]+\s+\w+=[0-9]+)$/ {s//\1/p;q}')" + [ -n "$xs" ] || exit 1 + for x in $xs; do + k="${x%%=*}" + case "${k^^[a-z]}" in + UIDVALIDITY) v="$UIDVALIDITY";; + UIDNEXT) v="$UIDNEXT";; + HIGHESTMODSEQ) v="$HIGHESTMODSEQ";; + MESSAGES) v="$MESSAGES";; + *) echo "Uh? $x" >&2; exit 1 + esac + if [ "${x#*=}" != "$v" ]; then + echo "$user($mailbox): ${k^^[a-z]} doesn't match! ${x#*=} != $v" >&2 + exit 1 + fi + done +} +check_mailboxes_status() { + local mailbox + for mailbox in "$@"; do + check_mailbox_status "$mailbox" + done +} + +# Check mailbox list constency between the local and remote servers +check_mailbox_list() { + local m i lns="inbox" lsep lprefix rns="inbox" rsep rprefix sub= + lsep="$(doveconf -c "$HOME_local/.config/dovecot/config" -h "namespace/$lns/separator")" + lprefix="$(doveconf -c "$HOME_local/.config/dovecot/config" -h "namespace/$lns/prefix")" + rsep="$(doveconf -c "$HOME_remote/.config/dovecot/config" -h "namespace/$lns/separator")" + rprefix="$(doveconf -c "$HOME_remote/.config/dovecot/config" -h "namespace/$lns/prefix")" + if [ $# -gt 0 ] && [ "$1" = "-s" ]; then + sub="-s" + shift + fi + + declare -a lmailboxes=() rmailboxes=() + if [ $# -eq 0 ]; then + lmailboxes=( "${lprefix}*" ) + rmailboxes=( "${rprefix}*" ) + else + for m in "$@"; do + lmailboxes+=( "$lprefix$m" ) + rmailboxes+=( "$rprefix${m//"$lsep"/"$rsep"}" ) + done + fi + + mapfile -t lmailboxes < <( doveadm -u "local" mailbox list $sub -- "${lmailboxes[@]}" ) + for ((i = 0; i < ${#lmailboxes[@]}; i++)); do + lmailboxes[i]="${lmailboxes[i]#"$lprefix"}" + done + + mapfile -t rmailboxes < <( doveadm -u "remote" mailbox list $sub -- "${rmailboxes[@]}" ) + for ((i = 0; i < ${#rmailboxes[@]}; i++)); do + rmailboxes[i]="${rmailboxes[i]#"$rprefix"}" + rmailboxes[i]="${rmailboxes[i]//"$rsep"/"$lsep"}" + done + + local IFS=$'\n' + diff -u --label="local/mailboxes" --label="remote/mailboxes" \ + <( printf "%s" "${lmailboxes[*]}" | sort ) <( printf "%s" "${rmailboxes[*]}" | sort ) +} + +# Wrappers for grep(1) and `grep -C` +xgrep() { + if ! grep -q "$@"; then + printf "\`grep %s\` failed on line %d\\n" "${*@Q}" ${BASH_LINENO[0]} >&2 + exit 1 + fi +} +xcgrep() { + local m="$1" n + shift + if ! n="$(grep -c "$@")" || [ $m -ne $n ]; then + printf "\`grep -c %s\` failed on line %d: %d != %d\\n" "${*@Q}" ${BASH_LINENO[0]} "$m" "$n" >&2 + exit 1 + fi +} + +# Run test in a sub-shell +declare -a ENVIRON=() +environ_set "local" +export TMPDIR TESTDIR STDOUT STDERR "${ENVIRON[@]}" +export -f environ_set doveadm interimap sample_message deliver +export -f check_mailbox_status check_mailbox_status_values check_mailbox_status2 +export -f check_mailboxes_status check_mailbox_list xgrep xcgrep +printf "%s..." "$TEST" +if ! bash -ue "$TESTDIR/run" >"$STDOUT" 2>"$STDERR"; then + echo " FAILED" + dump_test_result + exit 1 +else + echo " OK" + if grep -Paq "\\x00" -- "$STDOUT" "$STDERR"; then + printf "\\tWarn: binary output (outstanding \\0)!\\n" + fi + exit 0 +fi -- cgit v1.2.3