summary refs log tree commit diff stats
path: root/tests
diff options
context:
space:
mode:
Diffstat (limited to 'tests')
-rw-r--r--tests/Makefile8
-rw-r--r--tests/fdc-test.c192
-rwxr-xr-xtests/qemu-iotests/044117
-rw-r--r--tests/qemu-iotests/044.out6
-rw-r--r--tests/qemu-iotests/common13
-rw-r--r--tests/qemu-iotests/common.config10
-rw-r--r--tests/qemu-iotests/common.rc23
-rw-r--r--tests/qemu-iotests/group1
-rw-r--r--tests/qemu-iotests/iotests.py6
-rwxr-xr-xtests/qemu-iotests/qcow2.py9
-rw-r--r--tests/rtc-test.c40
-rw-r--r--tests/test-aio.c667
-rw-r--r--tests/test-thread-pool.c216
13 files changed, 1288 insertions, 20 deletions
diff --git a/tests/Makefile b/tests/Makefile
index 9bf0765de3..b60f0fb8f0 100644
--- a/tests/Makefile
+++ b/tests/Makefile
@@ -15,6 +15,8 @@ check-unit-y += tests/test-string-output-visitor$(EXESUF)
 check-unit-y += tests/test-coroutine$(EXESUF)
 check-unit-y += tests/test-visitor-serialization$(EXESUF)
 check-unit-y += tests/test-iov$(EXESUF)
+check-unit-y += tests/test-aio$(EXESUF)
+check-unit-y += tests/test-thread-pool$(EXESUF)
 
 check-block-$(CONFIG_POSIX) += tests/qemu-iotests-quick.sh
 
@@ -48,7 +50,9 @@ tests/check-qdict$(EXESUF): tests/check-qdict.o qdict.o qfloat.o qint.o qstring.
 tests/check-qlist$(EXESUF): tests/check-qlist.o qlist.o qint.o
 tests/check-qfloat$(EXESUF): tests/check-qfloat.o qfloat.o
 tests/check-qjson$(EXESUF): tests/check-qjson.o $(qobject-obj-y) qemu-tool.o
-tests/test-coroutine$(EXESUF): tests/test-coroutine.o $(coroutine-obj-y) $(tools-obj-y) $(block-obj-y) iov.o
+tests/test-coroutine$(EXESUF): tests/test-coroutine.o $(coroutine-obj-y) $(tools-obj-y) $(block-obj-y) iov.o libqemustub.a
+tests/test-aio$(EXESUF): tests/test-aio.o $(coroutine-obj-y) $(tools-obj-y) $(block-obj-y) libqemustub.a
+tests/test-thread-pool$(EXESUF): tests/test-thread-pool.o $(coroutine-obj-y) $(tools-obj-y) $(block-obj-y) libqemustub.a
 tests/test-iov$(EXESUF): tests/test-iov.o iov.o
 
 tests/test-qapi-types.c tests/test-qapi-types.h :\
@@ -81,7 +85,7 @@ TARGETS=$(patsubst %-softmmu,%, $(filter %-softmmu,$(TARGET_DIRS)))
 QTEST_TARGETS=$(foreach TARGET,$(TARGETS), $(if $(check-qtest-$(TARGET)-y), $(TARGET),))
 check-qtest-$(CONFIG_POSIX)=$(foreach TARGET,$(TARGETS), $(check-qtest-$(TARGET)-y))
 
-qtest-obj-y = tests/libqtest.o $(oslib-obj-y)
+qtest-obj-y = tests/libqtest.o $(oslib-obj-y) libqemustub.a
 $(check-qtest-y): $(qtest-obj-y)
 
 .PHONY: check-help
