diff options
-rw-r--r-- | MAINTAINERS | 1 | ||||
-rw-r--r-- | tests/Makefile.include | 5 | ||||
-rw-r--r-- | tests/libqtest.c | 17 | ||||
-rw-r--r-- | tests/libqtest.h | 8 | ||||
-rw-r--r-- | tests/qmp-test.c | 139 |
5 files changed, 164 insertions, 6 deletions
diff --git a/MAINTAINERS b/MAINTAINERS index b9a2171e9b..e3edd04982 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -1404,6 +1404,7 @@ F: qmp.c F: monitor.c F: docs/*qmp-* F: scripts/qmp/ +F: tests/qmp-test.c T: git git://repo.or.cz/qemu/armbru.git qapi-next Register API diff --git a/tests/Makefile.include b/tests/Makefile.include index 364ef1bd23..bb0f430b34 100644 --- a/tests/Makefile.include +++ b/tests/Makefile.include @@ -133,7 +133,9 @@ check-block-$(CONFIG_POSIX) += tests/qemu-iotests-quick.sh # All QTests for now are POSIX-only, but the dependencies are # really in libqtest, not in the testcases themselves. -check-qtest-generic-y = tests/device-introspect-test$(EXESUF) +check-qtest-generic-y = tests/qmp-test$(EXESUF) +gcov-files-generic-y = monitor.c qapi/qmp-dispatch.c +check-qtest-generic-y += tests/device-introspect-test$(EXESUF) gcov-files-generic-y = qdev-monitor.c qmp.c gcov-files-ipack-y += hw/ipack/ipack.c @@ -653,6 +655,7 @@ libqos-imx-obj-y = $(libqos-obj-y) tests/libqos/i2c-imx.o libqos-usb-obj-y = $(libqos-spapr-obj-y) $(libqos-pc-obj-y) tests/libqos/usb.o libqos-virtio-obj-y = $(libqos-spapr-obj-y) $(libqos-pc-obj-y) tests/libqos/virtio.o tests/libqos/virtio-pci.o tests/libqos/virtio-mmio.o tests/libqos/malloc-generic.o +tests/qmp-test$(EXESUF): tests/qmp-test.o tests/device-introspect-test$(EXESUF): tests/device-introspect-test.o tests/rtc-test$(EXESUF): tests/rtc-test.o tests/m48t59-test$(EXESUF): tests/m48t59-test.o diff --git a/tests/libqtest.c b/tests/libqtest.c index 951a3b4bf6..ca6b641963 100644 --- a/tests/libqtest.c +++ b/tests/libqtest.c @@ -149,7 +149,7 @@ void qtest_add_abrt_handler(GHookFunc fn, const void *data) g_hook_prepend(&abrt_hooks, hook); } -QTestState *qtest_init(const char *extra_args) +QTestState *qtest_init_without_qmp_handshake(const char *extra_args) { QTestState *s; int sock, qmpsock, i; @@ -204,10 +204,6 @@ QTestState *qtest_init(const char *extra_args) s->irq_level[i] = false; } - /* Read the QMP greeting and then do the handshake */ - qtest_qmp_discard_response(s, ""); - qtest_qmp_discard_response(s, "{ 'execute': 'qmp_capabilities' }"); - if (getenv("QTEST_STOP")) { kill(s->qemu_pid, SIGSTOP); } @@ -219,6 +215,17 @@ QTestState *qtest_init(const char *extra_args) return s; } +QTestState *qtest_init(const char *extra_args) +{ + QTestState *s = qtest_init_without_qmp_handshake(extra_args); + + /* Read the QMP greeting and then do the handshake */ + qtest_qmp_discard_response(s, ""); + qtest_qmp_discard_response(s, "{ 'execute': 'qmp_capabilities' }"); + + return s; +} + void qtest_quit(QTestState *s) { qtest_instances = g_list_remove(qtest_instances, s); diff --git a/tests/libqtest.h b/tests/libqtest.h index 90f182e1d8..2c9962d94f 100644 --- a/tests/libqtest.h +++ b/tests/libqtest.h @@ -32,6 +32,14 @@ extern QTestState *global_qtest; QTestState *qtest_init(const char *extra_args); /** + * qtest_init_without_qmp_handshake: + * @extra_args: other arguments to pass to QEMU. + * + * Returns: #QTestState instance. + */ +QTestState *qtest_init_without_qmp_handshake(const char *extra_args); + +/** * qtest_quit: * @s: #QTestState instance to operate on. * diff --git a/tests/qmp-test.c b/tests/qmp-test.c new file mode 100644 index 0000000000..405e49ec34 --- /dev/null +++ b/tests/qmp-test.c @@ -0,0 +1,139 @@ +/* + * QMP protocol test cases + * + * Copyright (c) 2017 Red Hat Inc. + * + * Authors: + * Markus Armbruster <armbru@redhat.com>, + * + * This work is licensed under the terms of the GNU GPL, version 2 or later. + * See the COPYING file in the top-level directory. + */ + +#include "qemu/osdep.h" +#include "libqtest.h" +#include "qapi-visit.h" +#include "qapi/error.h" +#include "qapi/qobject-input-visitor.h" +#include "qapi/visitor.h" + +const char common_args[] = "-nodefaults -machine none"; + +static const char *get_error_class(QDict *resp) +{ + QDict *error = qdict_get_qdict(resp, "error"); + const char *desc = qdict_get_try_str(error, "desc"); + + g_assert(desc); + return error ? qdict_get_try_str(error, "class") : NULL; +} + +static void test_version(QObject *version) +{ + Visitor *v; + VersionInfo *vinfo; + + g_assert(version); + v = qobject_input_visitor_new(version, true); + visit_type_VersionInfo(v, "version", &vinfo, &error_abort); + qapi_free_VersionInfo(vinfo); + visit_free(v); +} + +static void test_malformed(void) +{ + QDict *resp; + + /* Not even a dictionary */ + resp = qmp("null"); + g_assert_cmpstr(get_error_class(resp), ==, "GenericError"); + QDECREF(resp); + + /* No "execute" key */ + resp = qmp("{}"); + g_assert_cmpstr(get_error_class(resp), ==, "GenericError"); + QDECREF(resp); + + /* "execute" isn't a string */ + resp = qmp("{ 'execute': true }"); + g_assert_cmpstr(get_error_class(resp), ==, "GenericError"); + QDECREF(resp); + + /* "arguments" isn't a dictionary */ + resp = qmp("{ 'execute': 'no-such-cmd', 'arguments': [] }"); + g_assert_cmpstr(get_error_class(resp), ==, "GenericError"); + QDECREF(resp); + + /* extra key */ + resp = qmp("{ 'execute': 'no-such-cmd', 'extra': true }"); + g_assert_cmpstr(get_error_class(resp), ==, "GenericError"); + QDECREF(resp); +} + +static void test_qmp_protocol(void) +{ + QDict *resp, *q, *ret; + QList *capabilities; + + global_qtest = qtest_init_without_qmp_handshake(common_args); + + /* Test greeting */ + resp = qmp_receive(); + q = qdict_get_qdict(resp, "QMP"); + g_assert(q); + test_version(qdict_get(q, "version")); + capabilities = qdict_get_qlist(q, "capabilities"); + g_assert(capabilities && qlist_empty(capabilities)); + QDECREF(resp); + + /* Test valid command before handshake */ + resp = qmp("{ 'execute': 'query-version' }"); + g_assert_cmpstr(get_error_class(resp), ==, "CommandNotFound"); + QDECREF(resp); + + /* Test malformed commands before handshake */ + test_malformed(); + + /* Test handshake */ + resp = qmp("{ 'execute': 'qmp_capabilities' }"); + ret = qdict_get_qdict(resp, "return"); + g_assert(ret && !qdict_size(ret)); + QDECREF(resp); + + /* Test repeated handshake */ + resp = qmp("{ 'execute': 'qmp_capabilities' }"); + g_assert_cmpstr(get_error_class(resp), ==, "CommandNotFound"); + QDECREF(resp); + + /* Test valid command */ + resp = qmp("{ 'execute': 'query-version' }"); + test_version(qdict_get(resp, "return")); + QDECREF(resp); + + /* Test malformed commands */ + test_malformed(); + + /* Test 'id' */ + resp = qmp("{ 'execute': 'query-name', 'id': 'cookie#1' }"); + ret = qdict_get_qdict(resp, "return"); + g_assert(ret); + g_assert_cmpstr(qdict_get_try_str(resp, "id"), ==, "cookie#1"); + QDECREF(resp); + + /* Test command failure with 'id' */ + resp = qmp("{ 'execute': 'human-monitor-command', 'id': 2 }"); + g_assert_cmpstr(get_error_class(resp), ==, "GenericError"); + g_assert_cmpint(qdict_get_int(resp, "id"), ==, 2); + QDECREF(resp); + + qtest_end(); +} + +int main(int argc, char *argv[]) +{ + g_test_init(&argc, &argv, NULL); + + qtest_add_func("qmp/protocol", test_qmp_protocol); + + return g_test_run(); +} |