test: Test thread linking in all possible delivery orders
authorAustin Clements <amdragon@MIT.EDU>
Wed, 9 Jul 2014 21:15:38 +0000 (17:15 -0400)
committerDavid Bremner <david@tethera.net>
Wed, 16 Jul 2014 10:08:02 +0000 (07:08 -0300)
These tests deliver all possible (single-root) four-message threads in
all possible orders and check that notmuch successfully links them
into threads.  These tests supersede and replace the previous and much
less thorough "T260-thread-order" tests.

There are two variants of the test: one delivers messages that
reference only their immediate parent and the other delivers messages
that reference all of their parents.  The latter test is currently
known-broken.

test/T260-thread-order.sh
test/gen-threads.py [new file with mode: 0644]

index 6c3a4b3f70a2b85124075e8696fc72ef061f2a12..b435d79fb0db0712c3479c407e0ac3a03bbaf951 100755 (executable)
@@ -2,31 +2,75 @@
 test_description="threading when messages received out of order"
 . ./test-lib.sh
 
-test_begin_subtest "Adding initial child message"
-generate_message [body]=foo "[in-reply-to]=\<parent-id\>" [subject]=brokenthreadtest '[date]="Sat, 01 Jan 2000 12:00:00 -0000"'
-output=$(NOTMUCH_NEW)
-test_expect_equal "$output" "Added 1 new message to the database."
+# Generate all single-root four message thread structures.  We'll use
+# this for multiple tests below.
+THREADS=$(python ${TEST_DIRECTORY}/gen-threads.py 4)
+nthreads=$(wc -l <<< "$THREADS")
 
-test_begin_subtest "Searching returns the message"
-output=$(notmuch search foo | notmuch_search_sanitize)
-test_expect_equal "$output" "thread:XXX   2000-01-01 [1/1] Notmuch Test Suite; brokenthreadtest (inbox unread)"
+test_begin_subtest "Messages with one parent get linked in all delivery orders"
+# In the first variant, this delivers messages that reference only
+# their immediate parent.  Hence, we should only expect threads to be
+# fully joined at the end.
+for ((n = 0; n < 4; n++)); do
+    # Deliver the n'th message of every thread
+    thread=0
+    while read -a parents; do
+        parent=${parents[$n]}
+        generate_message \
+            [id]=m$n@t$thread [in-reply-to]="\<m$parent@t$thread\>" \
+            [subject]=p$thread [from]=m$n
+        thread=$((thread + 1))
+    done <<< "$THREADS"
+    notmuch new > /dev/null
+done
+output=$(notmuch search --sort=newest-first '*' | notmuch_search_sanitize)
+expected=$(for ((i = 0; i < $nthreads; i++)); do
+        echo "thread:XXX   2001-01-05 [4/4] m3, m2, m1, m0; p$i (inbox unread)"
+    done)
+test_expect_equal "$output" "$expected"
 
-test_begin_subtest "Adding second child message"
-generate_message [body]=foo "[in-reply-to]=\<parent-id\>" [subject]=brokenthreadtest '[date]="Sat, 01 Jan 2000 12:00:00 -0000"'
-output=$(NOTMUCH_NEW)
-test_expect_equal "$output" "Added 1 new message to the database."
+test_begin_subtest "Messages with all parents get linked in all delivery orders"
+test_subtest_known_broken
+# Here we do the same thing as the previous test, but each message
+# references all of its parents.  Since every message references the
+# root of the thread, each thread should always be fully joined.  This
+# is currently broken because of the bug detailed in
+# id:8738h7kv2q.fsf@qmul.ac.uk.
+rm ${MAIL_DIR}/*
+notmuch new > /dev/null
+output=""
+expected=""
+for ((n = 0; n < 4; n++)); do
+    # Deliver the n'th message of every thread
+    thread=0
+    while read -a parents; do
+        references=""
+        parent=${parents[$n]}
+        while [[ $parent != None ]]; do
+            references="<m$parent@t$thread> $references"
+            parent=${parents[$parent]}
+        done
 
-test_begin_subtest "Searching returns both messages in one thread"
-output=$(notmuch search foo | notmuch_search_sanitize)
-test_expect_equal "$output" "thread:XXX   2000-01-01 [2/2] Notmuch Test Suite; brokenthreadtest (inbox unread)"
+        generate_message \
+            [id]=m$n@t$thread [references]="'$references'" \
+            [subject]=p$thread [from]=m$n
+        thread=$((thread + 1))
+    done <<< "$THREADS"
+    notmuch new > /dev/null
 
-test_begin_subtest "Adding parent message"
-generate_message [body]=foo [id]=parent-id [subject]=brokenthreadtest '[date]="Sat, 01 Jan 2000 12:00:00 -0000"'
-output=$(NOTMUCH_NEW)
-test_expect_equal "$output" "Added 1 new message to the database."
+    output="$output
+$(notmuch search --sort=newest-first '*' | notmuch_search_sanitize)"
 
-test_begin_subtest "Searching returns all three messages in one thread"
-output=$(notmuch search foo | notmuch_search_sanitize)
-test_expect_equal "$output" "thread:XXX   2000-01-01 [3/3] Notmuch Test Suite; brokenthreadtest (inbox unread)"
+    # Construct expected output
+    template="thread:XXX   2001-01-05 [$((n+1))/$((n+1))]"
+    for ((m = n; m > 0; m--)); do
+        template="$template m$m,"
+    done
+    expected="$expected
+$(for ((i = 0; i < $nthreads; i++)); do
+        echo "$template m0; p$i (inbox unread)"
+    done)"
+done
+test_expect_equal "$output" "$expected"
 
 test_done
diff --git a/test/gen-threads.py b/test/gen-threads.py
new file mode 100644 (file)
index 0000000..9fbb847
--- /dev/null
@@ -0,0 +1,33 @@
+# Generate all possible single-root message thread structures of size
+# argv[1].  Each output line is a thread structure, where the n'th
+# field is either a number giving the parent of message n or "None"
+# for the root.
+
+import sys
+from itertools import chain, combinations
+
+def subsets(s):
+    return chain.from_iterable(combinations(s, r) for r in range(len(s)+1))
+
+nodes = set(range(int(sys.argv[1])))
+
+# Queue of (tree, free, to_expand) where tree is a {node: parent}
+# dictionary, free is a set of unattached nodes, and to_expand is
+# itself a queue of nodes in the tree that need to be expanded.
+# The queue starts with all single-node trees.
+queue = [({root: None}, nodes - {root}, (root,)) for root in nodes]
+
+# Process queue
+while queue:
+    tree, free, to_expand = queue.pop()
+
+    if len(to_expand) == 0:
+        # Only print full-sized trees
+        if len(free) == 0:
+            print(" ".join(map(str, [msg[1] for msg in sorted(tree.items())])))
+    else:
+        # Expand node to_expand[0] with each possible set of children
+        for children in subsets(free):
+            ntree = dict(tree, **{child: to_expand[0] for child in children})
+            nfree = free.difference(children)
+            queue.append((ntree, nfree, to_expand[1:] + tuple(children)))