diff --git a/t/interactive.sh b/t/interactive.sh
new file mode 100755
index 0000000..e4eaef9
--- /dev/null
+++ b/t/interactive.sh
@@ -0,0 +1,12 @@
+#!/bin/sh
+#
+# Copyright (c) 2007 Yann Dirson
+#
+
+test_description='Get a test environment for interactive experimentation.'
+
+. ./test-lib.sh
+
+bash
+
+test_done
diff --git a/t/t1800-import/patches/attribution.patch b/t/t1800-import/patches/attribution.patch
new file mode 100644
index 0000000..2b7c8f9
--- /dev/null
+++ b/t/t1800-import/patches/attribution.patch
@@ -0,0 +1,21 @@
+attribution
+
+From: Clark Williams <williams@redhat.com>
+
+
+---
+ jabberwocky.txt |    4 ++++
+ 1 files changed, 4 insertions(+), 0 deletions(-)
+
+diff --git a/jabberwocky.txt b/jabberwocky.txt
+index 066d2e8..a9dd1f3 100644
+--- a/jabberwocky.txt
++++ b/jabberwocky.txt
+@@ -32,3 +32,7 @@ O frabjous day! Callooh! Callay!'
+   Did gyre and gimble in the wabe;
+ All mimsy were the borogoves,
+   And the mome raths outgrabe.
++
++	JABBERWOCKY
++	Lewis Carroll
++	(from Through the Looking-Glass and What Alice Found There, 1872) 
diff --git a/t/t1800-import/patches/delete-extra-lines.patch b/t/t1800-import/patches/delete-extra-lines.patch
new file mode 100644
index 0000000..e5b7a65
--- /dev/null
+++ b/t/t1800-import/patches/delete-extra-lines.patch
@@ -0,0 +1,22 @@
+delete extra lines
+
+From: Clark Williams <williams@redhat.com>
+
+
+---
+ jabberwocky.txt |    2 --
+ 1 files changed, 0 insertions(+), 2 deletions(-)
+
+diff --git a/jabberwocky.txt b/jabberwocky.txt
+index 98cb716..066d2e8 100644
+--- a/jabberwocky.txt
++++ b/jabberwocky.txt
+@@ -28,8 +28,6 @@ He left it dead, and with its head
+ O frabjous day! Callooh! Callay!'
+   He chortled in his joy.
+ 
+-
+-
+ `Twas brillig, and the slithy toves
+   Did gyre and gimble in the wabe;
+ All mimsy were the borogoves,
diff --git a/t/t1800-import/patches/fifth-stanza.patch b/t/t1800-import/patches/fifth-stanza.patch
new file mode 100644
index 0000000..4f0e77c
--- /dev/null
+++ b/t/t1800-import/patches/fifth-stanza.patch
@@ -0,0 +1,22 @@
+fifth stanza
+
+From: Clark Williams <williams@redhat.com>
+
+
+---
+ jabberwocky.txt |    5 +++++
+ 1 files changed, 5 insertions(+), 0 deletions(-)
+
+diff --git a/jabberwocky.txt b/jabberwocky.txt
+index b1c2ad3..f1416dc 100644
+--- a/jabberwocky.txt
++++ b/jabberwocky.txt
+@@ -17,3 +17,8 @@ And, as in uffish thought he stood,
+   The Jabberwock, with eyes of flame,
+ Came whiffling through the tulgey wood,
+   And burbled as it came!
++
++One, two! One, two! And through and through
++  The vorpal blade went snicker-snack!
++He left it dead, and with its head
++  He went galumphing back.
diff --git a/t/t1800-import/patches/first-stanza.patch b/t/t1800-import/patches/first-stanza.patch
new file mode 100644
index 0000000..ee7818f
--- /dev/null
+++ b/t/t1800-import/patches/first-stanza.patch
@@ -0,0 +1,18 @@
+first stanza
+
+From: Clark Williams <williams@redhat.com>
+
+
+---
+ jabberwocky.txt |    4 ++++
+ 1 files changed, 4 insertions(+), 0 deletions(-)
+
+diff --git a/jabberwocky.txt b/jabberwocky.txt
+index e69de29..fba24dc 100644
+--- a/jabberwocky.txt
++++ b/jabberwocky.txt
+@@ -0,0 +1,4 @@
++`Twas brillig, and the slithy toves
++  Did gyre and gimble in the wabe:
++All mimsy were the borogoves,
++  And the mome raths outgrabe.
diff --git a/t/t1800-import/patches/fourth-stanza.patch b/t/t1800-import/patches/fourth-stanza.patch
new file mode 100644
index 0000000..eb2f8f2
--- /dev/null
+++ b/t/t1800-import/patches/fourth-stanza.patch
@@ -0,0 +1,22 @@
+fourth stanza
+
+From: Clark Williams <williams@redhat.com>
+
+
+---
+ jabberwocky.txt |    5 +++++
+ 1 files changed, 5 insertions(+), 0 deletions(-)
+
+diff --git a/jabberwocky.txt b/jabberwocky.txt
+index 6405f36..b1c2ad3 100644
+--- a/jabberwocky.txt
++++ b/jabberwocky.txt
+@@ -12,3 +12,8 @@ He took his vorpal sword in hand:
+   Long time the manxome foe he sought --
+ So rested he by the Tumtum tree,
+   And stood awhile in thought.
++
++And, as in uffish thought he stood,
++  The Jabberwock, with eyes of flame,
++Came whiffling through the tulgey wood,
++  And burbled as it came!
diff --git a/t/t1800-import/patches/second-stanza.patch b/t/t1800-import/patches/second-stanza.patch
new file mode 100644
index 0000000..bec1622
--- /dev/null
+++ b/t/t1800-import/patches/second-stanza.patch
@@ -0,0 +1,22 @@
+second stanza
+
+From: Clark Williams <williams@redhat.com>
+
+
+---
+ jabberwocky.txt |    5 +++++
+ 1 files changed, 5 insertions(+), 0 deletions(-)
+
+diff --git a/jabberwocky.txt b/jabberwocky.txt
+index fba24dc..9ed0b49 100644
+--- a/jabberwocky.txt
++++ b/jabberwocky.txt
+@@ -2,3 +2,8 @@
+   Did gyre and gimble in the wabe:
+ All mimsy were the borogoves,
+   And the mome raths outgrabe.
++
++"Beware the Jabberwock, my son!
++  The jaws that bite, the claws that catch!
++Beware the Jubjub bird, and shun
++  The frumious Bandersnatch!"
diff --git a/t/t1800-import/patches/series b/t/t1800-import/patches/series
new file mode 100644
index 0000000..5945c98
--- /dev/null
+++ b/t/t1800-import/patches/series
@@ -0,0 +1,10 @@
+# This series applies on GIT commit 6a8b6f6e2ecbcab26de7656b66b7f30eeba1ee96
+first-stanza.patch
+second-stanza.patch
+third-stanza.patch
+fourth-stanza.patch
+fifth-stanza.patch
+sixth-stanza.patch
+seventh-stanza.patch
+delete-extra-lines.patch
+attribution.patch
diff --git a/t/t1800-import/patches/seventh-stanza.patch b/t/t1800-import/patches/seventh-stanza.patch
new file mode 100644
index 0000000..555c200
--- /dev/null
+++ b/t/t1800-import/patches/seventh-stanza.patch
@@ -0,0 +1,24 @@
+seventh stanza
+
+From: Clark Williams <williams@redhat.com>
+
+
+---
+ jabberwocky.txt |    7 +++++++
+ 1 files changed, 7 insertions(+), 0 deletions(-)
+
+diff --git a/jabberwocky.txt b/jabberwocky.txt
+index bf732f5..98cb716 100644
+--- a/jabberwocky.txt
++++ b/jabberwocky.txt
+@@ -27,3 +27,10 @@ He left it dead, and with its head
+   Come to my arms, my beamish boy!
+ O frabjous day! Callooh! Callay!'
+   He chortled in his joy.
++
++
++
++`Twas brillig, and the slithy toves
++  Did gyre and gimble in the wabe;
++All mimsy were the borogoves,
++  And the mome raths outgrabe.
diff --git a/t/t1800-import/patches/sixth-stanza.patch b/t/t1800-import/patches/sixth-stanza.patch
new file mode 100644
index 0000000..2349b7e
--- /dev/null
+++ b/t/t1800-import/patches/sixth-stanza.patch
@@ -0,0 +1,22 @@
+sixth stanza
+
+From: Clark Williams <williams@redhat.com>
+
+
+---
+ jabberwocky.txt |    5 +++++
+ 1 files changed, 5 insertions(+), 0 deletions(-)
+
+diff --git a/jabberwocky.txt b/jabberwocky.txt
+index f1416dc..bf732f5 100644
+--- a/jabberwocky.txt
++++ b/jabberwocky.txt
+@@ -22,3 +22,8 @@ One, two! One, two! And through and through
+   The vorpal blade went snicker-snack!
+ He left it dead, and with its head
+   He went galumphing back.
++
++"And, has thou slain the Jabberwock?
++  Come to my arms, my beamish boy!
++O frabjous day! Callooh! Callay!'
++  He chortled in his joy.
diff --git a/t/t1800-import/patches/third-stanza.patch b/t/t1800-import/patches/third-stanza.patch
new file mode 100644
index 0000000..d942353
--- /dev/null
+++ b/t/t1800-import/patches/third-stanza.patch
@@ -0,0 +1,22 @@
+third stanza
+
+From: Clark Williams <williams@redhat.com>
+
+
+---
+ jabberwocky.txt |    5 +++++
+ 1 files changed, 5 insertions(+), 0 deletions(-)
+
+diff --git a/jabberwocky.txt b/jabberwocky.txt
+index 9ed0b49..6405f36 100644
+--- a/jabberwocky.txt
++++ b/jabberwocky.txt
+@@ -7,3 +7,8 @@ All mimsy were the borogoves,
+   The jaws that bite, the claws that catch!
+ Beware the Jubjub bird, and shun
+   The frumious Bandersnatch!"
++
++He took his vorpal sword in hand:
++  Long time the manxome foe he sought --
++So rested he by the Tumtum tree,
++  And stood awhile in thought.
diff --git a/t/test.py b/t/test.py
new file mode 100644
index 0000000..f9a2ab7
--- /dev/null
+++ b/t/test.py
@@ -0,0 +1,180 @@
+# Run the test suite in parallel.
+
+import glob
+import itertools as it
+import math
+import optparse
+import os
+import os.path
+import random
+import shutil
+import subprocess
+import sys
+import threading
+import time
+import traceback
+
+# Number of jobs to run in parallel.
+def default_num_jobs():
+    try:
+        # One job per processor should be about right.
+        import multiprocessing
+        return 2 * multiprocessing.cpu_count()
+    except ImportError:
+        # Couldn't determine number of processors (probably because
+        # Python version is < 2.6); use a conservative fallback.
+        return 4
+
+class TestQueue(object):
+    def __init__(self, tests, cleanup):
+
+        def cleanup_jobs(top_dirs):
+            for td in top_dirs:
+                for e in os.listdir(td):
+                    yield os.path.join(td, e)
+
+        self.__remaining = sorted(tests, reverse=True)
+        self.__running = set()
+        self.__success = set()
+        self.__fail = set()
+        self.__clean_jobs = set(cleanup)
+        self.__clean_todo = set(cleanup_jobs(self.__clean_jobs))
+        self.__clean_running = set()
+        self.__clean_done = set()
+        self.lock = threading.Lock()
+        self.__cv = threading.Condition(self.lock)
+    def __iter__(self):
+        return self
+
+    # True if all jobs have completed.
+    def __done(self):
+        # Called with self.lock held.
+        return (not self.__remaining and not self.__running
+                and not len(self.__clean_todo) and not self.__clean_running)
+
+    # Make progress report, and check if we're all done.
+    def __report(self):
+        # Called with self.lock held.
+        cd = len(self.__clean_done) + 1e-3  # clever way to avoid div by zero
+        cr = len(self.__clean_running)
+        ct = len(self.__clean_todo)
+        sys.stdout.write(("\rQueue: %3d,  Running: %3d,  OK: %3d,"
+                          "  Failed: %3d,  Cleanup: %3d%%")
+                         % (len(self.__remaining), len(self.__running),
+                            len(self.__success), len(self.__fail),
+                            math.floor(100.0 * cd / (cd + cr + ct))))
+        sys.stdout.flush()
+        if self.__done():
+            sys.stdout.write("\n")
+            self.__cv.notifyAll()
+
+    # Yield free jobs until none are left.
+    def next(self):
+        with self.lock:
+            if not self.__remaining:
+                raise StopIteration
+            t = self.__remaining.pop()
+            self.__running.add(t)
+            self.__report()
+            return t
+
+    # Report that a job has completed.
+    def finished(self, t, success):
+        with self.lock:
+            self.__running.remove(t)
+            (self.__success if success else self.__fail).add(t)
+            self.__report()
+
+    # Yield free cleaning jobs until none are left.
+    def cleaning_jobs(self):
+        while True:
+            with self.lock:
+                if not self.__clean_todo:
+                    return
+                c = self.__clean_todo.pop()
+                self.__clean_running.add(c)
+            yield c
+
+    # Report that a cleaning job has completed.
+    def deleted(self, c):
+        with self.lock:
+            self.__clean_running.remove(c)
+            self.__clean_done.add(c)
+            self.__report()
+
+    # Wait for all jobs to complete.
+    def wait(self):
+        with self.lock:
+            while not self.__done():
+                self.__cv.wait()
+            for c in self.__clean_jobs:
+                os.rmdir(c)
+            return set(self.__fail)
+
+def start_worker(q):
+    def w():
+        for t in q:
+            try:
+                ok = False  # assume the worst until proven otherwise
+                s = os.path.join("trash", t)
+                e = dict(os.environ)
+                e["SCRATCHDIR"] = s
+                p = subprocess.Popen([os.path.join(os.getcwd(), t), "-v"],
+                                     stdout=subprocess.PIPE,
+                                     stderr=subprocess.STDOUT,
+                                     env=e)
+                (out, err) = p.communicate()
+                assert err is None
+                with open(os.path.join(s, "output"), "w") as f:
+                    f.write(out)
+                    f.write("\nExited with code %d\n" % p.returncode)
+                if p.returncode == 0:
+                    ok = True
+            except:
+                # Log the traceback. Use the mutex so that we
+                # won't write multiple tracebacks to stderr at the
+                # same time.
+                with q.lock:
+                    traceback.print_exc()
+            finally:
+                q.finished(t, ok)
+    threading.Thread(target=w).start()
+
+def start_cleaner(q):
+    def w():
+        for c in q.cleaning_jobs():
+            try:
+                (shutil.rmtree if os.path.isdir(c) else os.remove)(c)
+            finally:
+                q.deleted(c)
+    threading.Thread(target=w).start()
+
+def main():
+    p = optparse.OptionParser()
+    p.add_option("-j", "--jobs", type="int",
+                 help="number of tests to run in parallel")
+    (opts, tests) = p.parse_args()
+    if not tests:
+        tests = glob.glob("t[0-9][0-9][0-9][0-9]-*.sh")
+    if opts.jobs is None:
+        opts.jobs = default_num_jobs()
+    print "Running %d tests in parallel" % opts.jobs
+
+    if os.path.exists("trash"):
+        os.rename("trash", "trash-being-deleted-%016x" % random.getrandbits(64))
+    os.mkdir("trash")
+    q = TestQueue(tests, glob.glob("trash-being-deleted-*"))
+    w = min(opts.jobs, len(tests))
+    for i in range(w):
+        start_worker(q)
+    for i in range(max(w / 4, 1)):
+        start_cleaner(q)
+    failed = q.wait()
+    if failed:
+        print "Failed:"
+        for t in sorted(failed):
+            print "  ", t
+        print "Done"
+
+if __name__ == "__main__":
+    main()