diff --git a/tests/fdc-test.c b/tests/fdc-test.c
index fa7441110d..4b0301da46 100644
--- a/tests/fdc-test.c
+++ b/tests/fdc-test.c
@@ -48,13 +48,17 @@ enum {
 
 enum {
     CMD_SENSE_INT           = 0x08,
+    CMD_READ_ID             = 0x0a,
     CMD_SEEK                = 0x0f,
+    CMD_VERIFY              = 0x16,
     CMD_READ                = 0xe6,
     CMD_RELATIVE_SEEK_OUT   = 0x8f,
     CMD_RELATIVE_SEEK_IN    = 0xcf,
 };
 
 enum {
+    BUSY    = 0x10,
+    NONDMA  = 0x20,
     RQM     = 0x80,
     DIO     = 0x40,
 
@@ -110,7 +114,7 @@ static void ack_irq(uint8_t *pcn)
     g_assert(!get_irq(FLOPPY_IRQ));
 }
 
-static uint8_t send_read_command(void)
+static uint8_t send_read_command(uint8_t cmd)
 {
     uint8_t drive = 0;
     uint8_t head = 0;
@@ -126,7 +130,7 @@ static uint8_t send_read_command(void)
 
     uint8_t ret = 0;
 
-    floppy_send(CMD_READ);
+    floppy_send(cmd);
     floppy_send(head << 2 | drive);
     g_assert(!get_irq(FLOPPY_IRQ));
     floppy_send(cyl);
@@ -152,7 +156,70 @@ static uint8_t send_read_command(void)
     }
 
     st0 = floppy_recv();
-    if (st0 != 0x60) {
+    if (st0 != 0x40) {
+        ret = 1;
+    }
+
+    floppy_recv();
+    floppy_recv();
+    floppy_recv();
+    floppy_recv();
+    floppy_recv();
+    floppy_recv();
+
+    return ret;
+}
+
+static uint8_t send_read_no_dma_command(int nb_sect, uint8_t expected_st0)
+{
+    uint8_t drive = 0;
+    uint8_t head = 0;
+    uint8_t cyl = 0;
+    uint8_t sect_addr = 1;
+    uint8_t sect_size = 2;
+    uint8_t eot = nb_sect;
+    uint8_t gap = 0x1b;
+    uint8_t gpl = 0xff;
+
+    uint8_t msr = 0;
+    uint8_t st0;
+
+    uint8_t ret = 0;
+
+    floppy_send(CMD_READ);
+    floppy_send(head << 2 | drive);
+    g_assert(!get_irq(FLOPPY_IRQ));
+    floppy_send(cyl);
+    floppy_send(head);
+    floppy_send(sect_addr);
+    floppy_send(sect_size);
+    floppy_send(eot);
+    floppy_send(gap);
+    floppy_send(gpl);
+
+    uint16_t i = 0;
+    uint8_t n = 2;
+    for (; i < n; i++) {
+        msr = inb(FLOPPY_BASE + reg_msr);
+        if (msr == (BUSY | NONDMA | DIO | RQM)) {
+            break;
+        }
+        sleep(1);
+    }
+
+    if (i >= n) {
+        return 1;
+    }
+
+    /* Non-DMA mode */
+    for (i = 0; i < 512 * 2 * nb_sect; i++) {
+        msr = inb(FLOPPY_BASE + reg_msr);
+        assert_bit_set(msr, BUSY | RQM | DIO);
+        inb(FLOPPY_BASE + reg_fifo);
+    }
+
+    st0 = floppy_recv();
+    if (st0 != expected_st0) {
         ret = 1;
     }
 
@@ -213,11 +280,11 @@ static void test_read_without_media(void)
 {
     uint8_t ret;
 
-    ret = send_read_command();
+    ret = send_read_command(CMD_READ);
     g_assert(ret == 0);
 }
 
-static void test_media_change(void)
+static void test_media_insert(void)
 {
     uint8_t dir;
 
@@ -245,6 +312,13 @@ static void test_media_change(void)
     assert_bit_clear(dir, DSKCHG);
     dir = inb(FLOPPY_BASE + reg_dir);
     assert_bit_clear(dir, DSKCHG);
+}
+
+static void test_media_change(void)
+{
+    uint8_t dir;
+
+    test_media_insert();
 
     /* Eject the floppy and check that DSKCHG is set. Reading it out doesn't
      * reset the bit. */
@@ -320,6 +394,108 @@ static void test_relative_seek(void)
     g_assert(pcn == 0);
 }
 
+static void test_read_id(void)
+{
+    uint8_t drive = 0;
+    uint8_t head = 0;
+    uint8_t cyl;
+    uint8_t st0;
+
+    /* Seek to track 0 and check with READ ID */
+    send_seek(0);
+
+    floppy_send(CMD_READ_ID);
+    g_assert(!get_irq(FLOPPY_IRQ));
+    floppy_send(head << 2 | drive);
+
+    while (!get_irq(FLOPPY_IRQ)) {
+        /* qemu involves a timer with READ ID... */
+        clock_step(1000000000LL / 50);
+    }
+
+    st0 = floppy_recv();
+    floppy_recv();
+    floppy_recv();
+    cyl = floppy_recv();
+    head = floppy_recv();
+    floppy_recv();
+    floppy_recv();
+
+    g_assert_cmpint(cyl, ==, 0);
+    g_assert_cmpint(head, ==, 0);
+    g_assert_cmpint(st0, ==, head << 2);
+
+    /* Seek to track 8 on head 1 and check with READ ID */
+    head = 1;
+    cyl = 8;
+
+    floppy_send(CMD_SEEK);
+    floppy_send(head << 2 | drive);
+    g_assert(!get_irq(FLOPPY_IRQ));
+    floppy_send(cyl);
+    g_assert(get_irq(FLOPPY_IRQ));
+    ack_irq(NULL);
+
+    floppy_send(CMD_READ_ID);
+    g_assert(!get_irq(FLOPPY_IRQ));
+    floppy_send(head << 2 | drive);
+
+    while (!get_irq(FLOPPY_IRQ)) {
+        /* qemu involves a timer with READ ID... */
+        clock_step(1000000000LL / 50);
+    }
+
+    st0 = floppy_recv();
+    floppy_recv();
+    floppy_recv();
+    cyl = floppy_recv();
+    head = floppy_recv();
+    floppy_recv();
+    floppy_recv();
+
+    g_assert_cmpint(cyl, ==, 8);
+    g_assert_cmpint(head, ==, 1);
+    g_assert_cmpint(st0, ==, head << 2);
+}
+
+static void test_read_no_dma_1(void)
+{
+    uint8_t ret;
+
+    outb(FLOPPY_BASE + reg_dor, inb(FLOPPY_BASE + reg_dor) & ~0x08);
+    send_seek(0);
+    ret = send_read_no_dma_command(1, 0x04);
+    g_assert(ret == 0);
+}
+
+static void test_read_no_dma_18(void)
+{
+    uint8_t ret;
+
+    outb(FLOPPY_BASE + reg_dor, inb(FLOPPY_BASE + reg_dor) & ~0x08);
+    send_seek(0);
+    ret = send_read_no_dma_command(18, 0x04);
+    g_assert(ret == 0);
+}
+
+static void test_read_no_dma_19(void)
+{
+    uint8_t ret;
+
+    outb(FLOPPY_BASE + reg_dor, inb(FLOPPY_BASE + reg_dor) & ~0x08);
+    send_seek(0);
+    ret = send_read_no_dma_command(19, 0x20);
+    g_assert(ret == 0);
+}
+
+static void test_verify(void)
+{
+    uint8_t ret;
+
+    ret = send_read_command(CMD_VERIFY);
+    g_assert(ret == 0);
+}
+
 /* success if no crash or abort */
 static void fuzz_registers(void)
 {
@@ -369,6 +545,12 @@ int main(int argc, char **argv)
     qtest_add_func("/fdc/media_change", test_media_change);
     qtest_add_func("/fdc/sense_interrupt", test_sense_interrupt);
     qtest_add_func("/fdc/relative_seek", test_relative_seek);
+    qtest_add_func("/fdc/read_id", test_read_id);
+    qtest_add_func("/fdc/verify", test_verify);
+    qtest_add_func("/fdc/media_insert", test_media_insert);
+    qtest_add_func("/fdc/read_no_dma_1", test_read_no_dma_1);
+    qtest_add_func("/fdc/read_no_dma_18", test_read_no_dma_18);
+    qtest_add_func("/fdc/read_no_dma_19", test_read_no_dma_19);
     qtest_add_func("/fdc/fuzz-registers", fuzz_registers);
 
     ret = g_test_run();
diff --git a/tests/qemu-iotests/044 b/tests/qemu-iotests/044
new file mode 100755
index 0000000000..11ea0f4d35
--- /dev/null
+++ b/tests/qemu-iotests/044
@@ -0,0 +1,117 @@
+#!/usr/bin/env python
+#
+# Tests growing a large refcount table.
+#
+# Copyright (C) 2012 Red Hat, Inc.
+#
+# 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 2 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/>.
+#
+
+import time
+import os
+import qcow2
+from qcow2 import QcowHeader
+import iotests
+from iotests import qemu_img, qemu_img_verbose, qemu_io
+import struct
+import subprocess
+
+test_img = os.path.join(iotests.test_dir, 'test.img')
+
+class TestRefcountTableGrowth(iotests.QMPTestCase):
+    '''Abstract base class for image mirroring test cases'''
+
+    def preallocate(self, name):
+        fd = open(name, "r+b")
+        try:
+            off_reftable = 512
+            off_refblock = off_reftable + (512 * 512)
+            off_l1       = off_refblock + (512 * 512 * 64)
+            off_l2       = off_l1 + (512 * 512 * 4 * 8)
+            off_data     = off_l2 + (512 * 512 * 4 * 512)
+
+            # Write a new header
+            h = QcowHeader(fd)
+            h.refcount_table_offset = off_reftable
+            h.refcount_table_clusters = 512
+            h.l1_table_offset = off_l1
+            h.l1_size = 512 * 512 * 4
+            h.update(fd)
+
+            # Write a refcount table
+            fd.seek(off_reftable)
+
+            for i in xrange(0, h.refcount_table_clusters):
+                sector = ''.join(struct.pack('>Q',
+                    off_refblock + i * 64 * 512 + j * 512)
+                    for j in xrange(0, 64))
+                fd.write(sector)
+
+            # Write the refcount blocks
+            assert(fd.tell() == off_refblock)
+            sector = ''.join(struct.pack('>H', 1) for j in xrange(0, 64 * 256))
+            for block in xrange(0, h.refcount_table_clusters):
+                fd.write(sector)
+
+            # Write the L1 table
+            assert(fd.tell() == off_l1)
+            assert(off_l2 + 512 * h.l1_size == off_data)
+            table = ''.join(struct.pack('>Q', (1 << 63) | off_l2 + 512 * j)
+                for j in xrange(0, h.l1_size))
+            fd.write(table)
+
+            # Write the L2 tables
+            assert(fd.tell() == off_l2)
+            img_file_size = h.refcount_table_clusters * 64 * 256 * 512
+            remaining = img_file_size - off_data
+
+            off = off_data
+            while remaining > 1024 * 512:
+                pytable = list((1 << 63) | off + 512 * j
+                    for j in xrange(0, 1024))
+                table = struct.pack('>1024Q', *pytable)
+                fd.write(table)
+                remaining = remaining - 1024 * 512
+                off = off + 1024 * 512
+
+            table = ''.join(struct.pack('>Q', (1 << 63) | off + 512 * j)
+                for j in xrange(0, remaining / 512))
+            fd.write(table)
+
+
+            # Data
+            fd.truncate(img_file_size)
+
+
+        finally:
+            fd.close()
+
+
+    def setUp(self):
+        qemu_img('create', '-f', iotests.imgfmt, '-o', 'cluster_size=512', test_img, '16G')
+        self.preallocate(test_img)
+        pass
+
+
+    def tearDown(self):
+        os.remove(test_img)
+        pass
+
+    def test_grow_refcount_table(self):
+        qemu_io('-c', 'write 3800M 1M', test_img)
+        qemu_img_verbose('check' , test_img)
+        pass
+
+if __name__ == '__main__':
+    iotests.main(supported_fmts=['qcow2'])
diff --git a/tests/qemu-iotests/044.out b/tests/qemu-iotests/044.out
new file mode 100644
index 0000000000..7a4007137d
--- /dev/null
+++ b/tests/qemu-iotests/044.out
@@ -0,0 +1,6 @@
+No errors were found on the image.
+.
+----------------------------------------------------------------------
+Ran 1 tests
+
+OK
diff --git a/tests/qemu-iotests/common b/tests/qemu-iotests/common
index 1f6fdf5c56..b3aad89e2c 100644
--- a/tests/qemu-iotests/common
+++ b/tests/qemu-iotests/common
@@ -136,6 +136,7 @@ check options
     -vmdk               test vmdk
     -rbd                test rbd
     -sheepdog           test sheepdog
+    -nbd                test nbd
     -xdiff		graphical mode diff
     -nocache		use O_DIRECT on backing file
     -misalign		misalign memory allocations
@@ -197,12 +198,14 @@ testlist options
 	    IMGPROTO=rbd
 	    xpand=false
 	    ;;
-
 	-sheepdog)
 	    IMGPROTO=sheepdog
 	    xpand=false
 	    ;;
-
+	-nbd)
+	    IMGPROTO=nbd
+	    xpand=false
+	    ;;
 	-nocache)
 	    QEMU_IO_OPTIONS="$QEMU_IO_OPTIONS --nocache"
 	    xpand=false
