diff options
60 files changed, 1499 insertions, 1 deletions
@@ -18,6 +18,7 @@ interimap (0.5) upstream; different InterIMAP instance for each local namespace <-> remote synchronization, for instance with the newly provided systemd template unit file). + * Add a small test-suite (requires dovecot-imapd). + interimap: write which --target to use in --delete command suggestions. + interimap: avoid caching hierarchy delimiters forever in the @@ -31,7 +31,10 @@ all: pullimap.1 interimap.1 install: +test: + @for t in tests/*; do [ -d "$$t" ] || continue; ./tests/run "$$t" || exit 1; done + clean: rm -f pullimap.1 interimap.1 -.PHONY: all install clean +.PHONY: all install clean test diff --git a/tests/00-db-exclusive/local.conf b/tests/00-db-exclusive/local.conf new file mode 100644 index 0000000..9c838fd --- /dev/null +++ b/tests/00-db-exclusive/local.conf @@ -0,0 +1,5 @@ +namespace inbox { + location = maildir:~/inbox:LAYOUT=index + inbox = yes + list = yes +} diff --git a/tests/00-db-exclusive/remote.conf b/tests/00-db-exclusive/remote.conf new file mode 100644 index 0000000..9c838fd --- /dev/null +++ b/tests/00-db-exclusive/remote.conf @@ -0,0 +1,5 @@ +namespace inbox { + location = maildir:~/inbox:LAYOUT=index + inbox = yes + list = yes +} diff --git a/tests/00-db-exclusive/run b/tests/00-db-exclusive/run new file mode 100644 index 0000000..1ae27b6 --- /dev/null +++ b/tests/00-db-exclusive/run @@ -0,0 +1,25 @@ +# verify that database isn't created in --watch mode +! interimap --watch=60 +xgrep -E "^DBI connect\(.*\) failed: unable to open database file at " <"$STDERR" + +# now create database +interimap + +# start a background process +interimap --watch=60 & pid=$! +cleanup() { + # kill interimap process and its children + pkill -P "$pid" -TERM + kill -TERM "$pid" + wait +} +trap cleanup EXIT INT TERM + +sleep .05 # wait a short while so we have time to lock the database (ugly and racy...) +# verify that subsequent runs fail as we can't acquire the exclusive lock +! interimap + +# line 177 is `$DBH->do("PRAGMA locking_mode = EXCLUSIVE");` +xgrep -Fx "DBD::SQLite::db do failed: database is locked at ./interimap line 177." <"$STDERR" + +# vim: set filetype=sh : diff --git a/tests/00-db-migration-0-to-1-delim-mismatch/before.sql b/tests/00-db-migration-0-to-1-delim-mismatch/before.sql new file mode 120000 index 0000000..0abb9bf --- /dev/null +++ b/tests/00-db-migration-0-to-1-delim-mismatch/before.sql @@ -0,0 +1 @@ +../00-db-migration-0-to-1/before.sql
\ No newline at end of file diff --git a/tests/00-db-migration-0-to-1-delim-mismatch/local.conf b/tests/00-db-migration-0-to-1-delim-mismatch/local.conf new file mode 100644 index 0000000..08438cb --- /dev/null +++ b/tests/00-db-migration-0-to-1-delim-mismatch/local.conf @@ -0,0 +1,6 @@ +namespace inbox { + separator = "\"" + location = maildir:~/inbox:LAYOUT=index + inbox = yes + list = yes +} diff --git a/tests/00-db-migration-0-to-1-delim-mismatch/remote.conf b/tests/00-db-migration-0-to-1-delim-mismatch/remote.conf new file mode 100644 index 0000000..cc6781d --- /dev/null +++ b/tests/00-db-migration-0-to-1-delim-mismatch/remote.conf @@ -0,0 +1,6 @@ +namespace inbox { + separator = ^ + location = maildir:~/inbox:LAYOUT=index + inbox = yes + list = yes +} diff --git a/tests/00-db-migration-0-to-1-delim-mismatch/run b/tests/00-db-migration-0-to-1-delim-mismatch/run new file mode 100644 index 0000000..434c678 --- /dev/null +++ b/tests/00-db-migration-0-to-1-delim-mismatch/run @@ -0,0 +1,8 @@ +# import an existing non-migrated database +sqlite3 "$XDG_DATA_HOME/interimap/remote.db" <"$TESTDIR/before.sql" +! interimap + +# may happen if the server(s) software or its configuration changed +xgrep -Fx 'ERROR: Local and remote hierachy delimiters differ (local "\"", remote "^"), refusing to update `mailboxes` table.' <"$STDERR" + +# vim: set filetype=sh : diff --git a/tests/00-db-migration-0-to-1-foreign-key-violation/local.conf b/tests/00-db-migration-0-to-1-foreign-key-violation/local.conf new file mode 100644 index 0000000..93497d9 --- /dev/null +++ b/tests/00-db-migration-0-to-1-foreign-key-violation/local.conf @@ -0,0 +1,6 @@ +namespace inbox { + separator = . + location = maildir:~/inbox:LAYOUT=index + inbox = yes + list = yes +} diff --git a/tests/00-db-migration-0-to-1-foreign-key-violation/remote.conf b/tests/00-db-migration-0-to-1-foreign-key-violation/remote.conf new file mode 100644 index 0000000..93497d9 --- /dev/null +++ b/tests/00-db-migration-0-to-1-foreign-key-violation/remote.conf @@ -0,0 +1,6 @@ +namespace inbox { + separator = . + location = maildir:~/inbox:LAYOUT=index + inbox = yes + list = yes +} diff --git a/tests/00-db-migration-0-to-1-foreign-key-violation/run b/tests/00-db-migration-0-to-1-foreign-key-violation/run new file mode 100644 index 0000000..f2d12a9 --- /dev/null +++ b/tests/00-db-migration-0-to-1-foreign-key-violation/run @@ -0,0 +1,23 @@ +# create new schema and add INBOX +interimap +xgrep "^Creating new schema in database file " <"$STDERR" +xgrep -Fx "database: Created mailbox INBOX" <"$STDERR" + +# empty table `mailboxes` and revert its schema to version 0 +sqlite3 "$XDG_DATA_HOME/interimap/remote.db" <<-EOF + PRAGMA foreign_keys = OFF; + PRAGMA user_version = 0; + DROP TABLE mailboxes; + CREATE TABLE mailboxes ( + idx INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, + mailbox TEXT NOT NULL CHECK (mailbox != '') UNIQUE, + subscribed BOOLEAN NOT NULL + ); +EOF + +# check that migration fails due to broken referential integrity +! interimap +xgrep -Fx "Upgrading database version from 0" <"$STDERR" +xgrep -Fx "database: ERROR: Broken referential integrity! Refusing to commit changes." <"$STDERR" + +# vim: set filetype=sh : diff --git a/tests/00-db-migration-0-to-1/after.sql b/tests/00-db-migration-0-to-1/after.sql new file mode 100644 index 0000000..18b0ad7 --- /dev/null +++ b/tests/00-db-migration-0-to-1/after.sql @@ -0,0 +1,14 @@ +PRAGMA foreign_keys=OFF; +BEGIN TRANSACTION; +CREATE TABLE local (idx INTEGER NOT NULL PRIMARY KEY REFERENCES mailboxes(idx), UIDVALIDITY UNSIGNED INT NOT NULL CHECK (UIDVALIDITY > 0), UIDNEXT UNSIGNED INT NOT NULL, HIGHESTMODSEQ UNSIGNED BIGINT NOT NULL); +CREATE TABLE remote (idx INTEGER NOT NULL PRIMARY KEY REFERENCES mailboxes(idx), UIDVALIDITY UNSIGNED INT NOT NULL CHECK (UIDVALIDITY > 0), UIDNEXT UNSIGNED INT NOT NULL, HIGHESTMODSEQ UNSIGNED BIGINT NOT NULL); +CREATE TABLE mapping (idx INTEGER NOT NULL REFERENCES mailboxes(idx), lUID UNSIGNED INT NOT NULL CHECK (lUID > 0), rUID UNSIGNED INT NOT NULL CHECK (rUID > 0), PRIMARY KEY (idx,lUID), UNIQUE (idx,rUID)); +CREATE TABLE IF NOT EXISTS "mailboxes" (idx INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, mailbox BLOB COLLATE BINARY NOT NULL CHECK (mailbox != '') UNIQUE, subscribed BOOLEAN NOT NULL); +INSERT INTO mailboxes VALUES(1,X'61006231006332',0); +INSERT INTO mailboxes VALUES(2,X'61006231006331',0); +INSERT INTO mailboxes VALUES(3,X'494e424f58',0); +INSERT INTO mailboxes VALUES(4,X'6132',0); +INSERT INTO mailboxes VALUES(5,X'610062320063',0); +DELETE FROM sqlite_sequence; +INSERT INTO sqlite_sequence VALUES('mailboxes',5); +COMMIT; diff --git a/tests/00-db-migration-0-to-1/before.sql b/tests/00-db-migration-0-to-1/before.sql new file mode 100644 index 0000000..333a1dc --- /dev/null +++ b/tests/00-db-migration-0-to-1/before.sql @@ -0,0 +1,14 @@ +PRAGMA foreign_keys=OFF; +BEGIN TRANSACTION; +CREATE TABLE mailboxes (idx INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, mailbox TEXT NOT NULL CHECK (mailbox != '') UNIQUE, subscribed BOOLEAN NOT NULL); +INSERT INTO mailboxes VALUES(1,'a.b1.c2',0); +INSERT INTO mailboxes VALUES(2,'a.b1.c1',0); +INSERT INTO mailboxes VALUES(3,'INBOX',0); +INSERT INTO mailboxes VALUES(4,'a2',0); +INSERT INTO mailboxes VALUES(5,'a.b2.c',0); +CREATE TABLE local (idx INTEGER NOT NULL PRIMARY KEY REFERENCES mailboxes(idx), UIDVALIDITY UNSIGNED INT NOT NULL CHECK (UIDVALIDITY > 0), UIDNEXT UNSIGNED INT NOT NULL, HIGHESTMODSEQ UNSIGNED BIGINT NOT NULL); +CREATE TABLE remote (idx INTEGER NOT NULL PRIMARY KEY REFERENCES mailboxes(idx), UIDVALIDITY UNSIGNED INT NOT NULL CHECK (UIDVALIDITY > 0), UIDNEXT UNSIGNED INT NOT NULL, HIGHESTMODSEQ UNSIGNED BIGINT NOT NULL); +CREATE TABLE mapping (idx INTEGER NOT NULL REFERENCES mailboxes(idx), lUID UNSIGNED INT NOT NULL CHECK (lUID > 0), rUID UNSIGNED INT NOT NULL CHECK (rUID > 0), PRIMARY KEY (idx,lUID), UNIQUE (idx,rUID)); +DELETE FROM sqlite_sequence; +INSERT INTO sqlite_sequence VALUES('mailboxes',5); +COMMIT; diff --git a/tests/00-db-migration-0-to-1/local.conf b/tests/00-db-migration-0-to-1/local.conf new file mode 100644 index 0000000..93497d9 --- /dev/null +++ b/tests/00-db-migration-0-to-1/local.conf @@ -0,0 +1,6 @@ +namespace inbox { + separator = . + location = maildir:~/inbox:LAYOUT=index + inbox = yes + list = yes +} diff --git a/tests/00-db-migration-0-to-1/remote.conf b/tests/00-db-migration-0-to-1/remote.conf new file mode 100644 index 0000000..93497d9 --- /dev/null +++ b/tests/00-db-migration-0-to-1/remote.conf @@ -0,0 +1,6 @@ +namespace inbox { + separator = . + location = maildir:~/inbox:LAYOUT=index + inbox = yes + list = yes +} diff --git a/tests/00-db-migration-0-to-1/run b/tests/00-db-migration-0-to-1/run new file mode 100644 index 0000000..e4eb770 --- /dev/null +++ b/tests/00-db-migration-0-to-1/run @@ -0,0 +1,26 @@ +# create some mailboxes +doveadm -u "local" mailbox create "a.b1.c1" "a.b1.c2" "a.b2.c" "a2" +doveadm -u "remote" mailbox create "a.b1.c1" "a.b1.c2" "a.b2.c" "a2" + +# import an existing non-migrated database +sqlite3 "$XDG_DATA_HOME/interimap/remote.db" <"$TESTDIR/before.sql" + +# migrate +interimap + +xgrep -Fx "Upgrading database version from 0" <"$STDERR" +check_mailboxes_status "a.b1.c1" "a.b1.c2" "a.b2.c" "a2" + +# verify that the new schema is as expected +sqlite3 "$XDG_DATA_HOME/interimap/remote.db" >"$TMPDIR/dump.sql" <<-EOF + DELETE FROM local; + DELETE FROM remote; + .dump +EOF + +# XXX need 'user_version' PRAGMA in the dump for future migrations +# http://sqlite.1065341.n5.nabble.com/dump-command-and-user-version-td101228.html +diff -u --label="a/dump.sql" --label="b/dump.sql" \ + "$TESTDIR/after.sql" "$TMPDIR/dump.sql" + +# vim: set filetype=sh : diff --git a/tests/01-rename-exists-db/local.conf b/tests/01-rename-exists-db/local.conf new file mode 100644 index 0000000..93497d9 --- /dev/null +++ b/tests/01-rename-exists-db/local.conf @@ -0,0 +1,6 @@ +namespace inbox { + separator = . + location = maildir:~/inbox:LAYOUT=index + inbox = yes + list = yes +} diff --git a/tests/01-rename-exists-db/remote.conf b/tests/01-rename-exists-db/remote.conf new file mode 100644 index 0000000..61e3d0d --- /dev/null +++ b/tests/01-rename-exists-db/remote.conf @@ -0,0 +1,6 @@ +namespace inbox { + separator = "\\" + location = maildir:~/inbox:LAYOUT=index + inbox = yes + list = yes +} diff --git a/tests/01-rename-exists-db/run b/tests/01-rename-exists-db/run new file mode 100644 index 0000000..29cb075 --- /dev/null +++ b/tests/01-rename-exists-db/run @@ -0,0 +1,14 @@ +doveadm -u "local" mailbox create "root.from" "root.from.child" "t.o" +doveadm -u "remote" mailbox create "root\\from" "root\\from\\child" "t\\o" + +interimap +check_mailbox_list + +# delete a mailbox on both servers but leave it in the database, then try to use it as target for --rename +doveadm -u "local" mailbox delete "t.o" +doveadm -u "remote" mailbox delete "t\\o" + +! interimap --rename "root.from" "t.o" +xgrep -Fx 'database: ERROR: Mailbox t.o exists. Run `interimap --target=database --delete t.o` to delete.' <"$STDERR" + +# vim: set filetype=sh : diff --git a/tests/01-rename-exists-local/local.conf b/tests/01-rename-exists-local/local.conf new file mode 100644 index 0000000..93497d9 --- /dev/null +++ b/tests/01-rename-exists-local/local.conf @@ -0,0 +1,6 @@ +namespace inbox { + separator = . + location = maildir:~/inbox:LAYOUT=index + inbox = yes + list = yes +} diff --git a/tests/01-rename-exists-local/remote.conf b/tests/01-rename-exists-local/remote.conf new file mode 100644 index 0000000..61e3d0d --- /dev/null +++ b/tests/01-rename-exists-local/remote.conf @@ -0,0 +1,6 @@ +namespace inbox { + separator = "\\" + location = maildir:~/inbox:LAYOUT=index + inbox = yes + list = yes +} diff --git a/tests/01-rename-exists-local/run b/tests/01-rename-exists-local/run new file mode 100644 index 0000000..17d8fcc --- /dev/null +++ b/tests/01-rename-exists-local/run @@ -0,0 +1,13 @@ +doveadm -u "local" mailbox create "root.from" "root.from.child" "t.o" +doveadm -u "remote" mailbox create "root\\from" "root\\from\\child" + +interimap +check_mailbox_list + +# delete a mailbox on the remote server, then try to use it as target for --rename +doveadm -u "remote" mailbox delete "t\\o" + +! interimap --rename "root.from" "t.o" +xgrep -Fx 'local: ERROR: Mailbox t.o exists. Run `interimap --target=local --delete t.o` to delete.' <"$STDERR" + +# vim: set filetype=sh : diff --git a/tests/01-rename-exists-remote/local.conf b/tests/01-rename-exists-remote/local.conf new file mode 100644 index 0000000..93497d9 --- /dev/null +++ b/tests/01-rename-exists-remote/local.conf @@ -0,0 +1,6 @@ +namespace inbox { + separator = . + location = maildir:~/inbox:LAYOUT=index + inbox = yes + list = yes +} diff --git a/tests/01-rename-exists-remote/remote.conf b/tests/01-rename-exists-remote/remote.conf new file mode 100644 index 0000000..61e3d0d --- /dev/null +++ b/tests/01-rename-exists-remote/remote.conf @@ -0,0 +1,6 @@ +namespace inbox { + separator = "\\" + location = maildir:~/inbox:LAYOUT=index + inbox = yes + list = yes +} diff --git a/tests/01-rename-exists-remote/run b/tests/01-rename-exists-remote/run new file mode 100644 index 0000000..c867a77 --- /dev/null +++ b/tests/01-rename-exists-remote/run @@ -0,0 +1,13 @@ +doveadm -u "local" mailbox create "root.from" "root.from.child" "t.o" +doveadm -u "remote" mailbox create "root\\from" "root\\from\\child" "t\\o" + +interimap +check_mailbox_list + +# delete a mailbox on the local server, then try to use it as target for --rename +doveadm -u "local" mailbox delete "t.o" + +! interimap --rename "root.from" "t.o" +xgrep -Fx 'remote: ERROR: Mailbox t\o exists. Run `interimap --target=remote --delete t.o` to delete.' <"$STDERR" + +# vim: set filetype=sh : diff --git a/tests/01-rename/local.conf b/tests/01-rename/local.conf new file mode 100644 index 0000000..93497d9 --- /dev/null +++ b/tests/01-rename/local.conf @@ -0,0 +1,6 @@ +namespace inbox { + separator = . + location = maildir:~/inbox:LAYOUT=index + inbox = yes + list = yes +} diff --git a/tests/01-rename/remote.conf b/tests/01-rename/remote.conf new file mode 100644 index 0000000..cc6781d --- /dev/null +++ b/tests/01-rename/remote.conf @@ -0,0 +1,6 @@ +namespace inbox { + separator = ^ + location = maildir:~/inbox:LAYOUT=index + inbox = yes + list = yes +} diff --git a/tests/01-rename/run b/tests/01-rename/run new file mode 100644 index 0000000..6541c5c --- /dev/null +++ b/tests/01-rename/run @@ -0,0 +1,84 @@ +doveadm -u "local" mailbox create "root.from" "root.from.child" "root.from.child2" "root.from.child.grandchild" +doveadm -u "remote" mailbox create "root^sibbling" "root^sibbling^grandchild" "root2" + +for m in "root.from" "root.from.child" "root.from.child2" "root.from.child.grandchild" "INBOX"; do + sample_message | deliver -u "local" -- -m "$m" +done +for m in "root^sibbling" "root^sibbling^grandchild" "root2" "INBOX"; do + sample_message | deliver -u "remote" -- -m "$m" +done + +interimap +check_mailboxes_status "root.from" "root.from.child" "root.from.child2" "root.from.child.grandchild" \ + "root.sibbling" "root.sibbling.grandchild" "root2" "INBOX" +sqlite3 "$XDG_DATA_HOME/interimap/remote.db" >"$TMPDIR/mailboxes.csv" <<-EOF + .mode csv + SELECT idx, hex(mailbox) + FROM mailboxes + ORDER BY idx +EOF + +# renaming a non-existent mailbox doesn't yield an error +interimap --rename "nonexistent" "nonexistent2" +check_mailbox_list + +# renaming to an existing name yields an error +! interimap --rename "root2" "root" +xgrep -E "^local: ERROR: Couldn't rename mailbox root2: NO \[ALREADYEXISTS\] .*" <"$STDERR" + +# rename 'root.from' to 'from.root', including inferiors +interimap --rename "root.from" "from.root" +xgrep -Fx 'local: Renamed mailbox root.from to from.root' <"$STDERR" +xgrep -Fx 'remote: Renamed mailbox root^from to from^root' <"$STDERR" +xgrep -Fx 'database: Renamed mailbox root.from to from.root' <"$STDERR" + +check_mailbox_list +check_mailboxes_status "from.root" "from.root.child" "from.root.child2" "from.root.child.grandchild" \ + "root.sibbling" "root.sibbling.grandchild" "root2" "INBOX" + +before="$(printf "%s\\0%s" "root" "from" | xxd -u -ps)" +after="$(printf "%s\\0%s" "from" "root" | xxd -ps)" +sqlite3 "$XDG_DATA_HOME/interimap/remote.db" >"$TMPDIR/mailboxes2.csv" <<-EOF + .mode csv + SELECT idx, + CASE + WHEN mailbox = x'$after' OR hex(mailbox) LIKE '${after}00%' + THEN '$before' || SUBSTR(hex(mailbox), $((${#after}+1))) + ELSE hex(mailbox) + END + FROM mailboxes + ORDER BY idx +EOF +diff -u --label="a/mailboxes.csv" --label="b/mailboxes.csv" \ + "$TMPDIR/mailboxes.csv" "$TMPDIR/mailboxes2.csv" + + +# Try to rename \NonExistent root and check that its children move +interimap --rename "root" "newroot" +xgrep -Fq 'local: Renamed mailbox root to newroot' <"$STDERR" +xgrep -Fq 'remote: Renamed mailbox root to newroot' <"$STDERR" +xgrep -Fq 'database: Renamed mailbox root to newroot' <"$STDERR" + +check_mailbox_list +check_mailboxes_status "from.root" "from.root.child" "from.root.child2" "from.root.child.grandchild" \ + "newroot.sibbling" "newroot.sibbling.grandchild" "root2" "INBOX" + +before2="$(printf "%s" "root" | xxd -u -ps)" +after2="$(printf "%s" "newroot" | xxd -ps)" +sqlite3 "$XDG_DATA_HOME/interimap/remote.db" >"$TMPDIR/mailboxes3.csv" <<-EOF + .mode csv + SELECT idx, + CASE + WHEN mailbox = x'$after' OR hex(mailbox) LIKE '${after}00%' + THEN '$before' || SUBSTR(hex(mailbox), $((${#after}+1))) + WHEN hex(mailbox) LIKE '${after2}00%' + THEN '$before2' || SUBSTR(hex(mailbox), $((${#after2}+1))) + ELSE hex(mailbox) + END + FROM mailboxes + ORDER BY idx +EOF +diff -u --label="a/mailboxes.csv" --label="b/mailboxes.csv" \ + "$TMPDIR/mailboxes2.csv" "$TMPDIR/mailboxes3.csv" + +# vim: set filetype=sh : diff --git a/tests/02-delete/local.conf b/tests/02-delete/local.conf new file mode 100644 index 0000000..93497d9 --- /dev/null +++ b/tests/02-delete/local.conf @@ -0,0 +1,6 @@ +namespace inbox { + separator = . + location = maildir:~/inbox:LAYOUT=index + inbox = yes + list = yes +} diff --git a/tests/02-delete/remote.conf b/tests/02-delete/remote.conf new file mode 100644 index 0000000..cc6781d --- /dev/null +++ b/tests/02-delete/remote.conf @@ -0,0 +1,6 @@ +namespace inbox { + separator = ^ + location = maildir:~/inbox:LAYOUT=index + inbox = yes + list = yes +} diff --git a/tests/02-delete/run b/tests/02-delete/run new file mode 100644 index 0000000..f63c52c --- /dev/null +++ b/tests/02-delete/run @@ -0,0 +1,67 @@ +doveadm -u "local" mailbox create "foo.bar" "foo.bar.baz" + +for m in "foo.bar" "foo.bar.baz" "INBOX"; do + sample_message | deliver -u "local" -- -m "$m" +done + +interimap +check_mailbox_list +check_mailboxes_status "foo.bar" "foo.bar.baz" "INBOX" +sqlite3 "$XDG_DATA_HOME/interimap/remote.db" >"$TMPDIR/dump.sql" <<-EOF + .dump +EOF + +# delete non-existent mailbox is a no-op +interimap --target="local,remote" --target="database" --delete "nonexistent" + +check_mailbox_list +check_mailboxes_status "foo.bar" "foo.bar.baz" "INBOX" +sqlite3 "$XDG_DATA_HOME/interimap/remote.db" >"$TMPDIR/dump2.sql" <<-EOF + .dump +EOF +diff -u --label="a/dump.sql" --label="b/dump.sql" \ + "$TMPDIR/dump.sql" "$TMPDIR/dump2.sql" + +# foo.bar will become \NoSelect in local, per RFC 3501: "It is permitted +# to delete a name that has inferior hierarchical names and does not +# have the \Noselect mailbox name attribute. In this case, all messages +# in that mailbox are removed, and the name will acquire the \Noselect +# mailbox name attribute." +interimap --target="local" --delete "foo.bar" + +check_mailbox_list +check_mailboxes_status "foo.bar.baz" "INBOX" + +sqlite3 "$XDG_DATA_HOME/interimap/remote.db" >"$TMPDIR/dump2.sql" <<-EOF + .dump +EOF +diff -u --label="a/dump.sql" --label="b/dump.sql" "$TMPDIR/dump.sql" "$TMPDIR/dump2.sql" + +! doveadm -u "local" mailbox status uidvalidity "foo.bar" # gone + doveadm -u "remote" mailbox status uidvalidity "foo^bar" + +sqlite3 "$XDG_DATA_HOME/interimap/remote.db" >"$TMPDIR/mailboxes.csv" <<-EOF + SELECT idx, mailbox + FROM mailboxes + WHERE mailbox != x'$(printf "%s\\0%s" "foo" "bar" | xxd -ps)' +EOF + + +# now delete from the remote server and the database +interimap --delete "foo.bar" + +! doveadm -u "local" mailbox status uidvalidity "foo.bar" +! doveadm -u "remote" mailbox status uidvalidity "foo^bar" + +sqlite3 "$XDG_DATA_HOME/interimap/remote.db" >"$TMPDIR/mailboxes2.csv" <<-EOF + SELECT idx, mailbox + FROM mailboxes + WHERE mailbox != x'$(printf "%s\\0%s" "foo" "bar" | xxd -ps)' +EOF +diff -u --label="a/mailboxes.csv" --label="b/mailboxes.csv" \ + "$TMPDIR/mailboxes.csv" "$TMPDIR/mailboxes2.csv" + +check_mailbox_list +check_mailboxes_status "foo.bar.baz" "INBOX" + +# vim: set filetype=sh : diff --git a/tests/03-sync-mailbox-list-partial/interimap.conf b/tests/03-sync-mailbox-list-partial/interimap.conf new file mode 100644 index 0000000..4970867 --- /dev/null +++ b/tests/03-sync-mailbox-list-partial/interimap.conf @@ -0,0 +1 @@ +list-mailbox = * diff --git a/tests/03-sync-mailbox-list-partial/local.conf b/tests/03-sync-mailbox-list-partial/local.conf new file mode 100644 index 0000000..93497d9 --- /dev/null +++ b/tests/03-sync-mailbox-list-partial/local.conf @@ -0,0 +1,6 @@ +namespace inbox { + separator = . + location = maildir:~/inbox:LAYOUT=index + inbox = yes + list = yes +} diff --git a/tests/03-sync-mailbox-list-partial/remote.conf b/tests/03-sync-mailbox-list-partial/remote.conf new file mode 100644 index 0000000..352cdd4 --- /dev/null +++ b/tests/03-sync-mailbox-list-partial/remote.conf @@ -0,0 +1,6 @@ +namespace inbox { + separator = ~ + location = maildir:~/inbox:LAYOUT=index + inbox = yes + list = yes +} diff --git a/tests/03-sync-mailbox-list-partial/run b/tests/03-sync-mailbox-list-partial/run new file mode 100644 index 0000000..449115d --- /dev/null +++ b/tests/03-sync-mailbox-list-partial/run @@ -0,0 +1,57 @@ +# try a bunch of invalid 'list-mailbox' values: +# empty string, missing space between values, unterminated string +for v in '""' '"f o o""bar"' '"f o o" "bar" "baz\" x'; do + sed -ri "s/^(list-mailbox\\s*=\\s*).*/\\1${v//\\/\\\\}/" "$XDG_CONFIG_HOME/interimap/config" + ! interimap + xgrep -xF "Invalid value for list-mailbox: $v" <"$STDERR" +done + +# create some mailboxes +doveadm -u "local" mailbox create "foo" "foo bar" "f\\\"o!o.bar" "f.o.o" "bad" +for m in "foo" "foo bar" "f\\\"o!o.bar" "f.o.o" "bad" "INBOX"; do + sample_message | deliver -u "local" -- -m "$m" +done + +# restrict 'list-mailbox' to the above minus "bad" +sed -ri 's/^(list-mailbox\s*=\s*).*/\1foo "foo bar" "f\\\\\\"o\\x21o.*" "f\\0o\\0o"/' \ + "$XDG_CONFIG_HOME/interimap/config" + +# run partial sync +interimap +check_mailbox_list "foo" "foo bar" "f\\\"o!o.bar" "f.o.o" "INBOX" "f\\\"o!o" "f" "f.o" +check_mailboxes_status "foo" "foo bar" "f\\\"o!o.bar" "f.o.o" + +# check that "bad" isn't in the remote imap server +! doveadm -u "remote" mailbox status uidvalidity "bad" + +# check that "bad" and "INBOX" aren't in the database +sqlite3 "$XDG_DATA_HOME/interimap/remote.db" >"$TMPDIR/count" <<-EOF + SELECT COUNT(*) + FROM mailboxes + WHERE mailbox = x'$(printf "%s" "bad" | xxd -ps)' + OR mailbox = x'$(printf "%s" "INBOX" | xxd -ps)' +EOF +[ $(< "$TMPDIR/count") -eq 0 ] + + +# run partial sync +doveadm -u "remote" mailbox create "f\\\"o!o~baz" "f\\\"o!o~bad" +for m in "f\\\"o!o~baz" "f\\\"o!o~bad"; do + sample_message | deliver -u "remote" -- -m "$m" +done +interimap "f\\\"o!o.baz" + +check_mailbox_list "foo" "foo bar" "f\\\"o!o.bar" "f.o.o" "INBOX" "f\\\"o!o" "f" "f.o" "f\\\"o!o.baz" +check_mailboxes_status "foo" "foo bar" "f\\\"o!o.bar" "f.o.o" "f\\\"o!o.baz" + +# check that "bad", "f\\\"o!o.bad" and "INBOX" aren't in the database +sqlite3 "$XDG_DATA_HOME/interimap/remote.db" >"$TMPDIR/count" <<-EOF + SELECT COUNT(*) + FROM mailboxes + WHERE mailbox = x'$(printf "%s" "bad" | xxd -ps)' + OR mailbox = x'$(printf "%s" "INBOX" | xxd -ps)' + OR mailbox = x'$(printf "%s\\0%s" "f\\\"o!o" "bad" | xxd -ps)' +EOF +[ $(< "$TMPDIR/count") -eq 0 ] + +# vim: set filetype=sh : diff --git a/tests/03-sync-mailbox-list-ref/local.conf b/tests/03-sync-mailbox-list-ref/local.conf new file mode 100644 index 0000000..6eccf43 --- /dev/null +++ b/tests/03-sync-mailbox-list-ref/local.conf @@ -0,0 +1,6 @@ +namespace inbox { + separator = / + location = maildir:~/inbox:LAYOUT=index + inbox = yes + list = yes +} diff --git a/tests/03-sync-mailbox-list-ref/remote.conf b/tests/03-sync-mailbox-list-ref/remote.conf new file mode 100644 index 0000000..61e3d0d --- /dev/null +++ b/tests/03-sync-mailbox-list-ref/remote.conf @@ -0,0 +1,6 @@ +namespace inbox { + separator = "\\" + location = maildir:~/inbox:LAYOUT=index + inbox = yes + list = yes +} diff --git a/tests/03-sync-mailbox-list-ref/run b/tests/03-sync-mailbox-list-ref/run new file mode 100644 index 0000000..3ead25d --- /dev/null +++ b/tests/03-sync-mailbox-list-ref/run @@ -0,0 +1,28 @@ +# Note: implementation-dependent as the reference name is not a level of +# mailbox hierarchy nor ends with the hierarchy delimiter +sed -ri 's#^\[local\]$#&\nlist-reference = foo#; s#^\[remote\]$#&\nlist-reference = bar#' \ + "$XDG_CONFIG_HOME/interimap/config" + +# create a bunch of mailboxes in and out the respective list # references +doveadm -u "local" mailbox create "foo" "foobar" "foo/bar/baz" "foo/baz" "bar" +doveadm -u "remote" mailbox create "foo" + +# deliver somemessages to these mailboxes +for m in "foo" "foobar" "foo/bar/baz" "foo/baz" "bar"; do + sample_message | deliver -u "local" -- -m "$m" +done +sample_message | deliver -u "remote" -- -m "foo" + +interimap + +# check that the mailbox lists match +diff -u --label="local/mailboxes" --label="remote/mailboxes" \ + <( doveadm -u "local" mailbox list | sed -n "s/^foo//p" | sort ) \ + <( doveadm -u "remote" mailbox list | sed -n "s/^bar//p" | tr '\\' '/' | sort ) + +for m in "" "bar" "/bar/baz" "/baz"; do + blob="x'$(printf "%s" "$m" | tr "/" "\\0" | xxd -c256 -ps)'" + check_mailbox_status2 "$blob" "foo$m" "remote" "bar${m//\//\\}" +done + +# vim: set filetype=sh : diff --git a/tests/03-sync-mailbox-list/local.conf b/tests/03-sync-mailbox-list/local.conf new file mode 100644 index 0000000..93497d9 --- /dev/null +++ b/tests/03-sync-mailbox-list/local.conf @@ -0,0 +1,6 @@ +namespace inbox { + separator = . + location = maildir:~/inbox:LAYOUT=index + inbox = yes + list = yes +} diff --git a/tests/03-sync-mailbox-list/remote.conf b/tests/03-sync-mailbox-list/remote.conf new file mode 100644 index 0000000..352cdd4 --- /dev/null +++ b/tests/03-sync-mailbox-list/remote.conf @@ -0,0 +1,6 @@ +namespace inbox { + separator = ~ + location = maildir:~/inbox:LAYOUT=index + inbox = yes + list = yes +} diff --git a/tests/03-sync-mailbox-list/run b/tests/03-sync-mailbox-list/run new file mode 100644 index 0000000..e9fda06 --- /dev/null +++ b/tests/03-sync-mailbox-list/run @@ -0,0 +1,73 @@ +# pre-create some mailboxes and susbscribe to some +# foo: present on both, subscribed to both +# bar: present on both, subscribed to local only +# baz: present on both, subscribed to remote only +# foo.bar: present on local only +# foo.baz: present on remote only +doveadm -u "local" mailbox create "foo" "bar" "baz" "foo.bar" "fo!o [b*a%r]" +doveadm -u "local" mailbox subscribe "foo" "bar" +doveadm -u "remote" mailbox create "foo" "bar" "baz" "foo~baz" "foo]bar" +doveadm -u "remote" mailbox subscribe "foo" "baz" + +interimap +xgrep -Fx "local: Subscribe to baz" <"$STDERR" +xgrep -Fx "remote: Subscribe to bar" <"$STDERR" +xgrep -Fx "local: Created mailbox foo.baz" <"$STDERR" +xgrep -Fx "remote: Created mailbox foo~bar" <"$STDERR" + +# check syncing +check_mailbox_list +check_mailboxes_status "foo" "bar" "baz" "foo.bar" "foo.baz" "INBOX" "fo!o [b*a%r]" "foo]bar" +check_mailbox_list -s + + +# delete a mailbox one server and verify that synchronization fails as it's still in the database +doveadm -u "remote" mailbox delete "foo~baz" +! interimap +xgrep -Fx 'database: ERROR: Mailbox foo.baz exists. Run `interimap --target=database --delete foo.baz` to delete.' <"$STDERR" +interimap --target="database" --delete "foo.baz" +xgrep -Fx 'database: Removed mailbox foo.baz' <"$STDERR" +interimap # create again +xgrep -Fx 'database: Created mailbox foo.baz' <"$STDERR" +xgrep -Fx 'remote: Created mailbox foo~baz' <"$STDERR" + +doveadm -u "local" mailbox delete "foo.bar" +! interimap +xgrep -Fx 'database: ERROR: Mailbox foo.bar exists. Run `interimap --target=database --delete foo.bar` to delete.' <"$STDERR" +interimap --target="database" --delete "foo.bar" +xgrep -Fx 'database: Removed mailbox foo.bar' <"$STDERR" +interimap +xgrep -Fx 'database: Created mailbox foo.bar' <"$STDERR" +xgrep -Fx 'local: Created mailbox foo.bar' <"$STDERR" + +check_mailbox_list +check_mailboxes_status "foo" "bar" "baz" "foo.bar" "foo.baz" "INBOX" "fo!o [b*a%r]" "foo]bar" +check_mailbox_list -s + + +# (un)subscribe from some mailboxes, including a non-existent one +doveadm -u "local" mailbox unsubscribe "foo" +doveadm -u "remote" mailbox unsubscribe "bar" +doveadm -u "local" mailbox subscribe "foo.bar" "foo.nonexistent" "foo.baz" +doveadm -u "remote" mailbox subscribe "foo~bar" "bar~nonexistent" + +interimap +xgrep -Fx 'remote: Unsubscribe to foo' <"$STDERR" +xgrep -Fx 'local: Unsubscribe to bar' <"$STDERR" +xgrep -Fx 'remote: Subscribe to foo~baz' <"$STDERR" +check_mailbox_list +check_mailbox_list -s $(doveadm -u "local" mailbox list) # exclude "foo.nonexistent" and "bar~nonexistent" + +# check that "baz", "foo.bar" and "foo.baz" are the only subscribed mailboxes +sqlite3 "$XDG_DATA_HOME/interimap/remote.db" >"$TMPDIR/count" <<-EOF + SELECT COUNT(*) + FROM mailboxes + WHERE subscribed <> (mailbox IN ( + x'$(printf "%s" "baz" | xxd -ps)', + x'$(printf "%s\\0%s" "foo" "bar" | xxd -ps)', + x'$(printf "%s\\0%s" "foo" "baz" | xxd -ps)' + )) +EOF +[ $(< "$TMPDIR/count") -eq 0 ] + +# vim: set filetype=sh : diff --git a/tests/04-resume/local.conf b/tests/04-resume/local.conf new file mode 100644 index 0000000..93497d9 --- /dev/null +++ b/tests/04-resume/local.conf @@ -0,0 +1,6 @@ +namespace inbox { + separator = . + location = maildir:~/inbox:LAYOUT=index + inbox = yes + list = yes +} diff --git a/tests/04-resume/remote.conf b/tests/04-resume/remote.conf new file mode 100644 index 0000000..352cdd4 --- /dev/null +++ b/tests/04-resume/remote.conf @@ -0,0 +1,6 @@ +namespace inbox { + separator = ~ + location = maildir:~/inbox:LAYOUT=index + inbox = yes + list = yes +} diff --git a/tests/04-resume/run b/tests/04-resume/run new file mode 100644 index 0000000..22d66bc --- /dev/null +++ b/tests/04-resume/run @@ -0,0 +1,98 @@ +# create and populate a bunch of mailboxes +doveadm -u "local" mailbox create "foo" "foo.bar" "baz" +for ((i = 0; i < 8; i++)); do + sample_message | deliver -u "local" -- -m "foo" + sample_message | deliver -u "local" -- -m "foo.bar" + sample_message | deliver -u "local" -- -m "INBOX" +done +interimap +check_mailbox_list +check_mailboxes_status "foo" "foo.bar" "baz" "INBOX" + +# spoof UIDNEXT in the database +set_uidnext() { + local imap="$1" mailbox="$2" uidnext="$3" + sqlite3 "$XDG_DATA_HOME/interimap/remote.db" <<-EOF + UPDATE $imap + SET UIDNEXT = $uidnext + WHERE idx = ( + SELECT idx + FROM mailboxes + WHERE mailbox = x'$mailbox' + ); + EOF +} + +# spoof "foo"'s UIDVALIDITY and UIDNEXT values +uidvalidity="$(doveadm -u "local" -f flow mailbox status uidvalidity "foo" | sed 's/.*=//')" +[ $uidvalidity -eq 4294967295 ] && uidvalidity2=1 || uidvalidity2=$((uidvalidity+1)) +doveadm -u "local" mailbox update --uid-validity "$uidvalidity2" "foo" +set_uidnext "local" "$(printf "%s" "foo" | xxd -ps)" 1 + +# verify that interimap chokes on the UIDVALIDITY change without doing any changes +sqlite3 "$XDG_DATA_HOME/interimap/remote.db" >"$TMPDIR/dump.sql" <<-EOF + .dump +EOF +doveadm -u "local" mailbox status "all" "foo" >"$TMPDIR/foo.local" +doveadm -u "remote" mailbox status "all" "foo" >"$TMPDIR/foo.remote" + +! interimap +xgrep -Fx "Resuming interrupted sync for foo" <"$STDERR" +xgrep -Fx "local(foo): ERROR: UIDVALIDITY changed! ($uidvalidity2 != $uidvalidity) Need to invalidate the UID cache." <"$STDERR" + +sqlite3 "$XDG_DATA_HOME/interimap/remote.db" >"$TMPDIR/dump2.sql" <<-EOF + .dump +EOF +doveadm -u "local" mailbox status "all" "foo" >"$TMPDIR/foo.local2" +doveadm -u "remote" mailbox status "all" "foo" >"$TMPDIR/foo.remote2" + +diff -u --label="a/dump.sql" --label="b/dump.sql" "$TMPDIR/dump2.sql" "$TMPDIR/dump.sql" +diff -u --label="a/foo.local" --label="b/foo.remote" "$TMPDIR/foo.local" "$TMPDIR/foo.local2" +diff -u --label="a/foo.local" --label="b/foo.remote" "$TMPDIR/foo.remote" "$TMPDIR/foo.remote2" + + +# spoof UIDNEXT values for INBOX (local+remote) and foo.bar (remote) +set_uidnext "local" "$(printf "%s" "INBOX" | xxd -ps)" 2 +set_uidnext "remote" "$(printf "%s" "INBOX" | xxd -ps)" 2 +set_uidnext "remote" "$(printf "%s\\0%s" "foo" "bar" | xxd -ps)" 0 + +# set some flags and remove some messages for UIDs >2 +doveadm -u "local" flags add "\\Seen" mailbox "INBOX" 6,7 +doveadm -u "remote" flags add "\\Deleted" mailbox "INBOX" 6,8 + +doveadm -u "local" expunge mailbox "INBOX" 4,5 +doveadm -u "remote" expunge mailbox "INBOX" 3,4 +doveadm -u "remote" expunge mailbox "foo~bar" 5 + +# add new messages +sample_message | deliver -u "local" -- -m "foo.bar" +sample_message | deliver -u "remote" -- -m "foo~bar" +sample_message | deliver -u "local" -- -m "baz" + +interimap "foo.bar" "InBoX" "baz" # ignore "foo" +xgrep -Fx "Resuming interrupted sync for foo.bar" <"$STDERR" +xgrep -Fx "Resuming interrupted sync for INBOX" <"$STDERR" +check_mailbox_list +check_mailboxes_status "foo.bar" "INBOX" "baz" # ignore "foo" + + +# count entries in the mapping table +sqlite3 "$XDG_DATA_HOME/interimap/remote.db" >"$TMPDIR/count" <<-EOF + SELECT COUNT(*) + FROM mapping NATURAL JOIN mailboxes + WHERE mailbox != x'$(printf "%s" "foo" | xxd -ps)' + GROUP BY idx + ORDER BY mailbox; +EOF + +# count messages: +# INBOX: 8-2-1 = 5 +# baz: 1 +# foo.bar: 8-1+1+1 = 9 +diff -u --label="a/count" --label="b/count" "$TMPDIR/count" - <<-EOF + 5 + 1 + 9 +EOF + +# vim: set filetype=sh : diff --git a/tests/05-repair/local.conf b/tests/05-repair/local.conf new file mode 100644 index 0000000..93497d9 --- /dev/null +++ b/tests/05-repair/local.conf @@ -0,0 +1,6 @@ +namespace inbox { + separator = . + location = maildir:~/inbox:LAYOUT=index + inbox = yes + list = yes +} diff --git a/tests/05-repair/remote.conf b/tests/05-repair/remote.conf new file mode 100644 index 0000000..352cdd4 --- /dev/null +++ b/tests/05-repair/remote.conf @@ -0,0 +1,6 @@ +namespace inbox { + separator = ~ + location = maildir:~/inbox:LAYOUT=index + inbox = yes + list = yes +} diff --git a/tests/05-repair/run b/tests/05-repair/run new file mode 100644 index 0000000..747b974 --- /dev/null +++ b/tests/05-repair/run @@ -0,0 +1,107 @@ +# create some mailboxes and populate them +doveadm -u "local" mailbox create "foo.bar" +doveadm -u "remote" mailbox create "foo~bar" "baz" +for ((i = 0; i < 8; i++)); do + sample_message | deliver -u "local" -- -m "foo.bar" + sample_message | deliver -u "remote" -- -m "foo~bar" +done +for ((i = 0; i < 64; i++)); do + sample_message | deliver -u "remote" -- -m "baz" +done + +interimap +check_mailbox_list +check_mailboxes_status "foo.bar" "baz" "INBOX" + +# make more changes (flag updates, new massages, deletions) +sample_message | deliver -u "remote" -- -m "INBOX" +doveadm -u "local" expunge mailbox "baz" 1:10 +doveadm -u "remote" expunge mailbox "baz" "$(seq -s"," 1 2 32),$(seq -s"," 40 2 64)" +doveadm -u "local" expunge mailbox "foo.bar" 2,3,5:7,10 +doveadm -u "remote" expunge mailbox "foo~bar" 4,5,7,10 +doveadm -u "local" flags add "\\Answered" mailbox "foo.bar" 2,3,5:7,10 +doveadm -u "remote" flags add "\\Seen" mailbox "foo~bar" 4,5,7 + +# spoof HIGHESTMODSEQ value in the database, to make it look that we recorded the new changes already +spoof() { + local k="$1" v m hex="$(printf "%s\\0%s" "foo" "bar" | xxd -ps)" + shift + while [ $# -gt 0 ]; do + [ "$1" = "local" ] && m="foo.bar" || m="$(printf "%s" "foo.bar" | tr "." "~")" + v="$(doveadm -u "$1" -f flow mailbox status "${k,,[A-Z]}" "$m" | sed 's/.*=//')" + sqlite3 "$XDG_DATA_HOME/interimap/remote.db" <<-EOF + UPDATE \`$1\` SET $k = $v + WHERE idx = (SELECT idx FROM mailboxes WHERE mailbox = x'$hex'); + EOF + shift + done +} + +spoof HIGHESTMODSEQ "local" "remote" +sqlite3 "$XDG_DATA_HOME/interimap/remote.db" >"$TMPDIR/dump.sql" <<-EOF + .dump +EOF +doveadm -u "local" mailbox status "all" "foo.bar" >"$TMPDIR/foo-bar.status.local" +doveadm -u "remote" mailbox status "all" "foo~bar" >"$TMPDIR/foo-bar.status.remote" + + +# verify that without --repair interimap does nothing due to the spoofed HIGHESTMODSEQ values +interimap "foo.bar" + +sqlite3 "$XDG_DATA_HOME/interimap/remote.db" >"$TMPDIR/dump2.sql" <<-EOF + .dump +EOF +doveadm -u "local" mailbox status all "foo.bar" >"$TMPDIR/foo-bar.status2.local" +doveadm -u "remote" mailbox status all "foo~bar" >"$TMPDIR/foo-bar.status2.remote" +diff -u --label="a/dump.sql" --label="b/dump.sql" "$TMPDIR/dump.sql" "$TMPDIR/dump2.sql" +diff -u --label="a/foo_bar.local" --label="a/foo_bar.local" "$TMPDIR/foo-bar.status.local" "$TMPDIR/foo-bar.status2.local" +diff -u --label="a/foo_bar.remote" --label="a/foo_bar.remote" "$TMPDIR/foo-bar.status.remote" "$TMPDIR/foo-bar.status2.remote" + + +# deliver more messages and spoof UIDNEXT, on one side only +sample_message | deliver -u "local" -- -m "foo.bar" +sample_message | deliver -u "remote" -- -m "foo~bar" +spoof UIDNEXT "local" +spoof HIGHESTMODSEQ "local" "remote" + +# now repair +interimap --repair "baz" "foo.bar" + +# 6 updates with \Answered (luid 4,8,11:13,16), 2 of which (luid 12,13) vanished from remote +# 3 updates with \Seen (ruid 6,8,10), 1 of which (uid 10) vanished from remote +# luid 16 <-> ruid 8 has both \Answered and \Seen +xcgrep 5 '^WARNING: Missed flag update in foo\.bar for ' <"$STDERR" +xcgrep 5 '^WARNING: Conflicting flag update in foo\.bar ' <"$STDERR" + +# luid 2 <-> ruid 10 +xcgrep 1 -E '^WARNING: Pair \(lUID,rUID\) = \([0-9]+,[0-9]+\) vanished from foo\.bar\. Repairing\.$' <"$STDERR" + +# 6-1 (luid 2 <-> ruid 10 is gone from both) +xcgrep 5 -E '^local\(foo\.bar\): WARNING: UID [0-9]+ disappeared\. Downloading remote UID [0-9]+ again\.$' <"$STDERR" + +# 6-1 (luid 2 <-> ruid 10 is gone from both) +xcgrep 3 -E '^remote\(foo~bar\): WARNING: UID [0-9]+ disappeared\. Downloading local UID [0-9]+ again\.$' <"$STDERR" + +xgrep -E '^local\(baz\): Removed 24 UID\(s\) ' <"$STDERR" +xgrep -E '^remote\(baz\): Removed 5 UID\(s\) ' <"$STDERR" + +# pining UIDs here is not very robust... +xgrep -E '^local\(foo\.bar\): Updated flags \(\\Answered \\Seen\) for UID 16$' <"$STDERR" +xgrep -E '^local\(foo\.bar\): Updated flags \(\\Seen\) for UID 14$' <"$STDERR" +xgrep -E '^remote\(foo~bar\): Updated flags \(\\Answered \\Seen\) for UID 8$' <"$STDERR" +xgrep -E '^remote\(foo~bar\): Updated flags \(\\Answered\) for UID 3,12,16$' <"$STDERR" + +# luid 17 +xcgrep 1 -E '^remote\(foo~bar\): WARNING: No match for modified local UID [0-9]+\. Downloading again\.' <"$STDERR" + +xgrep -E '^local\(foo\.bar\): Added 5 UID\(s\) ' <"$STDERR" +xgrep -E '^remote\(foo~bar\): Added 4 UID\(s\) ' <"$STDERR" +xgrep -E '^local\(foo\.bar\): Added 1 UID\(s\) ' <"$STDERR" # the new message + +check_mailbox_list +check_mailboxes_status "baz" "foo.bar" + +interimap +check_mailboxes_status "baz" "foo.bar" "INBOX" + +# vim: set filetype=sh : diff --git a/tests/06-largeint/local.conf b/tests/06-largeint/local.conf new file mode 100644 index 0000000..9c838fd --- /dev/null +++ b/tests/06-largeint/local.conf @@ -0,0 +1,5 @@ +namespace inbox { + location = maildir:~/inbox:LAYOUT=index + inbox = yes + list = yes +} diff --git a/tests/06-largeint/remote.conf b/tests/06-largeint/remote.conf new file mode 100644 index 0000000..9c838fd --- /dev/null +++ b/tests/06-largeint/remote.conf @@ -0,0 +1,5 @@ +namespace inbox { + location = maildir:~/inbox:LAYOUT=index + inbox = yes + list = yes +} diff --git a/tests/06-largeint/run b/tests/06-largeint/run new file mode 100644 index 0000000..edcbd31 --- /dev/null +++ b/tests/06-largeint/run @@ -0,0 +1,38 @@ +doveadm -u "local" mailbox create "foo" "bar" "baz" +doveadm -u "remote" mailbox create "foo" "bar" "baz" + +doveadm -u "local" mailbox update --uid-validity 1 "INBOX" +doveadm -u "local" mailbox update --uid-validity 2147483647 "foo" # 2^31-1 +doveadm -u "local" mailbox update --uid-validity 2147483648 "bar" # 2^31 +doveadm -u "local" mailbox update --uid-validity 4294967295 "baz" # 2^32-1 + +doveadm -u "remote" mailbox update --uid-validity 4294967295 "INBOX" # 2^32-1 +doveadm -u "remote" mailbox update --uid-validity 2147483648 "foo" # 2^31 +doveadm -u "remote" mailbox update --uid-validity 2147483647 "bar" # 2^31-1 +doveadm -u "remote" mailbox update --uid-validity 1 "baz" # + +run() { + local u m + for u in local remote; do + for m in "INBOX" "foo" "bar" "baz"; do + sample_message | deliver -u "$u" -- -m "$m" + done + done + interimap + check_mailbox_status "INBOX" "foo" "bar" "baz" +} +run + +# raise UIDNEXT AND HIGHESTMODSEQ close to the max values (resp. 2^32-1 och 2^63-1) +doveadm -u "local" mailbox update --min-next-uid 2147483647 --min-highest-modseq 9223372036854775807 "INBOX" # 2^31-1, 2^63-1 +doveadm -u "local" mailbox update --min-next-uid 2147483647 --min-highest-modseq 9223372036854775807 "foo" # 2^31-1, 2^63-1 +doveadm -u "local" mailbox update --min-next-uid 2147483648 --min-highest-modseq 9223372036854775808 "bar" # 2^31, 2^63 +doveadm -u "local" mailbox update --min-next-uid 2147483648 --min-highest-modseq 9223372036854775808 "baz" # 2^31, 2^63 + +doveadm -u "remote" mailbox update --min-next-uid 4294967168 --min-highest-modseq 18446744073709551488 "INBOX" # 2^32-128, 2^64-128 +doveadm -u "remote" mailbox update --min-next-uid 2147483776 --min-highest-modseq 9223372036854775936 "foo" # 2^31+128, 2^63+128 +doveadm -u "remote" mailbox update --min-next-uid 2147483648 --min-highest-modseq 9223372036854775808 "bar" # 2^31, 2^63 + +run + +# vim: set filetype=sh : diff --git a/tests/07-sync-live-multi/local.conf b/tests/07-sync-live-multi/local.conf new file mode 100644 index 0000000..baae39d --- /dev/null +++ b/tests/07-sync-live-multi/local.conf @@ -0,0 +1,30 @@ +namespace inbox { + separator = / + location = dbox:~/inbox:LAYOUT=index + inbox = yes + list = yes +} + +namespace foo { + separator = / + prefix = foo/ + location = dbox:~/foo:LAYOUT=index + inbox = no + list = yes +} + +namespace bar { + separator = / + prefix = bar/ + location = dbox:~/bar:LAYOUT=index + inbox = no + list = yes +} + +namespace baz { + separator = / + prefix = baz/ + location = dbox:~/baz:LAYOUT=index + inbox = no + list = yes +} diff --git a/tests/07-sync-live-multi/remote.conf b/tests/07-sync-live-multi/remote.conf new file mode 100644 index 0000000..3267182 --- /dev/null +++ b/tests/07-sync-live-multi/remote.conf @@ -0,0 +1,6 @@ +namespace inbox { + separator = ^ + location = dbox:~/inbox:LAYOUT=index + inbox = yes + list = yes +} diff --git a/tests/07-sync-live-multi/remote2.conf b/tests/07-sync-live-multi/remote2.conf new file mode 100644 index 0000000..062429e --- /dev/null +++ b/tests/07-sync-live-multi/remote2.conf @@ -0,0 +1,6 @@ +namespace inbox { + separator = "\\" + location = dbox:~/inbox:LAYOUT=index + inbox = yes + list = yes +} diff --git a/tests/07-sync-live-multi/remote3.conf b/tests/07-sync-live-multi/remote3.conf new file mode 100644 index 0000000..a4b9b1c --- /dev/null +++ b/tests/07-sync-live-multi/remote3.conf @@ -0,0 +1,6 @@ +namespace inbox { + separator = "?" + location = dbox:~/inbox:LAYOUT=index + inbox = yes + list = yes +} diff --git a/tests/07-sync-live-multi/run b/tests/07-sync-live-multi/run new file mode 100644 index 0000000..bf0d2f5 --- /dev/null +++ b/tests/07-sync-live-multi/run @@ -0,0 +1,138 @@ +# add references to each interimap instance +sed -ri 's#^\[local\]$#&\nlist-reference = foo/#' "$XDG_CONFIG_HOME/interimap/config" +sed -ri 's#^\[local\]$#&\nlist-reference = bar/#' "$XDG_CONFIG_HOME/interimap/config2" +sed -ri 's#^\[local\]$#&\nlist-reference = baz/#' "$XDG_CONFIG_HOME/interimap/config3" + +# create databases +interimap --config="config" +interimap --config="config2" +interimap --config="config3" + +# start long-lived interimap processes +interimap --config="config" --watch=1 & pid=$! +interimap --config="config2" --watch=1 & pid2=$! +interimap --config="config3" --watch=1 & pid3=$! + +abort() { + # kill interimap process and its children + pkill -P "$pid" -TERM + kill -TERM "$pid" + pkill -P "$pid2" -TERM + kill -TERM "$pid2" + pkill -P "$pid3" -TERM + kill -TERM "$pid3" + wait +} +trap abort EXIT INT TERM + + +# mailbox list (as seen on local) and alphabet +declare -a mailboxes=( "INBOX" ) alphabet=() +str="#+-0123456789@ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz" +for ((i=0; i < ${#str}; i++)); do + alphabet[i]="${str:i:1}" +done + +declare -a targets=( "local" "remote" "remote2" "remote3" ) + +timer=$(( $(date +%s) + 30 )) +while [ $(date +%s) -le $timer ]; do + # create new mailbox with 10% probability + if [ $(shuf -n1 -i0-9) -eq 0 ]; then + u="$(shuf -n1 -e -- "${targets[@]}")" # choose target at random + case "$u" in + local) ns="$(shuf -n1 -e "foo/" "bar/" "baz/")";; + remote) ns="foo/";; + remote2) ns="bar/";; + remote3) ns="baz/";; + *) echo "Uh?" >&2; exit 1;; + esac + + m= + d=$(shuf -n1 -i1-3) # random depth + for (( i=0; i < d; i++)); do + l=$(shuf -n1 -i1-16) + m="${m:+$m/}$(shuf -n "$l" -e -- "${alphabet[@]}" | tr -d '\n')" + done + mailboxes+=( "$ns$m" ) + case "$u" in + local) m="$ns$m";; + remote) m="${m//\//^}";; + remote2) m="${m//\//\\}";; + remote3) m="${m//\//\?}";; + *) echo "Uh?" >&2; exit 1;; + esac + doveadm -u "$u" mailbox create -- "$m" + fi + + # EXPUNGE some messages + u="$(shuf -n1 -e -- "${targets[@]}")" # choose target at random + n="$(shuf -n1 -i0-3)" + while read guid uid; do + doveadm -u "$u" expunge mailbox-guid "$guid" uid "$uid" + done < <(doveadm -u "$u" search all | shuf -n "$n") + + # mark some existing messages as read (toggle \Seen flag as unlike other + # flags it's easier to query and check_mailboxes_status checks it) + u="$(shuf -n1 -e -- "${targets[@]}")" # choose target at random + n="$(shuf -n1 -i0-9)" + while read guid uid; do + a="$(shuf -n1 -e add remove replace)" + doveadm -u "$u" flags "$a" "\\Seen" mailbox-guid "$guid" uid "$uid" + done < <(doveadm -u "$u" search all | shuf -n "$n") + + # select at random a mailbox where to deliver some messages + u="$(shuf -n1 -e "local" "remote")" # choose target at random + m="$(shuf -n1 -e -- "${mailboxes[@]}")" + if [ "$u" = "remote" ]; then + case "$m" in + foo/*) u="remote"; m="${m#foo/}"; m="${m//\//^}";; + bar/*) u="remote2"; m="${m#bar/}"; m="${m//\//\\}";; + baz/*) u="remote3"; m="${m#baz/}"; m="${m//\//\?}";; + INBOX) u="$(shuf -n1 -e "remote" "remote2" "remote3")";; + *) echo "Uh? $m" >&2; exit 1;; + esac + fi + + # deliver between 1 and 5 messages to the chosen mailbox + n="$(shuf -n1 -i1-5)" + for (( i=0; i < n; i++)); do + sample_message | deliver -u "$u" -- -m "$m" + done + + # sleep a little bit + sleep "0.$(shuf -n1 -i1-99)" +done + +# wait a little longer so interimap has time to run loop() again and +# synchronize outstanding changes, then terminate the processes we +# started above +sleep 2 + +abort +trap - EXIT INT TERM + +# check that the mailbox lists match +diff -u --label="local/mailboxes" --label="remote/mailboxes" \ + <( doveadm -u "local" mailbox list | sed -n "s,^foo/,,p" | sort ) \ + <( doveadm -u "remote" mailbox list | tr '^' '/' | sort ) +diff -u --label="local/mailboxes" --label="remote2/mailboxes" \ + <( doveadm -u "local" mailbox list | sed -n "s,^bar/,,p" | sort ) \ + <( doveadm -u "remote2" mailbox list | tr '\\' '/' | sort ) +diff -u --label="local/mailboxes" --label="remote3/mailboxes" \ + <( doveadm -u "local" mailbox list | sed -n "s,^baz/,,p" | sort ) \ + <( doveadm -u "remote3" mailbox list | tr '?' '/' | sort ) + +for m in "${mailboxes[@]}"; do + case "$m" in + foo/*) u="remote"; mb="${m#foo/}"; mr="${mb//\//^}";; + bar/*) u="remote2"; mb="${m#bar/}"; mr="${mb//\//\\}";; + baz/*) u="remote3"; mb="${m#baz/}"; mr="${mb//\//\?}";; + INBOX) continue;; + *) echo "Uh? $m" >&2; exit 1;; + esac + blob="x'$(printf "%s" "$mb" | tr "/" "\\0" | xxd -c256 -ps)'" + check_mailbox_status2 "$blob" "$m" "$u" "$mr" +done + +# vim: set filetype=sh : diff --git a/tests/07-sync-live/local.conf b/tests/07-sync-live/local.conf new file mode 100644 index 0000000..1333540 --- /dev/null +++ b/tests/07-sync-live/local.conf @@ -0,0 +1,6 @@ +namespace inbox { + separator = . + location = dbox:~/inbox:LAYOUT=index + inbox = yes + list = yes +} diff --git a/tests/07-sync-live/remote.conf b/tests/07-sync-live/remote.conf new file mode 100644 index 0000000..3267182 --- /dev/null +++ b/tests/07-sync-live/remote.conf @@ -0,0 +1,6 @@ +namespace inbox { + separator = ^ + location = dbox:~/inbox:LAYOUT=index + inbox = yes + list = yes +} diff --git a/tests/07-sync-live/run b/tests/07-sync-live/run new file mode 100644 index 0000000..1950e0b --- /dev/null +++ b/tests/07-sync-live/run @@ -0,0 +1,80 @@ +# create database +interimap + +# start a long-lived interimap process +interimap --watch=1 & pid=$! + +abort() { + # kill interimap process and its children + pkill -P "$pid" -TERM + kill -TERM "$pid" + wait +} +trap abort EXIT INT TERM + +# mailbox list and alphabet (exclude &, / and ~, which dovecot treats specially) +declare -a mailboxes=( "INBOX" ) alphabet=() +str="!\"#\$'()+,-0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ\[\\\]_\`abcdefghijklmnopqrstuvwxyz{|}" +for ((i=0; i < ${#str}; i++)); do + alphabet[i]="${str:i:1}" +done + +timer=$(( $(date +%s) + 30 )) +while [ $(date +%s) -le $timer ]; do + # create new mailbox with 10% probability + if [ $(shuf -n1 -i0-9) -eq 0 ]; then + m= + d=$(shuf -n1 -i1-3) # random depth + for (( i=0; i < d; i++)); do + l=$(shuf -n1 -i1-16) + m="${m:+$m.}$(shuf -n "$l" -e -- "${alphabet[@]}" | tr -d '\n')" + done + mailboxes+=( "$m" ) + u="$(shuf -n1 -e "local" "remote")" # choose target at random + [ "$u" = "local" ] || m="${m//./^}" + doveadm -u "$u" mailbox create -- "$m" + fi + + # EXPUNGE some messages + u="$(shuf -n1 -e "local" "remote")" # choose target at random + n="$(shuf -n1 -i0-3)" + while read guid uid; do + doveadm -u "$u" expunge mailbox-guid "$guid" uid "$uid" + done < <(doveadm -u "$u" search all | shuf -n "$n") + + # mark some existing messages as read (toggle \Seen flag as unlike other + # flags it's easier to query and check_mailboxes_status checks it) + u="$(shuf -n1 -e "local" "remote")" # choose target at random + n="$(shuf -n1 -i0-9)" + while read guid uid; do + a="$(shuf -n1 -e add remove replace)" + doveadm -u "$u" flags "$a" "\\Seen" mailbox-guid "$guid" uid "$uid" + done < <(doveadm -u "$u" search all | shuf -n "$n") + + # select at random a mailbox where to deliver some messages + u="$(shuf -n1 -e "local" "remote")" # choose target at random + m="$(shuf -n1 -e -- "${mailboxes[@]}")" + [ "$u" = "local" ] || m="${m//./^}" + + # deliver between 1 and 5 messages to the chosen mailbox + n="$(shuf -n1 -i1-5)" + for (( i=0; i < n; i++)); do + sample_message | deliver -u "$u" -- -m "$m" + done + + # sleep a little bit + sleep "0.$(shuf -n1 -i1-99)" +done + +# wait a little longer so interimap has time to run loop() again and +# synchronize outstanding changes, then terminate the process we started +# above +sleep 2 + +abort +trap - EXIT INT TERM + +check_mailbox_list +check_mailboxes_status "${mailboxes[@]}" + +# vim: set filetype=sh : 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 |