summaryrefslogtreecommitdiffstats
path: root/tests
diff options
context:
space:
mode:
authorPeter Maydell2015-12-18 13:42:10 +0100
committerPeter Maydell2015-12-18 13:42:10 +0100
commit67a708406221f476c0f8fa60c192c186150c5185 (patch)
tree3f9d266d76b202a93b1bf7fd8a47affa4d521308 /tests
parentMerge remote-tracking branch 'remotes/bonzini/tags/for-upstream' into staging (diff)
parentio: add QIOChannelBuffer class (diff)
downloadqemu-67a708406221f476c0f8fa60c192c186150c5185.tar.gz
qemu-67a708406221f476c0f8fa60c192c186150c5185.tar.xz
qemu-67a708406221f476c0f8fa60c192c186150c5185.zip
Merge remote-tracking branch 'remotes/berrange/tags/pull-io-channel-base-2015-12-18-1' into staging
Merge I/O channels base classes # gpg: Signature made Fri 18 Dec 2015 12:18:38 GMT using RSA key ID 15104FDF # gpg: Good signature from "Daniel P. Berrange <dan@berrange.com>" # gpg: aka "Daniel P. Berrange <berrange@redhat.com>" * remotes/berrange/tags/pull-io-channel-base-2015-12-18-1: io: add QIOChannelBuffer class io: add QIOChannelCommand class io: add QIOChannelWebsock class io: add QIOChannelTLS class io: add QIOChannelFile class io: add QIOChannelSocket class io: add QIOTask class for async operations io: add helper module for creating watches on FDs io: add abstract QIOChannel classes Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
Diffstat (limited to 'tests')
-rw-r--r--tests/.gitignore8
-rw-r--r--tests/Makefile19
-rw-r--r--tests/io-channel-helpers.c246
-rw-r--r--tests/io-channel-helpers.h42
-rw-r--r--tests/test-io-channel-buffer.c50
-rw-r--r--tests/test-io-channel-command.c129
-rw-r--r--tests/test-io-channel-file.c100
-rw-r--r--tests/test-io-channel-socket.c399
-rw-r--r--tests/test-io-channel-tls.c342
-rw-r--r--tests/test-io-task.c268
10 files changed, 1603 insertions, 0 deletions
diff --git a/tests/.gitignore b/tests/.gitignore
index 1e55722b6a..77aaba6c2f 100644
--- a/tests/.gitignore
+++ b/tests/.gitignore
@@ -24,6 +24,14 @@ test-cutils
test-hbitmap
test-int128
test-iov
+test-io-channel-buffer
+test-io-channel-command
+test-io-channel-command.fifo
+test-io-channel-file
+test-io-channel-file.txt
+test-io-channel-socket
+test-io-channel-tls
+test-io-task
test-mul64
test-opts-visitor
test-qapi-event.[ch]
diff --git a/tests/Makefile b/tests/Makefile
index 053c1ae481..6ff4627d0c 100644
--- a/tests/Makefile
+++ b/tests/Makefile
@@ -84,6 +84,12 @@ check-unit-$(CONFIG_GNUTLS) += tests/test-crypto-tlscredsx509$(EXESUF)
check-unit-$(CONFIG_GNUTLS) += tests/test-crypto-tlssession$(EXESUF)
check-unit-$(CONFIG_LINUX) += tests/test-qga$(EXESUF)
check-unit-y += tests/test-timed-average$(EXESUF)
+check-unit-y += tests/test-io-task$(EXESUF)
+check-unit-y += tests/test-io-channel-socket$(EXESUF)
+check-unit-y += tests/test-io-channel-file$(EXESUF)
+check-unit-$(CONFIG_GNUTLS) += tests/test-io-channel-tls$(EXESUF)
+check-unit-y += tests/test-io-channel-command$(EXESUF)
+check-unit-y += tests/test-io-channel-buffer$(EXESUF)
check-block-$(CONFIG_POSIX) += tests/qemu-iotests-quick.sh
@@ -381,6 +387,7 @@ test-qapi-obj-y = tests/test-qapi-visit.o tests/test-qapi-types.o \
$(test-qom-obj-y)
test-crypto-obj-y = $(crypto-obj-y) $(test-qom-obj-y)
test-block-obj-y = $(block-obj-y) $(test-crypto-obj-y)
+test-io-obj-y = $(io-obj-y) $(test-crypto-obj-y)
tests/check-qint$(EXESUF): tests/check-qint.o $(test-util-obj-y)
tests/check-qstring$(EXESUF): tests/check-qstring.o $(test-util-obj-y)
@@ -469,6 +476,18 @@ tests/test-crypto-tlscredsx509$(EXESUF): tests/test-crypto-tlscredsx509.o \
tests/test-crypto-tlssession.o-cflags := $(TASN1_CFLAGS)
tests/test-crypto-tlssession$(EXESUF): tests/test-crypto-tlssession.o \
tests/crypto-tls-x509-helpers.o tests/pkix_asn1_tab.o $(test-crypto-obj-y)
+tests/test-io-task$(EXESUF): tests/test-io-task.o $(test-io-obj-y)
+tests/test-io-channel-socket$(EXESUF): tests/test-io-channel-socket.o \
+ tests/io-channel-helpers.o $(test-io-obj-y)
+tests/test-io-channel-file$(EXESUF): tests/test-io-channel-file.o \
+ tests/io-channel-helpers.o $(test-io-obj-y)
+tests/test-io-channel-tls$(EXESUF): tests/test-io-channel-tls.o \
+ tests/crypto-tls-x509-helpers.o tests/pkix_asn1_tab.o \
+ tests/io-channel-helpers.o $(test-io-obj-y)
+tests/test-io-channel-command$(EXESUF): tests/test-io-channel-command.o \
+ tests/io-channel-helpers.o $(test-io-obj-y)
+tests/test-io-channel-buffer$(EXESUF): tests/test-io-channel-buffer.o \
+ tests/io-channel-helpers.o $(test-io-obj-y)
libqos-obj-y = tests/libqos/pci.o tests/libqos/fw_cfg.o tests/libqos/malloc.o
libqos-obj-y += tests/libqos/i2c.o tests/libqos/libqos.o
diff --git a/tests/io-channel-helpers.c b/tests/io-channel-helpers.c
new file mode 100644
index 0000000000..78d36dd703
--- /dev/null
+++ b/tests/io-channel-helpers.c
@@ -0,0 +1,246 @@
+/*
+ * QEMU I/O channel test helpers
+ *
+ * Copyright (c) 2015 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "io-channel-helpers.h"
+
+struct QIOChannelTest {
+ QIOChannel *src;
+ QIOChannel *dst;
+ bool blocking;
+ size_t len;
+ size_t niov;
+ char *input;
+ struct iovec *inputv;
+ char *output;
+ struct iovec *outputv;
+ Error *writeerr;
+ Error *readerr;
+};
+
+
+static void test_skip_iovec(struct iovec **iov,
+ size_t *niov,
+ size_t skip,
+ struct iovec *old)
+{
+ size_t offset = 0;
+ size_t i;
+
+ for (i = 0; i < *niov; i++) {
+ if (skip < (*iov)[i].iov_len) {
+ old->iov_len = (*iov)[i].iov_len;
+ old->iov_base = (*iov)[i].iov_base;
+
+ (*iov)[i].iov_len -= skip;
+ (*iov)[i].iov_base += skip;
+ break;
+ } else {
+ skip -= (*iov)[i].iov_len;
+
+ if (i == 0 && old->iov_base) {
+ (*iov)[i].iov_len = old->iov_len;
+ (*iov)[i].iov_base = old->iov_base;
+ old->iov_len = 0;
+ old->iov_base = NULL;
+ }
+
+ offset++;
+ }
+ }
+
+ *iov = *iov + offset;
+ *niov -= offset;
+}
+
+
+/* This thread sends all data using iovecs */
+static gpointer test_io_thread_writer(gpointer opaque)
+{
+ QIOChannelTest *data = opaque;
+ struct iovec *iov = data->inputv;
+ size_t niov = data->niov;
+ struct iovec old = { 0 };
+
+ qio_channel_set_blocking(data->src, data->blocking, NULL);
+
+ while (niov) {
+ ssize_t ret;
+ ret = qio_channel_writev(data->src,
+ iov,
+ niov,
+ &data->writeerr);
+ if (ret == QIO_CHANNEL_ERR_BLOCK) {
+ if (data->blocking) {
+ error_setg(&data->writeerr,
+ "Unexpected I/O blocking");
+ break;
+ } else {
+ qio_channel_wait(data->src,
+ G_IO_OUT);
+ continue;
+ }
+ } else if (ret < 0) {
+ break;
+ } else if (ret == 0) {
+ error_setg(&data->writeerr,
+ "Unexpected zero length write");
+ break;
+ }
+
+ test_skip_iovec(&iov, &niov, ret, &old);
+ }
+
+ return NULL;
+}
+
+
+/* This thread receives all data using iovecs */
+static gpointer test_io_thread_reader(gpointer opaque)
+{
+ QIOChannelTest *data = opaque;
+ struct iovec *iov = data->outputv;
+ size_t niov = data->niov;
+ struct iovec old = { 0 };
+
+ qio_channel_set_blocking(data->dst, data->blocking, NULL);
+
+ while (niov) {
+ ssize_t ret;
+
+ ret = qio_channel_readv(data->dst,
+ iov,
+ niov,
+ &data->readerr);
+
+ if (ret == QIO_CHANNEL_ERR_BLOCK) {
+ if (data->blocking) {
+ error_setg(&data->writeerr,
+ "Unexpected I/O blocking");
+ break;
+ } else {
+ qio_channel_wait(data->dst,
+ G_IO_IN);
+ continue;
+ }
+ } else if (ret < 0) {
+ break;
+ } else if (ret == 0) {
+ break;
+ }
+
+ test_skip_iovec(&iov, &niov, ret, &old);
+ }
+
+ return NULL;
+}
+
+
+QIOChannelTest *qio_channel_test_new(void)
+{
+ QIOChannelTest *data = g_new0(QIOChannelTest, 1);
+ size_t i;
+ size_t offset;
+
+
+ /* We'll send 1 MB of data */
+#define CHUNK_COUNT 250
+#define CHUNK_LEN 4194
+
+ data->len = CHUNK_COUNT * CHUNK_LEN;
+ data->input = g_new0(char, data->len);
+ data->output = g_new0(gchar, data->len);
+
+ /* Fill input with a pattern */
+ for (i = 0; i < data->len; i += CHUNK_LEN) {
+ memset(data->input + i, (i / CHUNK_LEN), CHUNK_LEN);
+ }
+
+ /* We'll split the data across a bunch of IO vecs */
+ data->niov = CHUNK_COUNT;
+ data->inputv = g_new0(struct iovec, data->niov);
+ data->outputv = g_new0(struct iovec, data->niov);
+
+ for (i = 0, offset = 0; i < data->niov; i++, offset += CHUNK_LEN) {
+ data->inputv[i].iov_base = data->input + offset;
+ data->outputv[i].iov_base = data->output + offset;
+ data->inputv[i].iov_len = CHUNK_LEN;
+ data->outputv[i].iov_len = CHUNK_LEN;
+ }
+
+ return data;
+}
+
+void qio_channel_test_run_threads(QIOChannelTest *test,
+ bool blocking,
+ QIOChannel *src,
+ QIOChannel *dst)
+{
+ GThread *reader, *writer;
+
+ test->src = src;
+ test->dst = dst;
+ test->blocking = blocking;
+
+ reader = g_thread_new("reader",
+ test_io_thread_reader,
+ test);
+ writer = g_thread_new("writer",
+ test_io_thread_writer,
+ test);
+
+ g_thread_join(reader);
+ g_thread_join(writer);
+
+ test->dst = test->src = NULL;
+}
+
+
+void qio_channel_test_run_writer(QIOChannelTest *test,
+ QIOChannel *src)
+{
+ test->src = src;
+ test_io_thread_writer(test);
+ test->src = NULL;
+}
+
+
+void qio_channel_test_run_reader(QIOChannelTest *test,
+ QIOChannel *dst)
+{
+ test->dst = dst;
+ test_io_thread_reader(test);
+ test->dst = NULL;
+}
+
+
+void qio_channel_test_validate(QIOChannelTest *test)
+{
+ g_assert_cmpint(memcmp(test->input,
+ test->output,
+ test->len), ==, 0);
+ g_assert(test->readerr == NULL);
+ g_assert(test->writeerr == NULL);
+
+ g_free(test->inputv);
+ g_free(test->outputv);
+ g_free(test->input);
+ g_free(test->output);
+ g_free(test);
+}
diff --git a/tests/io-channel-helpers.h b/tests/io-channel-helpers.h
new file mode 100644
index 0000000000..fedc64fd5a
--- /dev/null
+++ b/tests/io-channel-helpers.h
@@ -0,0 +1,42 @@
+/*
+ * QEMU I/O channel test helpers
+ *
+ * Copyright (c) 2015 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "io/channel.h"
+
+#ifndef TEST_IO_CHANNEL_HELPERS
+#define TEST_IO_CHANNEL_HELPERS
+
+typedef struct QIOChannelTest QIOChannelTest;
+
+QIOChannelTest *qio_channel_test_new(void);
+
+void qio_channel_test_run_threads(QIOChannelTest *test,
+ bool blocking,
+ QIOChannel *src,
+ QIOChannel *dst);
+
+void qio_channel_test_run_writer(QIOChannelTest *test,
+ QIOChannel *src);
+void qio_channel_test_run_reader(QIOChannelTest *test,
+ QIOChannel *dst);
+
+void qio_channel_test_validate(QIOChannelTest *test);
+
+#endif /* TEST_IO_CHANNEL_HELPERS */
diff --git a/tests/test-io-channel-buffer.c b/tests/test-io-channel-buffer.c
new file mode 100644
index 0000000000..6637501b41
--- /dev/null
+++ b/tests/test-io-channel-buffer.c
@@ -0,0 +1,50 @@
+/*
+ * QEMU I/O channel buffer test
+ *
+ * Copyright (c) 2015 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "io/channel-buffer.h"
+#include "io-channel-helpers.h"
+
+
+static void test_io_channel_buf(void)
+{
+ QIOChannelBuffer *buf;
+ QIOChannelTest *test;
+
+ buf = qio_channel_buffer_new(0);
+
+ test = qio_channel_test_new();
+ qio_channel_test_run_writer(test, QIO_CHANNEL(buf));
+ buf->offset = 0;
+ qio_channel_test_run_reader(test, QIO_CHANNEL(buf));
+ qio_channel_test_validate(test);
+
+ object_unref(OBJECT(buf));
+}
+
+
+int main(int argc, char **argv)
+{
+ module_call_init(MODULE_INIT_QOM);
+
+ g_test_init(&argc, &argv, NULL);
+
+ g_test_add_func("/io/channel/buf", test_io_channel_buf);
+ return g_test_run();
+}
diff --git a/tests/test-io-channel-command.c b/tests/test-io-channel-command.c
new file mode 100644
index 0000000000..03cac36a3f
--- /dev/null
+++ b/tests/test-io-channel-command.c
@@ -0,0 +1,129 @@
+/*
+ * QEMU I/O channel command test
+ *
+ * Copyright (c) 2015 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "io/channel-command.h"
+#include "io-channel-helpers.h"
+
+#ifndef WIN32
+static void test_io_channel_command_fifo(bool async)
+{
+#define TEST_FIFO "tests/test-io-channel-command.fifo"
+ QIOChannel *src, *dst;
+ QIOChannelTest *test;
+ char *srcfifo = g_strdup_printf("PIPE:%s,wronly", TEST_FIFO);
+ char *dstfifo = g_strdup_printf("PIPE:%s,rdonly", TEST_FIFO);
+ const char *srcargv[] = {
+ "/bin/socat", "-", srcfifo, NULL,
+ };
+ const char *dstargv[] = {
+ "/bin/socat", dstfifo, "-", NULL,
+ };
+
+ unlink(TEST_FIFO);
+ if (access("/bin/socat", X_OK) < 0) {
+ return; /* Pretend success if socat is not present */
+ }
+ if (mkfifo(TEST_FIFO, 0600) < 0) {
+ abort();
+ }
+ src = QIO_CHANNEL(qio_channel_command_new_spawn(srcargv,
+ O_WRONLY,
+ &error_abort));
+ dst = QIO_CHANNEL(qio_channel_command_new_spawn(dstargv,
+ O_RDONLY,
+ &error_abort));
+
+ test = qio_channel_test_new();
+ qio_channel_test_run_threads(test, async, src, dst);
+ qio_channel_test_validate(test);
+
+ object_unref(OBJECT(src));
+ object_unref(OBJECT(dst));
+
+ g_free(srcfifo);
+ g_free(dstfifo);
+ unlink(TEST_FIFO);
+}
+
+
+static void test_io_channel_command_fifo_async(void)
+{
+ test_io_channel_command_fifo(true);
+}
+
+static void test_io_channel_command_fifo_sync(void)
+{
+ test_io_channel_command_fifo(false);
+}
+
+
+static void test_io_channel_command_echo(bool async)
+{
+ QIOChannel *ioc;
+ QIOChannelTest *test;
+ const char *socatargv[] = {
+ "/bin/socat", "-", "-", NULL,
+ };
+
+ if (access("/bin/socat", X_OK) < 0) {
+ return; /* Pretend success if socat is not present */
+ }
+
+ ioc = QIO_CHANNEL(qio_channel_command_new_spawn(socatargv,
+ O_RDWR,
+ &error_abort));
+ test = qio_channel_test_new();
+ qio_channel_test_run_threads(test, async, ioc, ioc);
+ qio_channel_test_validate(test);
+
+ object_unref(OBJECT(ioc));
+}
+
+
+static void test_io_channel_command_echo_async(void)
+{
+ test_io_channel_command_echo(true);
+}
+
+static void test_io_channel_command_echo_sync(void)
+{
+ test_io_channel_command_echo(false);
+}
+#endif
+
+int main(int argc, char **argv)
+{
+ module_call_init(MODULE_INIT_QOM);
+
+ g_test_init(&argc, &argv, NULL);
+
+#ifndef WIN32
+ g_test_add_func("/io/channel/command/fifo/sync",
+ test_io_channel_command_fifo_sync);
+ g_test_add_func("/io/channel/command/fifo/async",
+ test_io_channel_command_fifo_async);
+ g_test_add_func("/io/channel/command/echo/sync",
+ test_io_channel_command_echo_sync);
+ g_test_add_func("/io/channel/command/echo/async",
+ test_io_channel_command_echo_async);
+#endif
+
+ return g_test_run();
+}
diff --git a/tests/test-io-channel-file.c b/tests/test-io-channel-file.c
new file mode 100644
index 0000000000..f276a32de0
--- /dev/null
+++ b/tests/test-io-channel-file.c
@@ -0,0 +1,100 @@
+/*
+ * QEMU I/O channel file test
+ *
+ * Copyright (c) 2015 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "io/channel-file.h"
+#include "io-channel-helpers.h"
+
+
+static void test_io_channel_file(void)
+{
+ QIOChannel *src, *dst;
+ QIOChannelTest *test;
+
+#define TEST_FILE "tests/test-io-channel-file.txt"
+ unlink(TEST_FILE);
+ src = QIO_CHANNEL(qio_channel_file_new_path(
+ TEST_FILE,
+ O_WRONLY | O_CREAT | O_TRUNC | O_BINARY, 0600,
+ &error_abort));
+ dst = QIO_CHANNEL(qio_channel_file_new_path(
+ TEST_FILE,
+ O_RDONLY | O_BINARY, 0,
+ &error_abort));
+
+ test = qio_channel_test_new();
+ qio_channel_test_run_writer(test, src);
+ qio_channel_test_run_reader(test, dst);
+ qio_channel_test_validate(test);
+
+ unlink(TEST_FILE);
+ object_unref(OBJECT(src));
+ object_unref(OBJECT(dst));
+}
+
+
+#ifndef _WIN32
+static void test_io_channel_pipe(bool async)
+{
+ QIOChannel *src, *dst;
+ QIOChannelTest *test;
+ int fd[2];
+
+ if (pipe(fd) < 0) {
+ perror("pipe");
+ abort();
+ }
+
+ src = QIO_CHANNEL(qio_channel_file_new_fd(fd[1]));
+ dst = QIO_CHANNEL(qio_channel_file_new_fd(fd[0]));
+
+ test = qio_channel_test_new();
+ qio_channel_test_run_threads(test, async, src, dst);
+ qio_channel_test_validate(test);
+
+ object_unref(OBJECT(src));
+ object_unref(OBJECT(dst));
+}
+
+
+static void test_io_channel_pipe_async(void)
+{
+ test_io_channel_pipe(true);
+}
+
+static void test_io_channel_pipe_sync(void)
+{
+ test_io_channel_pipe(false);
+}
+#endif /* ! _WIN32 */
+
+
+int main(int argc, char **argv)
+{
+ module_call_init(MODULE_INIT_QOM);
+
+ g_test_init(&argc, &argv, NULL);
+
+ g_test_add_func("/io/channel/file", test_io_channel_file);
+#ifndef _WIN32
+ g_test_add_func("/io/channel/pipe/sync", test_io_channel_pipe_sync);
+ g_test_add_func("/io/channel/pipe/async", test_io_channel_pipe_async);
+#endif
+ return g_test_run();
+}
diff --git a/tests/test-io-channel-socket.c b/tests/test-io-channel-socket.c
new file mode 100644
index 0000000000..194d043878
--- /dev/null
+++ b/tests/test-io-channel-socket.c
@@ -0,0 +1,399 @@
+/*
+ * QEMU I/O channel sockets test
+ *
+ * Copyright (c) 2015 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "io/channel-socket.h"
+#include "io-channel-helpers.h"
+#ifdef HAVE_IFADDRS_H
+#include <ifaddrs.h>
+#endif
+
+static int check_protocol_support(bool *has_ipv4, bool *has_ipv6)
+{
+#ifdef HAVE_IFADDRS_H
+ struct ifaddrs *ifaddr = NULL, *ifa;
+ struct addrinfo hints = { 0 };
+ struct addrinfo *ai = NULL;
+ int gaierr;
+
+ *has_ipv4 = *has_ipv6 = false;
+
+ if (getifaddrs(&ifaddr) < 0) {
+ g_printerr("Failed to lookup interface addresses: %s\n",
+ strerror(errno));
+ return -1;
+ }
+
+ for (ifa = ifaddr; ifa != NULL; ifa = ifa->ifa_next) {
+ if (!ifa->ifa_addr) {
+ continue;
+ }
+
+ if (ifa->ifa_addr->sa_family == AF_INET) {
+ *has_ipv4 = true;
+ }
+ if (ifa->ifa_addr->sa_family == AF_INET6) {
+ *has_ipv6 = true;
+ }
+ }
+
+ freeifaddrs(ifaddr);
+
+ hints.ai_flags = AI_PASSIVE | AI_ADDRCONFIG;
+ hints.ai_family = AF_INET6;
+ hints.ai_socktype = SOCK_STREAM;
+
+ gaierr = getaddrinfo("::1", NULL, &hints, &ai);
+ if (gaierr != 0) {
+ if (gaierr == EAI_ADDRFAMILY ||
+ gaierr == EAI_FAMILY ||
+ gaierr == EAI_NONAME) {
+ *has_ipv6 = false;
+ } else {
+ g_printerr("Failed to resolve ::1 address: %s\n",
+ gai_strerror(gaierr));
+ return -1;
+ }
+ }
+
+ freeaddrinfo(ai);
+
+ return 0;
+#else
+ *has_ipv4 = *has_ipv6 = false;
+
+ return -1;
+#endif
+}
+
+
+static void test_io_channel_set_socket_bufs(QIOChannel *src,
+ QIOChannel *dst)
+{
+ int buflen = 64 * 1024;
+
+ /*
+ * Make the socket buffers small so that we see
+ * the effects of partial reads/writes
+ */
+ setsockopt(((QIOChannelSocket *)src)->fd,
+ SOL_SOCKET, SO_SNDBUF,
+ (char *)&buflen,
+ sizeof(buflen));
+
+ setsockopt(((QIOChannelSocket *)dst)->fd,
+ SOL_SOCKET, SO_SNDBUF,
+ (char *)&buflen,
+ sizeof(buflen));
+}
+
+
+static void test_io_channel_setup_sync(SocketAddress *listen_addr,
+ SocketAddress *connect_addr,
+ QIOChannel **src,
+ QIOChannel **dst)
+{
+ QIOChannelSocket *lioc;
+
+ lioc = qio_channel_socket_new();
+ qio_channel_socket_listen_sync(lioc, listen_addr, &error_abort);
+
+ if (listen_addr->type == SOCKET_ADDRESS_KIND_INET) {
+ SocketAddress *laddr = qio_channel_socket_get_local_address(
+ lioc, &error_abort);
+
+ g_free(connect_addr->u.inet->port);
+ connect_addr->u.inet->port = g_strdup(laddr->u.inet->port);
+
+ qapi_free_SocketAddress(laddr);
+ }
+
+ *src = QIO_CHANNEL(qio_channel_socket_new());
+ qio_channel_socket_connect_sync(
+ QIO_CHANNEL_SOCKET(*src), connect_addr, &error_abort);
+ qio_channel_set_delay(*src, false);
+
+ *dst = QIO_CHANNEL(qio_channel_socket_accept(lioc, &error_abort));
+ g_assert(*dst);
+
+ test_io_channel_set_socket_bufs(*src, *dst);
+
+ object_unref(OBJECT(lioc));
+}
+
+
+struct TestIOChannelData {
+ bool err;
+ GMainLoop *loop;
+};
+
+
+static void test_io_channel_complete(Object *src,
+ Error *err,
+ gpointer opaque)
+{
+ struct TestIOChannelData *data = opaque;
+ data->err = err != NULL;
+ g_main_loop_quit(data->loop);
+}
+
+
+static void test_io_channel_setup_async(SocketAddress *listen_addr,
+ SocketAddress *connect_addr,
+ QIOChannel **src,
+ QIOChannel **dst)
+{
+ QIOChannelSocket *lioc;
+ struct TestIOChannelData data;
+
+ data.loop = g_main_loop_new(g_main_context_default(),
+ TRUE);
+
+ lioc = qio_channel_socket_new();
+ qio_channel_socket_listen_async(
+ lioc, listen_addr,
+ test_io_channel_complete, &data, NULL);
+
+ g_main_loop_run(data.loop);
+ g_main_context_iteration(g_main_context_default(), FALSE);
+
+ g_assert(!data.err);
+
+ if (listen_addr->type == SOCKET_ADDRESS_KIND_INET) {
+ SocketAddress *laddr = qio_channel_socket_get_local_address(
+ lioc, &error_abort);
+
+ g_free(connect_addr->u.inet->port);
+ connect_addr->u.inet->port = g_strdup(laddr->u.inet->port);
+
+ qapi_free_SocketAddress(laddr);
+ }
+
+ *src = QIO_CHANNEL(qio_channel_socket_new());
+
+ qio_channel_socket_connect_async(
+ QIO_CHANNEL_SOCKET(*src), connect_addr,
+ test_io_channel_complete, &data, NULL);
+
+ g_main_loop_run(data.loop);
+ g_main_context_iteration(g_main_context_default(), FALSE);
+
+ g_assert(!data.err);
+
+ *dst = QIO_CHANNEL(qio_channel_socket_accept(lioc, &error_abort));
+ g_assert(*dst);
+
+ qio_channel_set_delay(*src, false);
+ test_io_channel_set_socket_bufs(*src, *dst);
+
+ object_unref(OBJECT(lioc));
+
+ g_main_loop_unref(data.loop);
+}
+
+
+static void test_io_channel(bool async,
+ SocketAddress *listen_addr,
+ SocketAddress *connect_addr)
+{
+ QIOChannel *src, *dst;
+ QIOChannelTest *test;
+ if (async) {
+ test_io_channel_setup_async(listen_addr, connect_addr, &src, &dst);
+
+ test = qio_channel_test_new();
+ qio_channel_test_run_threads(test, true, src, dst);
+ qio_channel_test_validate(test);
+
+ object_unref(OBJECT(src));
+ object_unref(OBJECT(dst));
+
+ test_io_channel_setup_async(listen_addr, connect_addr, &src, &dst);
+
+ test = qio_channel_test_new();
+ qio_channel_test_run_threads(test, false, src, dst);
+ qio_channel_test_validate(test);
+
+ object_unref(OBJECT(src));
+ object_unref(OBJECT(dst));
+ } else {
+ test_io_channel_setup_sync(listen_addr, connect_addr, &src, &dst);
+
+ test = qio_channel_test_new();
+ qio_channel_test_run_threads(test, true, src, dst);
+ qio_channel_test_validate(test);
+
+ object_unref(OBJECT(src));
+ object_unref(OBJECT(dst));
+
+ test_io_channel_setup_sync(listen_addr, connect_addr, &src, &dst);
+
+ test = qio_channel_test_new();
+ qio_channel_test_run_threads(test, false, src, dst);
+ qio_channel_test_validate(test);
+
+ object_unref(OBJECT(src));
+ object_unref(OBJECT(dst));
+ }
+}
+
+
+static void test_io_channel_ipv4(bool async)
+{
+ SocketAddress *listen_addr = g_new0(SocketAddress, 1);
+ SocketAddress *connect_addr = g_new0(SocketAddress, 1);
+
+ listen_addr->type = SOCKET_ADDRESS_KIND_INET;
+ listen_addr->u.inet = g_new0(InetSocketAddress, 1);
+ listen_addr->u.inet->host = g_strdup("0.0.0.0");
+ listen_addr->u.inet->port = NULL; /* Auto-select */
+
+ connect_addr->type = SOCKET_ADDRESS_KIND_INET;
+ connect_addr->u.inet = g_new0(InetSocketAddress, 1);
+ connect_addr->u.inet->host = g_strdup("127.0.0.1");
+ connect_addr->u.inet->port = NULL; /* Filled in later */
+
+ test_io_channel(async, listen_addr, connect_addr);
+
+ qapi_free_SocketAddress(listen_addr);
+ qapi_free_SocketAddress(connect_addr);
+}
+
+
+static void test_io_channel_ipv4_sync(void)
+{
+ return test_io_channel_ipv4(false);
+}
+
+
+static void test_io_channel_ipv4_async(void)
+{
+ return test_io_channel_ipv4(true);
+}
+
+
+static void test_io_channel_ipv6(bool async)
+{
+ SocketAddress *listen_addr = g_new0(SocketAddress, 1);
+ SocketAddress *connect_addr = g_new0(SocketAddress, 1);
+
+ listen_addr->type = SOCKET_ADDRESS_KIND_INET;
+ listen_addr->u.inet = g_new0(InetSocketAddress, 1);
+ listen_addr->u.inet->host = g_strdup("::");
+ listen_addr->u.inet->port = NULL; /* Auto-select */
+
+ connect_addr->type = SOCKET_ADDRESS_KIND_INET;
+ connect_addr->u.inet = g_new0(InetSocketAddress, 1);
+ connect_addr->u.inet->host = g_strdup("::1");
+ connect_addr->u.inet->port = NULL; /* Filled in later */
+
+ test_io_channel(async, listen_addr, connect_addr);
+
+ qapi_free_SocketAddress(listen_addr);
+ qapi_free_SocketAddress(connect_addr);
+}
+
+
+static void test_io_channel_ipv6_sync(void)
+{
+ return test_io_channel_ipv6(false);
+}
+
+
+static void test_io_channel_ipv6_async(void)
+{
+ return test_io_channel_ipv6(true);
+}
+
+
+#ifndef _WIN32
+static void test_io_channel_unix(bool async)
+{
+ SocketAddress *listen_addr = g_new0(SocketAddress, 1);
+ SocketAddress *connect_addr = g_new0(SocketAddress, 1);
+
+#define TEST_SOCKET "test-io-channel-socket.sock"
+ listen_addr->type = SOCKET_ADDRESS_KIND_UNIX;
+ listen_addr->u.q_unix = g_new0(UnixSocketAddress, 1);
+ listen_addr->u.q_unix->path = g_strdup(TEST_SOCKET);
+
+ connect_addr->type = SOCKET_ADDRESS_KIND_UNIX;
+ connect_addr->u.q_unix = g_new0(UnixSocketAddress, 1);
+ connect_addr->u.q_unix->path = g_strdup(TEST_SOCKET);
+
+ test_io_channel(async, listen_addr, connect_addr);
+
+ qapi_free_SocketAddress(listen_addr);
+ qapi_free_SocketAddress(connect_addr);
+ unlink(TEST_SOCKET);
+}
+
+
+static void test_io_channel_unix_sync(void)
+{
+ return test_io_channel_unix(false);
+}
+
+
+static void test_io_channel_unix_async(void)
+{
+ return test_io_channel_unix(true);
+}
+#endif /* _WIN32 */
+
+
+int main(int argc, char **argv)
+{
+ bool has_ipv4, has_ipv6;
+
+ module_call_init(MODULE_INIT_QOM);
+
+ g_test_init(&argc, &argv, NULL);
+
+ /* We're creating actual IPv4/6 sockets, so we should
+ * check if the host running tests actually supports
+ * each protocol to avoid breaking tests on machines
+ * with either IPv4 or IPv6 disabled.
+ */
+ if (check_protocol_support(&has_ipv4, &has_ipv6) < 0) {
+ return 1;
+ }
+
+ if (has_ipv4) {
+ g_test_add_func("/io/channel/socket/ipv4-sync",
+ test_io_channel_ipv4_sync);
+ g_test_add_func("/io/channel/socket/ipv4-async",
+ test_io_channel_ipv4_async);
+ }
+ if (has_ipv6) {
+ g_test_add_func("/io/channel/socket/ipv6-sync",
+ test_io_channel_ipv6_sync);
+ g_test_add_func("/io/channel/socket/ipv6-async",
+ test_io_channel_ipv6_async);
+ }
+
+#ifndef _WIN32
+ g_test_add_func("/io/channel/socket/unix-sync",
+ test_io_channel_unix_sync);
+ g_test_add_func("/io/channel/socket/unix-async",
+ test_io_channel_unix_async);
+#endif /* _WIN32 */
+
+ return g_test_run();
+}
diff --git a/tests/test-io-channel-tls.c b/tests/test-io-channel-tls.c
new file mode 100644
index 0000000000..3c11a5097c
--- /dev/null
+++ b/tests/test-io-channel-tls.c
@@ -0,0 +1,342 @@
+/*
+ * QEMU I/O channel TLS test
+ *
+ * Copyright (C) 2015 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Author: Daniel P. Berrange <berrange@redhat.com>
+ */
+
+
+#include <stdlib.h>
+#include <fcntl.h>
+
+#include "config-host.h"
+#include "crypto-tls-x509-helpers.h"
+#include "io/channel-tls.h"
+#include "io/channel-socket.h"
+#include "io-channel-helpers.h"
+#include "crypto/tlscredsx509.h"
+#include "qemu/acl.h"
+#include "qom/object_interfaces.h"
+
+#ifdef QCRYPTO_HAVE_TLS_TEST_SUPPORT
+
+#define WORKDIR "tests/test-io-channel-tls-work/"
+#define KEYFILE WORKDIR "key-ctx.pem"
+
+struct QIOChannelTLSTestData {
+ const char *servercacrt;
+ const char *clientcacrt;
+ const char *servercrt;
+ const char *clientcrt;
+ bool expectServerFail;
+ bool expectClientFail;
+ const char *hostname;
+ const char *const *wildcards;
+};
+
+struct QIOChannelTLSHandshakeData {
+ bool finished;
+ bool failed;
+};
+
+static void test_tls_handshake_done(Object *source,
+ Error *err,
+ gpointer opaque)
+{
+ struct QIOChannelTLSHandshakeData *data = opaque;
+
+ data->finished = true;
+ data->failed = err != NULL;
+}
+
+
+static QCryptoTLSCreds *test_tls_creds_create(QCryptoTLSCredsEndpoint endpoint,
+ const char *certdir,
+ Error **errp)
+{
+ Object *parent = object_get_objects_root();
+ Object *creds = object_new_with_props(
+ TYPE_QCRYPTO_TLS_CREDS_X509,
+ parent,
+ (endpoint == QCRYPTO_TLS_CREDS_ENDPOINT_SERVER ?
+ "testtlscredsserver" : "testtlscredsclient"),
+ errp,
+ "endpoint", (endpoint == QCRYPTO_TLS_CREDS_ENDPOINT_SERVER ?
+ "server" : "client"),
+ "dir", certdir,
+ "verify-peer", "yes",
+ /* We skip initial sanity checks here because we
+ * want to make sure that problems are being
+ * detected at the TLS session validation stage,
+ * and the test-crypto-tlscreds test already
+ * validate the sanity check code.
+ */
+ "sanity-check", "no",
+ NULL
+ );
+
+ if (*errp) {
+ return NULL;
+ }
+ return QCRYPTO_TLS_CREDS(creds);
+}
+
+
+/*
+ * This tests validation checking of peer certificates
+ *
+ * This is replicating the checks that are done for an
+ * active TLS session after handshake completes. To
+ * simulate that we create our TLS contexts, skipping
+ * sanity checks. When then get a socketpair, and
+ * initiate a TLS session across them. Finally do
+ * do actual cert validation tests
+ */
+static void test_io_channel_tls(const void *opaque)
+{
+ struct QIOChannelTLSTestData *data =
+ (struct QIOChannelTLSTestData *)opaque;
+ QCryptoTLSCreds *clientCreds;
+ QCryptoTLSCreds *serverCreds;
+ QIOChannelTLS *clientChanTLS;
+ QIOChannelTLS *serverChanTLS;
+ QIOChannelSocket *clientChanSock;
+ QIOChannelSocket *serverChanSock;
+ qemu_acl *acl;
+ const char * const *wildcards;
+ int channel[2];
+ struct QIOChannelTLSHandshakeData clientHandshake = { false, false };
+ struct QIOChannelTLSHandshakeData serverHandshake = { false, false };
+ Error *err = NULL;
+ QIOChannelTest *test;
+ GMainContext *mainloop;
+
+ /* We'll use this for our fake client-server connection */
+ g_assert(socketpair(AF_UNIX, SOCK_STREAM, 0, channel) == 0);
+
+#define CLIENT_CERT_DIR "tests/test-crypto-tlssession-client/"
+#define SERVER_CERT_DIR "tests/test-crypto-tlssession-server/"
+ mkdir(CLIENT_CERT_DIR, 0700);
+ mkdir(SERVER_CERT_DIR, 0700);
+
+ unlink(SERVER_CERT_DIR QCRYPTO_TLS_CREDS_X509_CA_CERT);
+ unlink(SERVER_CERT_DIR QCRYPTO_TLS_CREDS_X509_SERVER_CERT);
+ unlink(SERVER_CERT_DIR QCRYPTO_TLS_CREDS_X509_SERVER_KEY);
+
+ unlink(CLIENT_CERT_DIR QCRYPTO_TLS_CREDS_X509_CA_CERT);
+ unlink(CLIENT_CERT_DIR QCRYPTO_TLS_CREDS_X509_CLIENT_CERT);
+ unlink(CLIENT_CERT_DIR QCRYPTO_TLS_CREDS_X509_CLIENT_KEY);
+
+ g_assert(link(data->servercacrt,
+ SERVER_CERT_DIR QCRYPTO_TLS_CREDS_X509_CA_CERT) == 0);
+ g_assert(link(data->servercrt,
+ SERVER_CERT_DIR QCRYPTO_TLS_CREDS_X509_SERVER_CERT) == 0);
+ g_assert(link(KEYFILE,
+ SERVER_CERT_DIR QCRYPTO_TLS_CREDS_X509_SERVER_KEY) == 0);
+
+ g_assert(link(data->clientcacrt,
+ CLIENT_CERT_DIR QCRYPTO_TLS_CREDS_X509_CA_CERT) == 0);
+ g_assert(link(data->clientcrt,
+ CLIENT_CERT_DIR QCRYPTO_TLS_CREDS_X509_CLIENT_CERT) == 0);
+ g_assert(link(KEYFILE,
+ CLIENT_CERT_DIR QCRYPTO_TLS_CREDS_X509_CLIENT_KEY) == 0);
+
+ clientCreds = test_tls_creds_create(
+ QCRYPTO_TLS_CREDS_ENDPOINT_CLIENT,
+ CLIENT_CERT_DIR,
+ &err);
+ g_assert(clientCreds != NULL);
+
+ serverCreds = test_tls_creds_create(
+ QCRYPTO_TLS_CREDS_ENDPOINT_SERVER,
+ SERVER_CERT_DIR,
+ &err);
+ g_assert(serverCreds != NULL);
+
+ acl = qemu_acl_init("channeltlsacl");
+ qemu_acl_reset(acl);
+ wildcards = data->wildcards;
+ while (wildcards && *wildcards) {
+ qemu_acl_append(acl, 0, *wildcards);
+ wildcards++;
+ }
+
+ clientChanSock = qio_channel_socket_new_fd(
+ channel[0], &err);
+ g_assert(clientChanSock != NULL);
+ serverChanSock = qio_channel_socket_new_fd(
+ channel[1], &err);
+ g_assert(serverChanSock != NULL);
+
+ /*
+ * We have an evil loop to do the handshake in a single
+ * thread, so we need these non-blocking to avoid deadlock
+ * of ourselves
+ */
+ qio_channel_set_blocking(QIO_CHANNEL(clientChanSock), false, NULL);
+ qio_channel_set_blocking(QIO_CHANNEL(serverChanSock), false, NULL);
+
+ /* Now the real part of the test, setup the sessions */
+ clientChanTLS = qio_channel_tls_new_client(
+ QIO_CHANNEL(clientChanSock), clientCreds,
+ data->hostname, &err);
+ g_assert(clientChanTLS != NULL);
+
+ serverChanTLS = qio_channel_tls_new_server(
+ QIO_CHANNEL(serverChanSock), serverCreds,
+ "channeltlsacl", &err);
+ g_assert(serverChanTLS != NULL);
+
+ qio_channel_tls_handshake(clientChanTLS,
+ test_tls_handshake_done,
+ &clientHandshake,
+ NULL);
+ qio_channel_tls_handshake(serverChanTLS,
+ test_tls_handshake_done,
+ &serverHandshake,
+ NULL);
+
+ /*
+ * Finally we loop around & around doing handshake on each
+ * session until we get an error, or the handshake completes.
+ * This relies on the socketpair being nonblocking to avoid
+ * deadlocking ourselves upon handshake
+ */
+ mainloop = g_main_context_default();
+ do {
+ g_main_context_iteration(mainloop, TRUE);
+ } while (!clientHandshake.finished &&
+ !serverHandshake.finished);
+
+ g_assert(clientHandshake.failed == data->expectClientFail);
+ g_assert(serverHandshake.failed == data->expectServerFail);
+
+ test = qio_channel_test_new();
+ qio_channel_test_run_threads(test, false,
+ QIO_CHANNEL(clientChanTLS),
+ QIO_CHANNEL(serverChanTLS));
+ qio_channel_test_validate(test);
+
+ test = qio_channel_test_new();
+ qio_channel_test_run_threads(test, true,
+ QIO_CHANNEL(clientChanTLS),
+ QIO_CHANNEL(serverChanTLS));
+ qio_channel_test_validate(test);
+
+ unlink(SERVER_CERT_DIR QCRYPTO_TLS_CREDS_X509_CA_CERT);
+ unlink(SERVER_CERT_DIR QCRYPTO_TLS_CREDS_X509_SERVER_CERT);
+ unlink(SERVER_CERT_DIR QCRYPTO_TLS_CREDS_X509_SERVER_KEY);
+
+ unlink(CLIENT_CERT_DIR QCRYPTO_TLS_CREDS_X509_CA_CERT);
+ unlink(CLIENT_CERT_DIR QCRYPTO_TLS_CREDS_X509_CLIENT_CERT);
+ unlink(CLIENT_CERT_DIR QCRYPTO_TLS_CREDS_X509_CLIENT_KEY);
+
+ rmdir(CLIENT_CERT_DIR);
+ rmdir(SERVER_CERT_DIR);
+
+ object_unparent(OBJECT(serverCreds));
+ object_unparent(OBJECT(clientCreds));
+
+ object_unref(OBJECT(serverChanTLS));
+ object_unref(OBJECT(clientChanTLS));
+
+ object_unref(OBJECT(serverChanSock));
+ object_unref(OBJECT(clientChanSock));
+
+ close(channel[0]);
+ close(channel[1]);
+}
+
+
+int main(int argc, char **argv)
+{
+ int ret;
+
+ module_call_init(MODULE_INIT_QOM);
+ g_test_init(&argc, &argv, NULL);
+ setenv("GNUTLS_FORCE_FIPS_MODE", "2", 1);
+
+ mkdir(WORKDIR, 0700);
+
+ test_tls_init(KEYFILE);
+
+# define TEST_CHANNEL(name, caCrt, \
+ serverCrt, clientCrt, \
+ expectServerFail, expectClientFail, \
+ hostname, wildcards) \
+ struct QIOChannelTLSTestData name = { \
+ caCrt, caCrt, serverCrt, clientCrt, \
+ expectServerFail, expectClientFail, \
+ hostname, wildcards \
+ }; \
+ g_test_add_data_func("/qio/channel/tls/" # name, \
+ &name, test_io_channel_tls);
+
+ /* A perfect CA, perfect client & perfect server */
+
+ /* Basic:CA:critical */
+ TLS_ROOT_REQ(cacertreq,
+ "UK", "qemu CA", NULL, NULL, NULL, NULL,
+ true, true, true,
+ true, true, GNUTLS_KEY_KEY_CERT_SIGN,
+ false, false, NULL, NULL,
+ 0, 0);
+ TLS_CERT_REQ(servercertreq, cacertreq,
+ "UK", "qemu.org", NULL, NULL, NULL, NULL,
+ true, true, false,
+ true, true,
+ GNUTLS_KEY_DIGITAL_SIGNATURE | GNUTLS_KEY_KEY_ENCIPHERMENT,
+ true, true, GNUTLS_KP_TLS_WWW_SERVER, NULL,
+ 0, 0);
+ TLS_CERT_REQ(clientcertreq, cacertreq,
+ "UK", "qemu", NULL, NULL, NULL, NULL,
+ true, true, false,
+ true, true,
+ GNUTLS_KEY_DIGITAL_SIGNATURE | GNUTLS_KEY_KEY_ENCIPHERMENT,
+ true, true, GNUTLS_KP_TLS_WWW_CLIENT, NULL,
+ 0, 0);
+
+ const char *const wildcards[] = {
+ "C=UK,CN=qemu*",
+ NULL,
+ };
+ TEST_CHANNEL(basic, cacertreq.filename, servercertreq.filename,
+ clientcertreq.filename, false, false,
+ "qemu.org", wildcards);
+
+ ret = g_test_run();
+
+ test_tls_discard_cert(&clientcertreq);
+ test_tls_discard_cert(&servercertreq);
+ test_tls_discard_cert(&cacertreq);
+
+ test_tls_cleanup(KEYFILE);
+ rmdir(WORKDIR);
+
+ return ret == 0 ? EXIT_SUCCESS : EXIT_FAILURE;
+}
+
+#else /* ! QCRYPTO_HAVE_TLS_TEST_SUPPORT */
+
+int
+main(void)
+{
+ return EXIT_SUCCESS;
+}
+
+#endif /* ! QCRYPTO_HAVE_TLS_TEST_SUPPORT */
diff --git a/tests/test-io-task.c b/tests/test-io-task.c
new file mode 100644
index 0000000000..3344382c7f
--- /dev/null
+++ b/tests/test-io-task.c
@@ -0,0 +1,268 @@
+/*
+ * QEMU I/O task tests
+ *
+ * Copyright (c) 2015 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <glib.h>
+
+#include "io/task.h"
+
+#define TYPE_DUMMY "qemu:dummy"
+
+typedef struct DummyObject DummyObject;
+typedef struct DummyObjectClass DummyObjectClass;
+
+struct DummyObject {
+ Object parent;
+};
+
+struct DummyObjectClass {
+ ObjectClass parent;
+};
+
+static const TypeInfo dummy_info = {
+ .parent = TYPE_OBJECT,
+ .name = TYPE_DUMMY,
+ .instance_size = sizeof(DummyObject),
+ .class_size = sizeof(DummyObjectClass),
+};
+
+struct TestTaskData {
+ Object *source;
+ Error *err;
+ bool freed;
+};
+
+
+static void task_callback(Object *source,
+ Error *err,
+ gpointer opaque)
+{
+ struct TestTaskData *data = opaque;
+
+ data->source = source;
+ data->err = err;
+}
+
+
+static void test_task_complete(void)
+{
+ QIOTask *task;
+ Object *obj = object_new(TYPE_DUMMY);
+ Object *src;
+ struct TestTaskData data = { NULL, NULL, false };
+
+ task = qio_task_new(obj, task_callback, &data, NULL);
+ src = qio_task_get_source(task);
+
+ qio_task_complete(task);
+
+ g_assert(obj == src);
+
+ object_unref(obj);
+ object_unref(src);
+
+ g_assert(data.source == obj);
+ g_assert(data.err == NULL);
+ g_assert(data.freed == false);
+}
+
+
+static void task_data_free(gpointer opaque)
+{
+ struct TestTaskData *data = opaque;
+
+ data->freed = true;
+}
+
+
+static void test_task_data_free(void)
+{
+ QIOTask *task;
+ Object *obj = object_new(TYPE_DUMMY);
+ struct TestTaskData data = { NULL, NULL, false };
+
+ task = qio_task_new(obj, task_callback, &data, task_data_free);
+
+ qio_task_complete(task);
+
+ object_unref(obj);
+
+ g_assert(data.source == obj);
+ g_assert(data.err == NULL);
+ g_assert(data.freed == true);
+}
+
+
+static void test_task_error(void)
+{
+ QIOTask *task;
+ Object *obj = object_new(TYPE_DUMMY);
+ struct TestTaskData data = { NULL, NULL, false };
+ Error *err = NULL;
+
+ task = qio_task_new(obj, task_callback, &data, NULL);
+
+ error_setg(&err, "Some error");
+
+ qio_task_abort(task, err);
+
+ error_free(err);
+ object_unref(obj);
+
+ g_assert(data.source == obj);
+ g_assert(data.err == err);
+ g_assert(data.freed == false);
+
+}
+
+
+struct TestThreadWorkerData {
+ Object *source;
+ Error *err;
+ bool fail;
+ GThread *worker;
+ GThread *complete;
+ GMainLoop *loop;
+};
+
+static int test_task_thread_worker(QIOTask *task,
+ Error **errp,
+ gpointer opaque)
+{
+ struct TestThreadWorkerData *data = opaque;
+
+ data->worker = g_thread_self();
+
+ if (data->fail) {
+ error_setg(errp, "Testing fail");
+ return -1;
+ }
+
+ return 0;
+}
+
+
+static void test_task_thread_callback(Object *source,
+ Error *err,
+ gpointer opaque)
+{
+ struct TestThreadWorkerData *data = opaque;
+
+ data->source = source;
+ data->err = err;
+
+ data->complete = g_thread_self();
+
+ g_main_loop_quit(data->loop);
+}
+
+
+static void test_task_thread_complete(void)
+{
+ QIOTask *task;
+ Object *obj = object_new(TYPE_DUMMY);
+ struct TestThreadWorkerData data = { 0 };
+ GThread *self;
+
+ data.loop = g_main_loop_new(g_main_context_default(),
+ TRUE);
+
+ task = qio_task_new(obj,
+ test_task_thread_callback,
+ &data,
+ NULL);
+
+ qio_task_run_in_thread(task,
+ test_task_thread_worker,
+ &data,
+ NULL);
+
+ g_main_loop_run(data.loop);
+
+ g_main_loop_unref(data.loop);
+ object_unref(obj);
+
+ g_assert(data.source == obj);
+ g_assert(data.err == NULL);
+
+ self = g_thread_self();
+
+ /* Make sure the test_task_thread_worker actually got
+ * run in a different thread */
+ g_assert(data.worker != self);
+
+ /* And that the test_task_thread_callback got rnu in
+ * the main loop thread (ie this one) */
+ g_assert(data.complete == self);
+}
+
+
+static void test_task_thread_error(void)
+{
+ QIOTask *task;
+ Object *obj = object_new(TYPE_DUMMY);
+ struct TestThreadWorkerData data = { 0 };
+ GThread *self;
+
+ data.loop = g_main_loop_new(g_main_context_default(),
+ TRUE);
+ data.fail = true;
+
+ task = qio_task_new(obj,
+ test_task_thread_callback,
+ &data,
+ NULL);
+
+ qio_task_run_in_thread(task,
+ test_task_thread_worker,
+ &data,
+ NULL);
+
+ g_main_loop_run(data.loop);
+
+ g_main_loop_unref(data.loop);
+ object_unref(obj);
+
+ g_assert(data.source == obj);
+ g_assert(data.err != NULL);
+
+ self = g_thread_self();
+
+ /* Make sure the test_task_thread_worker actually got
+ * run in a different thread */
+ g_assert(data.worker != self);
+
+ /* And that the test_task_thread_callback got rnu in
+ * the main loop thread (ie this one) */
+ g_assert(data.complete == self);
+}
+
+
+int main(int argc, char **argv)
+{
+ g_test_init(&argc, &argv, NULL);
+ module_call_init(MODULE_INIT_QOM);
+ type_register_static(&dummy_info);
+ g_test_add_func("/crypto/task/complete", test_task_complete);
+ g_test_add_func("/crypto/task/datafree", test_task_data_free);
+ g_test_add_func("/crypto/task/error", test_task_error);
+ g_test_add_func("/crypto/task/thread_complete", test_task_thread_complete);
+ g_test_add_func("/crypto/task/thread_error", test_task_thread_error);
+ return g_test_run();
+}