@@ -350,7 +353,11 @@ fi
 
 [ "$QEMU" = "" ] && _fatal "qemu not found"
 [ "$QEMU_IMG" = "" ] && _fatal "qemu-img not found"
-[ "$QEMU_IO" = "" ] && _fatal "qemu-img not found"
+[ "$QEMU_IO" = "" ] && _fatal "qemu-io not found"
+
+if [ "$IMGPROTO" = "nbd" ] ; then
+    [ "$QEMU_NBD" = "" ] && _fatal "qemu-nbd not found"
+fi
 
 if $valgrind; then
     export REAL_QEMU_IO="$QEMU_IO_PROG"
diff --git a/tests/qemu-iotests/common.config b/tests/qemu-iotests/common.config
index df082e750c..08a3f100b8 100644
--- a/tests/qemu-iotests/common.config
+++ b/tests/qemu-iotests/common.config
@@ -90,21 +90,23 @@ export PS_ALL_FLAGS="-ef"
 if [ -z "$QEMU_PROG" ]; then
     export QEMU_PROG="`set_prog_path qemu`"
 fi
-[ "$QEMU_PROG" = "" ] && _fatal "qemu not found"
 
 if [ -z "$QEMU_IMG_PROG" ]; then
     export QEMU_IMG_PROG="`set_prog_path qemu-img`"
 fi
-[ "$QEMU_IMG_PROG" = "" ] && _fatal "qemu-img not found"
 
 if [ -z "$QEMU_IO_PROG" ]; then
     export QEMU_IO_PROG="`set_prog_path qemu-io`"
 fi
-[ "$QEMU_IO_PROG" = "" ] && _fatal "qemu-io not found"
+
+if [ -z "$QEMU_NBD_PROG" ]; then
+    export QEMU_NBD_PROG="`set_prog_path qemu-nbd`"
+fi
 
 export QEMU=$QEMU_PROG
-export QEMU_IMG=$QEMU_IMG_PROG 
+export QEMU_IMG=$QEMU_IMG_PROG
 export QEMU_IO="$QEMU_IO_PROG $QEMU_IO_OPTIONS"
+export QEMU_NBD=$QEMU_NBD_PROG
 
 [ -f /etc/qemu-iotest.config ]       && . /etc/qemu-iotest.config
 
diff --git a/tests/qemu-iotests/common.rc b/tests/qemu-iotests/common.rc
index 334534f22c..aef5f52b4f 100644
--- a/tests/qemu-iotests/common.rc
+++ b/tests/qemu-iotests/common.rc
@@ -49,6 +49,9 @@ umask 022
 
 if [ "$IMGPROTO" = "file" ]; then
     TEST_IMG=$TEST_DIR/t.$IMGFMT
+elif [ "$IMGPROTO" = "nbd" ]; then
+    TEST_IMG_FILE=$TEST_DIR/t.$IMGFMT
+    TEST_IMG="nbd:127.0.0.1:10810"
 else
     TEST_IMG=$IMGPROTO:$TEST_DIR/t.$IMGFMT
 fi
@@ -86,6 +89,13 @@ _make_test_img()
     local extra_img_options=""
     local image_size=$*
     local optstr=""
+    local img_name=""
+
+    if [ -n "$TEST_IMG_FILE" ]; then
+        img_name=$TEST_IMG_FILE
+    else
+        img_name=$TEST_IMG
+    fi
 
     if [ -n "$IMGOPTS" ]; then
         optstr=$(_optstr_add "$optstr" "$IMGOPTS")
@@ -104,7 +114,7 @@ _make_test_img()
     fi
 
     # XXX(hch): have global image options?
-    $QEMU_IMG create -f $IMGFMT $extra_img_options $TEST_IMG $image_size | \
+    $QEMU_IMG create -f $IMGFMT $extra_img_options $img_name $image_size | \
         sed -e "s#$IMGPROTO:$TEST_DIR#TEST_DIR#g" \
             -e "s#$TEST_DIR#TEST_DIR#g" \
             -e "s#$IMGFMT#IMGFMT#g" \
@@ -115,12 +125,23 @@ _make_test_img()
             -e "s# compat6=\\(on\\|off\\)##g" \
             -e "s# static=\\(on\\|off\\)##g" \
             -e "s# lazy_refcounts=\\(on\\|off\\)##g"
+
+    # Start an NBD server on the image file, which is what we'll be talking to
+    if [ $IMGPROTO = "nbd" ]; then
+        eval "$QEMU_NBD -v -t -b 127.0.0.1 -p 10810  $TEST_IMG_FILE &"
+        QEMU_NBD_PID=$!
+        sleep 1 # FIXME: qemu-nbd needs to be listening before we continue
+    fi
 }
 
 _cleanup_test_img()
 {
     case "$IMGPROTO" in
 
+        nbd)
+            kill $QEMU_NBD_PID
+            rm -f $TEST_IMG_FILE
+            ;;
         file)
             rm -f $TEST_DIR/t.$IMGFMT
             rm -f $TEST_DIR/t.$IMGFMT.orig
diff --git a/tests/qemu-iotests/group b/tests/qemu-iotests/group
index ac86f54ae3..a4a9044f24 100644
--- a/tests/qemu-iotests/group
+++ b/tests/qemu-iotests/group
@@ -50,3 +50,4 @@
 041 rw auto backing
 042 rw auto quick
 043 rw auto backing
+044 rw auto
diff --git a/tests/qemu-iotests/iotests.py b/tests/qemu-iotests/iotests.py
index 735c6745d7..b2eaf20f0b 100644
--- a/tests/qemu-iotests/iotests.py
+++ b/tests/qemu-iotests/iotests.py
@@ -42,6 +42,10 @@ def qemu_img(*args):
     devnull = open('/dev/null', 'r+')
     return subprocess.call(qemu_img_args + list(args), stdin=devnull, stdout=devnull)
 
+def qemu_img_verbose(*args):
+    '''Run qemu-img without supressing its output and return the exit code'''
+    return subprocess.call(qemu_img_args + list(args))
+
 def qemu_io(*args):
     '''Run qemu-io and return the stdout data'''
     args = qemu_io_args + list(args)
