aboutsummaryrefslogtreecommitdiffstats
path: root/tests/run
diff options
context:
space:
mode:
authorGuilhem Moulin <guilhem@fripost.org>2019-05-26 23:28:04 +0200
committerGuilhem Moulin <guilhem@fripost.org>2019-05-27 00:39:49 +0200
commit8e379c62a48d68cd5ab2a32c6fc9244b1ae94084 (patch)
treedd3656ac60fe10c1e1739a88350f77c2f2a417f9 /tests/run
parent456946609aa1e64a42578ff1c4962ea939d31da4 (diff)
Add test-suite (requires dovecot-imapd).
Diffstat (limited to 'tests/run')
-rwxr-xr-xtests/run336
1 files changed, 336 insertions, 0 deletions
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 <guilhem@fripost.org>
+#
+# 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 <http://www.gnu.org/licenses/>.
+#----------------------------------------------------------------------
+
+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: <sender@example.net>
+ To: <recipient@example.net>
+ 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