@@ -182,4 +186,4 @@ def main(supported_fmts=[]):
     try:
         unittest.main(testRunner=MyTestRunner)
     finally:
-        sys.stderr.write(re.sub(r'Ran (\d+) test[s] in [\d.]+s', r'Ran \1 tests', output.getvalue()))
+        sys.stderr.write(re.sub(r'Ran (\d+) tests? in [\d.]+s', r'Ran \1 tests', output.getvalue()))
diff --git a/tests/qemu-iotests/qcow2.py b/tests/qemu-iotests/qcow2.py
index 97f37707bc..fecf5b9a59 100755
--- a/tests/qemu-iotests/qcow2.py
+++ b/tests/qemu-iotests/qcow2.py
@@ -233,8 +233,9 @@ def usage():
     for name, handler, num_args, desc in cmds:
         print "    %-20s - %s" % (name, desc)
 
-if len(sys.argv) < 3:
-    usage()
-    sys.exit(1)
+if __name__ == '__main__':
+    if len(sys.argv) < 3:
+        usage()
+        sys.exit(1)
 
-main(sys.argv[1], sys.argv[2], sys.argv[3:])
+    main(sys.argv[1], sys.argv[2], sys.argv[3:])
diff --git a/tests/rtc-test.c b/tests/rtc-test.c
index 7fdc94a3de..02edbf5727 100644
--- a/tests/rtc-test.c
+++ b/tests/rtc-test.c
@@ -327,6 +327,45 @@ static void fuzz_registers(void)
     }
 }
 
+static void register_b_set_flag(void)
+{
+    /* Enable binary-coded decimal (BCD) mode and SET flag in Register B*/
+    cmos_write(RTC_REG_B, (cmos_read(RTC_REG_B) & ~REG_B_DM) | REG_B_SET);
+
+    cmos_write(RTC_REG_A, 0x76);
+    cmos_write(RTC_YEAR, 0x11);
+    cmos_write(RTC_CENTURY, 0x20);
+    cmos_write(RTC_MONTH, 0x02);
+    cmos_write(RTC_DAY_OF_MONTH, 0x02);
+    cmos_write(RTC_HOURS, 0x02);
+    cmos_write(RTC_MINUTES, 0x04);
+    cmos_write(RTC_SECONDS, 0x58);
+    cmos_write(RTC_REG_A, 0x26);
+
+    /* Since SET flag is still enabled, these are equality checks. */
+    g_assert_cmpint(cmos_read(RTC_HOURS), ==, 0x02);
+    g_assert_cmpint(cmos_read(RTC_MINUTES), ==, 0x04);
+    g_assert_cmpint(cmos_read(RTC_SECONDS), ==, 0x58);
+    g_assert_cmpint(cmos_read(RTC_DAY_OF_MONTH), ==, 0x02);
+    g_assert_cmpint(cmos_read(RTC_MONTH), ==, 0x02);
+    g_assert_cmpint(cmos_read(RTC_YEAR), ==, 0x11);
+    g_assert_cmpint(cmos_read(RTC_CENTURY), ==, 0x20);
+
+    /* Disable SET flag in Register B */
+    cmos_write(RTC_REG_B, cmos_read(RTC_REG_B) & ~REG_B_SET);
+
+    g_assert_cmpint(cmos_read(RTC_HOURS), ==, 0x02);
+    g_assert_cmpint(cmos_read(RTC_MINUTES), ==, 0x04);
+
+    /* Since SET flag is disabled, this is an inequality check.
+     * We (reasonably) assume that no (sexagesimal) overflow occurs. */
+    g_assert_cmpint(cmos_read(RTC_SECONDS), >=, 0x58);
+    g_assert_cmpint(cmos_read(RTC_DAY_OF_MONTH), ==, 0x02);
+    g_assert_cmpint(cmos_read(RTC_MONTH), ==, 0x02);
+    g_assert_cmpint(cmos_read(RTC_YEAR), ==, 0x11);
+    g_assert_cmpint(cmos_read(RTC_CENTURY), ==, 0x20);
+}
+
 int main(int argc, char **argv)
 {
     QTestState *s = NULL;
@@ -342,6 +381,7 @@ int main(int argc, char **argv)
     qtest_add_func("/rtc/alarm-time", alarm_time);
     qtest_add_func("/rtc/set-year/20xx", set_year_20xx);
     qtest_add_func("/rtc/set-year/1980", set_year_1980);
+    qtest_add_func("/rtc/register_b_set_flag", register_b_set_flag);
     qtest_add_func("/rtc/fuzz-registers", fuzz_registers);
     ret = g_test_run();
 
diff --git a/tests/test-aio.c b/tests/test-aio.c
new file mode 100644
index 0000000000..f53c908707
--- /dev/null
+++ b/tests/test-aio.c
@@ -0,0 +1,667 @@
+/*
+ * AioContext tests
+ *
+ * Copyright Red Hat, Inc. 2012
+ *
+ * Authors:
+ *  Paolo Bonzini    <pbonzini@redhat.com>
+ *
+ * This work is licensed under the terms of the GNU LGPL, version 2 or later.
+ * See the COPYING.LIB file in the top-level directory.
+ */
+
+#include <glib.h>
+#include "qemu-aio.h"
+
+AioContext *ctx;
+
+/* Simple callbacks for testing.  */
+
+typedef struct {
+    QEMUBH *bh;
+    int n;
+    int max;
+} BHTestData;
+
+static void bh_test_cb(void *opaque)
+{
+    BHTestData *data = opaque;
+    if (++data->n < data->max) {
+        qemu_bh_schedule(data->bh);
+    }
+}
+
+static void bh_delete_cb(void *opaque)
+{
+    BHTestData *data = opaque;
+    if (++data->n < data->max) {
+        qemu_bh_schedule(data->bh);
+    } else {
+        qemu_bh_delete(data->bh);
+        data->bh = NULL;
+    }
+}
+
+typedef struct {
+    EventNotifier e;
+    int n;
+    int active;
+    bool auto_set;
+} EventNotifierTestData;
+
+static int event_active_cb(EventNotifier *e)
+{
+    EventNotifierTestData *data = container_of(e, EventNotifierTestData, e);
+    return data->active > 0;
+}
+
+static void event_ready_cb(EventNotifier *e)
+{
+    EventNotifierTestData *data = container_of(e, EventNotifierTestData, e);
+    g_assert(event_notifier_test_and_clear(e));
+    data->n++;
+    if (data->active > 0) {
+        data->active--;
+    }
+    if (data->auto_set && data->active) {
+        event_notifier_set(e);
+    }
+}
+
+/* Tests using aio_*.  */
+
+static void test_notify(void)
+{
+    g_assert(!aio_poll(ctx, false));
+    aio_notify(ctx);
+    g_assert(!aio_poll(ctx, true));
+    g_assert(!aio_poll(ctx, false));
+}
+
+static void test_flush(void)
+{
+    g_assert(!aio_poll(ctx, false));
+    aio_notify(ctx);
+    aio_flush(ctx);
+    g_assert(!aio_poll(ctx, false));
+}
+
+static void test_bh_schedule(void)
+{
+    BHTestData data = { .n = 0 };
+    data.bh = aio_bh_new(ctx, bh_test_cb, &data);
+
+    qemu_bh_schedule(data.bh);
+    g_assert_cmpint(data.n, ==, 0);
+
+    g_assert(aio_poll(ctx, true));
+    g_assert_cmpint(data.n, ==, 1);
+
+    g_assert(!aio_poll(ctx, false));
+    g_assert_cmpint(data.n, ==, 1);
+    qemu_bh_delete(data.bh);
+}
+
+static void test_bh_schedule10(void)
+{
+    BHTestData data = { .n = 0, .max = 10 };
+    data.bh = aio_bh_new(ctx, bh_test_cb, &data);
+
+    qemu_bh_schedule(data.bh);
+    g_assert_cmpint(data.n, ==, 0);
+
+    g_assert(aio_poll(ctx, false));
+    g_assert_cmpint(data.n, ==, 1);
+
+    g_assert(aio_poll(ctx, true));
+    g_assert_cmpint(data.n, ==, 2);
+
+    aio_flush(ctx);
+    g_assert_cmpint(data.n, ==, 10);
+
+    g_assert(!aio_poll(ctx, false));
+    g_assert_cmpint(data.n, ==, 10);
+    qemu_bh_delete(data.bh);
+}
+
+static void test_bh_cancel(void)
+{
+    BHTestData data = { .n = 0 };
+    data.bh = aio_bh_new(ctx, bh_test_cb, &data);
+
+    qemu_bh_schedule(data.bh);
+    g_assert_cmpint(data.n, ==, 0);
+
+    qemu_bh_cancel(data.bh);
+    g_assert_cmpint(data.n, ==, 0);
+
+    g_assert(!aio_poll(ctx, false));
+    g_assert_cmpint(data.n, ==, 0);
+    qemu_bh_delete(data.bh);
+}
+
+static void test_bh_delete(void)
+{
+    BHTestData data = { .n = 0 };
+    data.bh = aio_bh_new(ctx, bh_test_cb, &data);
+
+    qemu_bh_schedule(data.bh);
+    g_assert_cmpint(data.n, ==, 0);
+
+    qemu_bh_delete(data.bh);
+    g_assert_cmpint(data.n, ==, 0);
+
+    g_assert(!aio_poll(ctx, false));
+    g_assert_cmpint(data.n, ==, 0);
+}
+
+static void test_bh_delete_from_cb(void)
+{
+    BHTestData data1 = { .n = 0, .max = 1 };
+
+    data1.bh = aio_bh_new(ctx, bh_delete_cb, &data1);
+
+    qemu_bh_schedule(data1.bh);
+    g_assert_cmpint(data1.n, ==, 0);
+
+    aio_flush(ctx);
+    g_assert_cmpint(data1.n, ==, data1.max);
+    g_assert(data1.bh == NULL);
+
+    g_assert(!aio_poll(ctx, false));
+    g_assert(!aio_poll(ctx, true));
+}
+
+static void test_bh_delete_from_cb_many(void)
+{
+    BHTestData data1 = { .n = 0, .max = 1 };
+    BHTestData data2 = { .n = 0, .max = 3 };
+    BHTestData data3 = { .n = 0, .max = 2 };
+    BHTestData data4 = { .n = 0, .max = 4 };
+
+    data1.bh = aio_bh_new(ctx, bh_delete_cb, &data1);
+    data2.bh = aio_bh_new(ctx, bh_delete_cb, &data2);
+    data3.bh = aio_bh_new(ctx, bh_delete_cb, &data3);
+    data4.bh = aio_bh_new(ctx, bh_delete_cb, &data4);
+
+    qemu_bh_schedule(data1.bh);
+    qemu_bh_schedule(data2.bh);
+    qemu_bh_schedule(data3.bh);
+    qemu_bh_schedule(data4.bh);
+    g_assert_cmpint(data1.n, ==, 0);
+    g_assert_cmpint(data2.n, ==, 0);
+    g_assert_cmpint(data3.n, ==, 0);
+    g_assert_cmpint(data4.n, ==, 0);
+
+    g_assert(aio_poll(ctx, false));
+    g_assert_cmpint(data1.n, ==, 1);
+    g_assert_cmpint(data2.n, ==, 1);
+    g_assert_cmpint(data3.n, ==, 1);
+    g_assert_cmpint(data4.n, ==, 1);
+    g_assert(data1.bh == NULL);
+
+    aio_flush(ctx);
+    g_assert_cmpint(data1.n, ==, data1.max);
+    g_assert_cmpint(data2.n, ==, data2.max);
+    g_assert_cmpint(data3.n, ==, data3.max);
+    g_assert_cmpint(data4.n, ==, data4.max);
+    g_assert(data1.bh == NULL);
+    g_assert(data2.bh == NULL);
+    g_assert(data3.bh == NULL);
+    g_assert(data4.bh == NULL);
+}
+
+static void test_bh_flush(void)
+{
+    BHTestData data = { .n = 0 };
+    data.bh = aio_bh_new(ctx, bh_test_cb, &data);
+
+    qemu_bh_schedule(data.bh);
+    g_assert_cmpint(data.n, ==, 0);
+
+    aio_flush(ctx);
+    g_assert_cmpint(data.n, ==, 1);
+
+    g_assert(!aio_poll(ctx, false));
+    g_assert_cmpint(data.n, ==, 1);
+    qemu_bh_delete(data.bh);
+}
+
+static void test_set_event_notifier(void)
+{
+    EventNotifierTestData data = { .n = 0, .active = 0 };
+    event_notifier_init(&data.e, false);
+    aio_set_event_notifier(ctx, &data.e, event_ready_cb, event_active_cb);
+    g_assert(!aio_poll(ctx, false));
+    g_assert_cmpint(data.n, ==, 0);
+
+    aio_set_event_notifier(ctx, &data.e, NULL, NULL);
+    g_assert(!aio_poll(ctx, false));
+    g_assert_cmpint(data.n, ==, 0);
+    event_notifier_cleanup(&data.e);
+}
+
+static void test_wait_event_notifier(void)
+{
+    EventNotifierTestData data = { .n = 0, .active = 1 };
+    event_notifier_init(&data.e, false);
+    aio_set_event_notifier(ctx, &data.e, event_ready_cb, event_active_cb);
+    g_assert(aio_poll(ctx, false));
+    g_assert_cmpint(data.n, ==, 0);
+    g_assert_cmpint(data.active, ==, 1);
+
+    event_notifier_set(&data.e);
+    g_assert(aio_poll(ctx, false));
+    g_assert_cmpint(data.n, ==, 1);
+    g_assert_cmpint(data.active, ==, 0);
+
+    g_assert(!aio_poll(ctx, false));
+    g_assert_cmpint(data.n, ==, 1);
+    g_assert_cmpint(data.active, ==, 0);
+
+    aio_set_event_notifier(ctx, &data.e, NULL, NULL);
+    g_assert(!aio_poll(ctx, false));
+    g_assert_cmpint(data.n, ==, 1);
+
+    event_notifier_cleanup(&data.e);
+}
+
+static void test_flush_event_notifier(void)
+{
+    EventNotifierTestData data = { .n = 0, .active = 10, .auto_set = true };
+    event_notifier_init(&data.e, false);
+    aio_set_event_notifier(ctx, &data.e, event_ready_cb, event_active_cb);
+    g_assert(aio_poll(ctx, false));
+    g_assert_cmpint(data.n, ==, 0);
+    g_assert_cmpint(data.active, ==, 10);
+
+    event_notifier_set(&data.e);
+    g_assert(aio_poll(ctx, false));
+    g_assert_cmpint(data.n, ==, 1);
+    g_assert_cmpint(data.active, ==, 9);
+    g_assert(aio_poll(ctx, false));
+
+    aio_flush(ctx);
+    g_assert_cmpint(data.n, ==, 10);
+    g_assert_cmpint(data.active, ==, 0);
+    g_assert(!aio_poll(ctx, false));
+
+    aio_set_event_notifier(ctx, &data.e, NULL, NULL);
+    g_assert(!aio_poll(ctx, false));
+    event_notifier_cleanup(&data.e);
+}
+
+static void test_wait_event_notifier_noflush(void)
+{
+    EventNotifierTestData data = { .n = 0 };
+    EventNotifierTestData dummy = { .n = 0, .active = 1 };
+
+    event_notifier_init(&data.e, false);
+    aio_set_event_notifier(ctx, &data.e, event_ready_cb, NULL);
+
+    g_assert(!aio_poll(ctx, false));
+    g_assert_cmpint(data.n, ==, 0);
+
+    /* Until there is an active descriptor, aio_poll may or may not call
+     * event_ready_cb.  Still, it must not block.  */
+    event_notifier_set(&data.e);
+    g_assert(!aio_poll(ctx, true));
+    data.n = 0;
+
+    /* An active event notifier forces aio_poll to look at EventNotifiers.  */
+    event_notifier_init(&dummy.e, false);
+    aio_set_event_notifier(ctx, &dummy.e, event_ready_cb, event_active_cb);
+
+    event_notifier_set(&data.e);
+    g_assert(aio_poll(ctx, false));
+    g_assert_cmpint(data.n, ==, 1);
+    g_assert(!aio_poll(ctx, false));
+    g_assert_cmpint(data.n, ==, 1);
+
+    event_notifier_set(&data.e);
+    g_assert(aio_poll(ctx, false));
+    g_assert_cmpint(data.n, ==, 2);
+    g_assert(!aio_poll(ctx, false));
+    g_assert_cmpint(data.n, ==, 2);
+
+    event_notifier_set(&dummy.e);
+    aio_flush(ctx);
+    g_assert_cmpint(data.n, ==, 2);
+    g_assert_cmpint(dummy.n, ==, 1);
+    g_assert_cmpint(dummy.active, ==, 0);
+
+    aio_set_event_notifier(ctx, &dummy.e, NULL, NULL);
+    event_notifier_cleanup(&dummy.e);
+
+    aio_set_event_notifier(ctx, &data.e, NULL, NULL);
+    g_assert(!aio_poll(ctx, false));
+    g_assert_cmpint(data.n, ==, 2);
+
+    event_notifier_cleanup(&data.e);
+}
+
+/* Now the same tests, using the context as a GSource.  They are
+ * very similar to the ones above, with g_main_context_iteration
+ * replacing aio_poll.  However:
+ * - sometimes both the AioContext and the glib main loop wake
+ *   themselves up.  Hence, some "g_assert(!aio_poll(ctx, false));"
+ *   are replaced by "while (g_main_context_iteration(NULL, false));".
+ * - there is no exact replacement for aio_flush's blocking wait.
+ *   "while (g_main_context_iteration(NULL, true)" seems to work,
+ *   but it is not documented _why_ it works.  For these tests a
+ *   non-blocking loop like "while (g_main_context_iteration(NULL, false)"
+ *   works well, and that's what I am using.
+ */
+
+static void test_source_notify(void)
+{
+    while (g_main_context_iteration(NULL, false));
+    aio_notify(ctx);
+    g_assert(g_main_context_iteration(NULL, true));
+    g_assert(!g_main_context_iteration(NULL, false));
+}
+
+static void test_source_flush(void)
+{
+    g_assert(!g_main_context_iteration(NULL, false));
+    aio_notify(ctx);
+    while (g_main_context_iteration(NULL, false));
+    g_assert(!g_main_context_iteration(NULL, false));
+}
+
+static void test_source_bh_schedule(void)
+{
+    BHTestData data = { .n = 0 };
+    data.bh = aio_bh_new(ctx, bh_test_cb, &data);
+
+    qemu_bh_schedule(data.bh);
+    g_assert_cmpint(data.n, ==, 0);
+
+    g_assert(g_main_context_iteration(NULL, true));
+    g_assert_cmpint(data.n, ==, 1);
+
+    g_assert(!g_main_context_iteration(NULL, false));
+    g_assert_cmpint(data.n, ==, 1);
+    qemu_bh_delete(data.bh);
+}
+
+static void test_source_bh_schedule10(void)
+{
+    BHTestData data = { .n = 0, .max = 10 };
+    data.bh = aio_bh_new(ctx, bh_test_cb, &data);
+
+    qemu_bh_schedule(data.bh);
+    g_assert_cmpint(data.n, ==, 0);
+
+    g_assert(g_main_context_iteration(NULL, false));
+    g_assert_cmpint(data.n, ==, 1);
+
+    g_assert(g_main_context_iteration(NULL, true));
+    g_assert_cmpint(data.n, ==, 2);
+
+    while (g_main_context_iteration(NULL, false));
+    g_assert_cmpint(data.n, ==, 10);
+
+    g_assert(!g_main_context_iteration(NULL, false));
+    g_assert_cmpint(data.n, ==, 10);
+    qemu_bh_delete(data.bh);
+}
+
+static void test_source_bh_cancel(void)
+{
+    BHTestData data = { .n = 0 };
+    data.bh = aio_bh_new(ctx, bh_test_cb, &data);
+
+    qemu_bh_schedule(data.bh);
+    g_assert_cmpint(data.n, ==, 0);
+
+    qemu_bh_cancel(data.bh);
+    g_assert_cmpint(data.n, ==, 0);
+
+    while (g_main_context_iteration(NULL, false));
+    g_assert_cmpint(data.n, ==, 0);
+    qemu_bh_delete(data.bh);
+}
+
+static void test_source_bh_delete(void)
+{
+    BHTestData data = { .n = 0 };
+    data.bh = aio_bh_new(ctx, bh_test_cb, &data);
+
+    qemu_bh_schedule(data.bh);
+    g_assert_cmpint(data.n, ==, 0);
+
+    qemu_bh_delete(data.bh);
+    g_assert_cmpint(data.n, ==, 0);
+
+    while (g_main_context_iteration(NULL, false));
+    g_assert_cmpint(data.n, ==, 0);
+}
+
+static void test_source_bh_delete_from_cb(void)
+{
+    BHTestData data1 = { .n = 0, .max = 1 };
+
+    data1.bh = aio_bh_new(ctx, bh_delete_cb, &data1);
+
+    qemu_bh_schedule(data1.bh);
+    g_assert_cmpint(data1.n, ==, 0);
+
+    g_main_context_iteration(NULL, true);
+    g_assert_cmpint(data1.n, ==, data1.max);
+    g_assert(data1.bh == NULL);
+
+    g_assert(!g_main_context_iteration(NULL, false));
+}
+
+static void test_source_bh_delete_from_cb_many(void)
+{
+    BHTestData data1 = { .n = 0, .max = 1 };
+    BHTestData data2 = { .n = 0, .max = 3 };
+    BHTestData data3 = { .n = 0, .max = 2 };
+    BHTestData data4 = { .n = 0, .max = 4 };
+
+    data1.bh = aio_bh_new(ctx, bh_delete_cb, &data1);
+    data2.bh = aio_bh_new(ctx, bh_delete_cb, &data2);
+    data3.bh = aio_bh_new(ctx, bh_delete_cb, &data3);
+    data4.bh = aio_bh_new(ctx, bh_delete_cb, &data4);
+
+    qemu_bh_schedule(data1.bh);
+    qemu_bh_schedule(data2.bh);
+    qemu_bh_schedule(data3.bh);
+    qemu_bh_schedule(data4.bh);
+    g_assert_cmpint(data1.n, ==, 0);
+    g_assert_cmpint(data2.n, ==, 0);
+    g_assert_cmpint(data3.n, ==, 0);
+    g_assert_cmpint(data4.n, ==, 0);
+
+    g_assert(g_main_context_iteration(NULL, false));
+    g_assert_cmpint(data1.n, ==, 1);
+    g_assert_cmpint(data2.n, ==, 1);
+    g_assert_cmpint(data3.n, ==, 1);
+    g_assert_cmpint(data4.n, ==, 1);
+    g_assert(data1.bh == NULL);
+
+    while (g_main_context_iteration(NULL, false));
+    g_assert_cmpint(data1.n, ==, data1.max);
+    g_assert_cmpint(data2.n, ==, data2.max);
+    g_assert_cmpint(data3.n, ==, data3.max);
+    g_assert_cmpint(data4.n, ==, data4.max);
+    g_assert(data1.bh == NULL);
+    g_assert(data2.bh == NULL);
+    g_assert(data3.bh == NULL);
+    g_assert(data4.bh == NULL);
+}
+
+static void test_source_bh_flush(void)
+{
+    BHTestData data = { .n = 0 };
+    data.bh = aio_bh_new(ctx, bh_test_cb, &data);
+
+    qemu_bh_schedule(data.bh);
+    g_assert_cmpint(data.n, ==, 0);
+
+    g_assert(g_main_context_iteration(NULL, true));
+    g_assert_cmpint(data.n, ==, 1);
+
+    g_assert(!g_main_context_iteration(NULL, false));
+    g_assert_cmpint(data.n, ==, 1);
+    qemu_bh_delete(data.bh);
+}
+
+static void test_source_set_event_notifier(void)
+{
+    EventNotifierTestData data = { .n = 0, .active = 0 };
+    event_notifier_init(&data.e, false);
+    aio_set_event_notifier(ctx, &data.e, event_ready_cb, event_active_cb);
+    while (g_main_context_iteration(NULL, false));
+    g_assert_cmpint(data.n, ==, 0);
+
+    aio_set_event_notifier(ctx, &data.e, NULL, NULL);
+    while (g_main_context_iteration(NULL, false));
+    g_assert_cmpint(data.n, ==, 0);
+    event_notifier_cleanup(&data.e);
+}
+
+static void test_source_wait_event_notifier(void)
+{
+    EventNotifierTestData data = { .n = 0, .active = 1 };
+    event_notifier_init(&data.e, false);
+    aio_set_event_notifier(ctx, &data.e, event_ready_cb, event_active_cb);
+    g_assert(g_main_context_iteration(NULL, false));
+    g_assert_cmpint(data.n, ==, 0);
+    g_assert_cmpint(data.active, ==, 1);
+
+    event_notifier_set(&data.e);
+    g_assert(g_main_context_iteration(NULL, false));
+    g_assert_cmpint(data.n, ==, 1);
+    g_assert_cmpint(data.active, ==, 0);
+
+    while (g_main_context_iteration(NULL, false));
+    g_assert_cmpint(data.n, ==, 1);
+    g_assert_cmpint(data.active, ==, 0);
+
+    aio_set_event_notifier(ctx, &data.e, NULL, NULL);
+    while (g_main_context_iteration(NULL, false));
+    g_assert_cmpint(data.n, ==, 1);
+
+    event_notifier_cleanup(&data.e);
+}
+
+static void test_source_flush_event_notifier(void)
+{
+    EventNotifierTestData data = { .n = 0, .active = 10, .auto_set = true };
+    event_notifier_init(&data.e, false);
+    aio_set_event_notifier(ctx, &data.e, event_ready_cb, event_active_cb);
+    g_assert(g_main_context_iteration(NULL, false));
+    g_assert_cmpint(data.n, ==, 0);
+    g_assert_cmpint(data.active, ==, 10);
+
+    event_notifier_set(&data.e);
+    g_assert(g_main_context_iteration(NULL, false));
+    g_assert_cmpint(data.n, ==, 1);
+    g_assert_cmpint(data.active, ==, 9);
+    g_assert(g_main_context_iteration(NULL, false));
+
+    while (g_main_context_iteration(NULL, false));
+    g_assert_cmpint(data.n, ==, 10);
+    g_assert_cmpint(data.active, ==, 0);
+    g_assert(!g_main_context_iteration(NULL, false));
+
+    aio_set_event_notifier(ctx, &data.e, NULL, NULL);
+    while (g_main_context_iteration(NULL, false));
+    event_notifier_cleanup(&data.e);
+}
+
+static void test_source_wait_event_notifier_noflush(void)
+{
+    EventNotifierTestData data = { .n = 0 };
+    EventNotifierTestData dummy = { .n = 0, .active = 1 };
+
+    event_notifier_init(&data.e, false);
+    aio_set_event_notifier(ctx, &data.e, event_ready_cb, NULL);
+
+    while (g_main_context_iteration(NULL, false));
+    g_assert_cmpint(data.n, ==, 0);
+
+    /* Until there is an active descriptor, glib may or may not call
+     * event_ready_cb.  Still, it must not block.  */
+    event_notifier_set(&data.e);
+    g_main_context_iteration(NULL, true);
+    data.n = 0;
+
+    /* An active event notifier forces aio_poll to look at EventNotifiers.  */
+    event_notifier_init(&dummy.e, false);
+    aio_set_event_notifier(ctx, &dummy.e, event_ready_cb, event_active_cb);
+
+    event_notifier_set(&data.e);
+    g_assert(g_main_context_iteration(NULL, false));
+    g_assert_cmpint(data.n, ==, 1);
+    g_assert(!g_main_context_iteration(NULL, false));
+    g_assert_cmpint(data.n, ==, 1);
+
+    event_notifier_set(&data.e);
+    g_assert(g_main_context_iteration(NULL, false));
+    g_assert_cmpint(data.n, ==, 2);
+    g_assert(!g_main_context_iteration(NULL, false));
+    g_assert_cmpint(data.n, ==, 2);
+
+    event_notifier_set(&dummy.e);
+    while (g_main_context_iteration(NULL, false));
+    g_assert_cmpint(data.n, ==, 2);
+    g_assert_cmpint(dummy.n, ==, 1);
+    g_assert_cmpint(dummy.active, ==, 0);
+
+    aio_set_event_notifier(ctx, &dummy.e, NULL, NULL);
+    event_notifier_cleanup(&dummy.e);
+
+    aio_set_event_notifier(ctx, &data.e, NULL, NULL);
+    while (g_main_context_iteration(NULL, false));
+    g_assert_cmpint(data.n, ==, 2);
+
+    event_notifier_cleanup(&data.e);
+}
+
+/* End of tests.  */
+
+int main(int argc, char **argv)
+{
+    GSource *src;
+
+    ctx = aio_context_new();
+    src = aio_get_g_source(ctx);
+    g_source_attach(src, NULL);
+    g_source_unref(src);
+
+    while (g_main_context_iteration(NULL, false));
+
+    g_test_init(&argc, &argv, NULL);
+    g_test_add_func("/aio/notify",                  test_notify);
+    g_test_add_func("/aio/flush",                   test_flush);
+    g_test_add_func("/aio/bh/schedule",             test_bh_schedule);
+    g_test_add_func("/aio/bh/schedule10",           test_bh_schedule10);
+    g_test_add_func("/aio/bh/cancel",               test_bh_cancel);
+    g_test_add_func("/aio/bh/delete",               test_bh_delete);
+    g_test_add_func("/aio/bh/callback-delete/one",  test_bh_delete_from_cb);
+    g_test_add_func("/aio/bh/callback-delete/many", test_bh_delete_from_cb_many);
+    g_test_add_func("/aio/bh/flush",                test_bh_flush);
+    g_test_add_func("/aio/event/add-remove",        test_set_event_notifier);
+    g_test_add_func("/aio/event/wait",              test_wait_event_notifier);
+    g_test_add_func("/aio/event/wait/no-flush-cb",  test_wait_event_notifier_noflush);
+    g_test_add_func("/aio/event/flush",             test_flush_event_notifier);
+
+    g_test_add_func("/aio-gsource/notify",                  test_source_notify);
+    g_test_add_func("/aio-gsource/flush",                   test_source_flush);
+    g_test_add_func("/aio-gsource/bh/schedule",             test_source_bh_schedule);
+    g_test_add_func("/aio-gsource/bh/schedule10",           test_source_bh_schedule10);
+    g_test_add_func("/aio-gsource/bh/cancel",               test_source_bh_cancel);
+    g_test_add_func("/aio-gsource/bh/delete",               test_source_bh_delete);
+    g_test_add_func("/aio-gsource/bh/callback-delete/one",  test_source_bh_delete_from_cb);
+    g_test_add_func("/aio-gsource/bh/callback-delete/many", test_source_bh_delete_from_cb_many);
+    g_test_add_func("/aio-gsource/bh/flush",                test_source_bh_flush);
+    g_test_add_func("/aio-gsource/event/add-remove",        test_source_set_event_notifier);
+    g_test_add_func("/aio-gsource/event/wait",              test_source_wait_event_notifier);
+    g_test_add_func("/aio-gsource/event/wait/no-flush-cb",  test_source_wait_event_notifier_noflush);
+    g_test_add_func("/aio-gsource/event/flush",             test_source_flush_event_notifier);
+    return g_test_run();
+}
diff --git a/tests/test-thread-pool.c b/tests/test-thread-pool.c
new file mode 100644
index 0000000000..fea0445fb4
--- /dev/null
+++ b/tests/test-thread-pool.c
@@ -0,0 +1,216 @@
+#include <glib.h>
+#include "qemu-common.h"
+#include "qemu-aio.h"
+#include "thread-pool.h"
+#include "block.h"
+
+static int active;
+
+typedef struct {
+    BlockDriverAIOCB *aiocb;
+    int n;
+    int ret;
+} WorkerTestData;
+
+static int worker_cb(void *opaque)
+{
+    WorkerTestData *data = opaque;
+    return __sync_fetch_and_add(&data->n, 1);
+}
+
+static int long_cb(void *opaque)
+{
+    WorkerTestData *data = opaque;
+    __sync_fetch_and_add(&data->n, 1);
+    g_usleep(2000000);
+    __sync_fetch_and_add(&data->n, 1);
+    return 0;
+}
+
+static void done_cb(void *opaque, int ret)
+{
+    WorkerTestData *data = opaque;
+    g_assert_cmpint(data->ret, ==, -EINPROGRESS);
+    data->ret = ret;
+    data->aiocb = NULL;
+
+    /* Callbacks are serialized, so no need to use atomic ops.  */
+    active--;
+}
+
+/* A non-blocking poll of the main AIO context (we cannot use aio_poll
+ * because we do not know the AioContext).
+ */
+static void qemu_aio_wait_nonblocking(void)
+{
+    qemu_notify_event();
+    qemu_aio_wait();
+}
+
+static void test_submit(void)
+{
+    WorkerTestData data = { .n = 0 };
+    thread_pool_submit(worker_cb, &data);
+    qemu_aio_flush();
+    g_assert_cmpint(data.n, ==, 1);
+}
+
+static void test_submit_aio(void)
+{
+    WorkerTestData data = { .n = 0, .ret = -EINPROGRESS };
+    data.aiocb = thread_pool_submit_aio(worker_cb, &data, done_cb, &data);
+
+    /* The callbacks are not called until after the first wait.  */
+    active = 1;
+    g_assert_cmpint(data.ret, ==, -EINPROGRESS);
+    qemu_aio_flush();
+    g_assert_cmpint(active, ==, 0);
+    g_assert_cmpint(data.n, ==, 1);
+    g_assert_cmpint(data.ret, ==, 0);
+}
+
+static void co_test_cb(void *opaque)
+{
+    WorkerTestData *data = opaque;
+
+    active = 1;
+    data->n = 0;
+    data->ret = -EINPROGRESS;
+    thread_pool_submit_co(worker_cb, data);
+
+    /* The test continues in test_submit_co, after qemu_coroutine_enter... */
+
+    g_assert_cmpint(data->n, ==, 1);
+    data->ret = 0;
+    active--;
+
+    /* The test continues in test_submit_co, after qemu_aio_flush... */
+}
+
+static void test_submit_co(void)
+{
+    WorkerTestData data;
+    Coroutine *co = qemu_coroutine_create(co_test_cb);
+
+    qemu_coroutine_enter(co, &data);
+
+    /* Back here once the worker has started.  */
+
+    g_assert_cmpint(active, ==, 1);
+    g_assert_cmpint(data.ret, ==, -EINPROGRESS);
+
+    /* qemu_aio_flush will execute the rest of the coroutine.  */
+
+    qemu_aio_flush();
+
+    /* Back here after the coroutine has finished.  */
+
+    g_assert_cmpint(active, ==, 0);
+    g_assert_cmpint(data.ret, ==, 0);
+}
+
+static void test_submit_many(void)
+{
+    WorkerTestData data[100];
+    int i;
+
+    /* Start more work items than there will be threads.  */
+    for (i = 0; i < 100; i++) {
+        data[i].n = 0;
+        data[i].ret = -EINPROGRESS;
+        thread_pool_submit_aio(worker_cb, &data[i], done_cb, &data[i]);
+    }
+
+    active = 100;
+    while (active > 0) {
+        qemu_aio_wait();
+    }
+    for (i = 0; i < 100; i++) {
+        g_assert_cmpint(data[i].n, ==, 1);
+        g_assert_cmpint(data[i].ret, ==, 0);
+    }
+}
+
+static void test_cancel(void)
+{
+    WorkerTestData data[100];
+    int num_canceled;
+    int i;
+
+    /* Start more work items than there will be threads, to ensure
+     * the pool is full.
+     */
+    test_submit_many();
+
+    /* Start long running jobs, to ensure we can cancel some.  */
+    for (i = 0; i < 100; i++) {
+        data[i].n = 0;
+        data[i].ret = -EINPROGRESS;
+        data[i].aiocb = thread_pool_submit_aio(long_cb, &data[i],
+                                               done_cb, &data[i]);
+    }
+
+    /* Starting the threads may be left to a bottom half.  Let it
+     * run, but do not waste too much time...
+     */
+    active = 100;
+    qemu_aio_wait_nonblocking();
+
+    /* Wait some time for the threads to start, with some sanity
+     * testing on the behavior of the scheduler...
+     */
+    g_assert_cmpint(active, ==, 100);
+    g_usleep(1000000);
+    g_assert_cmpint(active, >, 50);
+
+    /* Cancel the jobs that haven't been started yet.  */
+    num_canceled = 0;
+    for (i = 0; i < 100; i++) {
+        if (__sync_val_compare_and_swap(&data[i].n, 0, 3) == 0) {
+            data[i].ret = -ECANCELED;
+            bdrv_aio_cancel(data[i].aiocb);
+            active--;
+            num_canceled++;
+        }
+    }
+    g_assert_cmpint(active, >, 0);
+    g_assert_cmpint(num_canceled, <, 100);
+
+    /* Canceling the others will be a blocking operation.  */
+    for (i = 0; i < 100; i++) {
+        if (data[i].n != 3) {
+            bdrv_aio_cancel(data[i].aiocb);
+        }
+    }
+
+    /* Finish execution and execute any remaining callbacks.  */
+    qemu_aio_flush();
+    g_assert_cmpint(active, ==, 0);
+    for (i = 0; i < 100; i++) {
+        if (data[i].n == 3) {
+            g_assert_cmpint(data[i].ret, ==, -ECANCELED);
+            g_assert(data[i].aiocb != NULL);
+        } else {
+            g_assert_cmpint(data[i].n, ==, 2);
+            g_assert_cmpint(data[i].ret, ==, 0);
+            g_assert(data[i].aiocb == NULL);
+        }
+    }
+}
+
+int main(int argc, char **argv)
+{
+    /* These should be removed once each AioContext has its thread pool.
+     * The test should create its own AioContext.
+     */
+    qemu_init_main_loop();
+    bdrv_init();
+
+    g_test_init(&argc, &argv, NULL);
+    g_test_add_func("/thread-pool/submit", test_submit);
+    g_test_add_func("/thread-pool/submit-aio", test_submit_aio);
+    g_test_add_func("/thread-pool/submit-co", test_submit_co);
+    g_test_add_func("/thread-pool/submit-many", test_submit_many);
+    g_test_add_func("/thread-pool/cancel", test_cancel);
+    return g_test_run();
+}