X-Git-Url: https://repo.jachan.dev/qemu.git/blobdiff_plain/ddee57e0176f6ab53b13c6c97605b62737a8fd7a..1f63669ec925b9158095c289ef590384d3a4a591:/tests/libqtest.c diff --git a/tests/libqtest.c b/tests/libqtest.c index 6f33a37667..44ce118cfc 100644 --- a/tests/libqtest.c +++ b/tests/libqtest.c @@ -21,10 +21,10 @@ #include #include "libqtest.h" +#include "qemu-common.h" #include "qemu/cutils.h" #include "qapi/error.h" #include "qapi/qmp/json-parser.h" -#include "qapi/qmp/json-streamer.h" #include "qapi/qmp/qdict.h" #include "qapi/qmp/qjson.h" #include "qapi/qmp/qlist.h" @@ -48,10 +48,6 @@ struct QTestState static GHookList abrt_hooks; static struct sigaction sigact_old; -#define g_assert_no_errno(ret) do { \ - g_assert_cmpint(ret, !=, -1); \ -} while (0) - static int qtest_query_target_endianness(QTestState *s); static int init_socket(const char *socket_path) @@ -61,7 +57,7 @@ static int init_socket(const char *socket_path) int ret; sock = socket(PF_UNIX, SOCK_STREAM, 0); - g_assert_no_errno(sock); + g_assert_cmpint(sock, !=, -1); addr.sun_family = AF_UNIX; snprintf(addr.sun_path, sizeof(addr.sun_path), "%s", socket_path); @@ -70,9 +66,9 @@ static int init_socket(const char *socket_path) do { ret = bind(sock, (struct sockaddr *)&addr, sizeof(addr)); } while (ret == -1 && errno == EINTR); - g_assert_no_errno(ret); + g_assert_cmpint(ret, !=, -1); ret = listen(sock, 1); - g_assert_no_errno(ret); + g_assert_cmpint(ret, !=, -1); return sock; } @@ -103,8 +99,33 @@ static int socket_accept(int sock) static void kill_qemu(QTestState *s) { if (s->qemu_pid != -1) { + int wstatus = 0; + pid_t pid; + kill(s->qemu_pid, SIGTERM); - waitpid(s->qemu_pid, NULL, 0); + TFR(pid = waitpid(s->qemu_pid, &wstatus, 0)); + + assert(pid == s->qemu_pid); + /* + * We expect qemu to exit with status 0; anything else is + * fishy and should be logged with as much detail as possible. + */ + if (wstatus) { + if (WIFEXITED(wstatus)) { + fprintf(stderr, "%s:%d: kill_qemu() tried to terminate QEMU " + "process but encountered exit status %d\n", + __FILE__, __LINE__, WEXITSTATUS(wstatus)); + } else if (WIFSIGNALED(wstatus)) { + int sig = WTERMSIG(wstatus); + const char *signame = strsignal(sig) ?: "unknown ???"; + const char *dump = WCOREDUMP(wstatus) ? " (core dumped)" : ""; + + fprintf(stderr, "%s:%d: kill_qemu() detected QEMU death " + "from signal %d (%s)%s\n", + __FILE__, __LINE__, sig, signame, dump); + } + abort(); + } } } @@ -242,32 +263,33 @@ QTestState *qtest_init_without_qmp_handshake(bool use_oob, QTestState *qtest_init(const char *extra_args) { QTestState *s = qtest_init_without_qmp_handshake(false, extra_args); + QDict *greeting; /* Read the QMP greeting and then do the handshake */ - qtest_qmp_discard_response(s, ""); - qtest_qmp_discard_response(s, "{ 'execute': 'qmp_capabilities' }"); + greeting = qtest_qmp_receive(s); + qobject_unref(greeting); + qobject_unref(qtest_qmp(s, "{ 'execute': 'qmp_capabilities' }")); return s; } -QTestState *qtest_vstartf(const char *fmt, va_list ap) +QTestState *qtest_vinitf(const char *fmt, va_list ap) { char *args = g_strdup_vprintf(fmt, ap); QTestState *s; - s = qtest_start(args); + s = qtest_init(args); g_free(args); - global_qtest = NULL; return s; } -QTestState *qtest_startf(const char *fmt, ...) +QTestState *qtest_initf(const char *fmt, ...) { va_list ap; QTestState *s; va_start(ap, fmt); - s = qtest_vstartf(fmt, ap); + s = qtest_vinitf(fmt, ap); va_end(ap); return s; } @@ -299,7 +321,6 @@ static void socket_send(int fd, const char *buf, size_t size) continue; } - g_assert_no_errno(len); g_assert_cmpint(len, >, 0); offset += len; @@ -341,7 +362,7 @@ static GString *qtest_recv_line(QTestState *s) if (len == -1 || len == 0) { fprintf(stderr, "Broken pipe\n"); - exit(1); + abort(); } g_string_append_len(s->rx, buffer, len); @@ -420,15 +441,16 @@ typedef struct { QDict *response; } QMPResponseParser; -static void qmp_response(JSONMessageParser *parser, GQueue *tokens) +static void qmp_response(void *opaque, QObject *obj, Error *err) { - QMPResponseParser *qmp = container_of(parser, QMPResponseParser, parser); - QObject *obj; + QMPResponseParser *qmp = opaque; - obj = json_parser_parse(tokens, NULL); - if (!obj) { - fprintf(stderr, "QMP JSON response parsing failed\n"); - exit(1); + assert(!obj != !err); + + if (err) { + error_prepend(&err, "QMP JSON response parsing failed: "); + error_report_err(err); + abort(); } g_assert(!qmp->response); @@ -442,7 +464,7 @@ QDict *qmp_fd_receive(int fd) bool log = getenv("QTEST_LOG") != NULL; qmp.response = NULL; - json_message_parser_init(&qmp.parser, qmp_response); + json_message_parser_init(&qmp.parser, qmp_response, &qmp, NULL); while (!qmp.response) { ssize_t len; char c; @@ -454,7 +476,7 @@ QDict *qmp_fd_receive(int fd) if (len == -1 || len == 0) { fprintf(stderr, "Broken pipe\n"); - exit(1); + abort(); } if (log) { @@ -477,26 +499,12 @@ QDict *qtest_qmp_receive(QTestState *s) * in the case that they choose to discard all replies up until * a particular EVENT is received. */ -void qmp_fd_sendv(int fd, const char *fmt, va_list ap) +void qmp_fd_vsend(int fd, const char *fmt, va_list ap) { - va_list ap_copy; QObject *qobj; - /* qobject_from_jsonv() silently eats leading 0xff as invalid - * JSON, but we want to test sending them over the wire to force - * resyncs */ - if (*fmt == '\377') { - socket_send(fd, fmt, 1); - fmt++; - } - - /* Going through qobject ensures we escape strings properly. - * This seemingly unnecessary copy is required in case va_list - * is an array type. - */ - va_copy(ap_copy, ap); - qobj = qobject_from_jsonv(fmt, &ap_copy, &error_abort); - va_end(ap_copy); + /* Going through qobject ensures we escape strings properly */ + qobj = qobject_from_vjsonf_nofail(fmt, ap); /* No need to send anything for an empty QObject. */ if (qobj) { @@ -517,26 +525,26 @@ void qmp_fd_sendv(int fd, const char *fmt, va_list ap) /* Send QMP request */ socket_send(fd, str, qstring_get_length(qstr)); - QDECREF(qstr); - qobject_decref(qobj); + qobject_unref(qstr); + qobject_unref(qobj); } } -void qtest_async_qmpv(QTestState *s, const char *fmt, va_list ap) +void qtest_qmp_vsend(QTestState *s, const char *fmt, va_list ap) { - qmp_fd_sendv(s->qmp_fd, fmt, ap); + qmp_fd_vsend(s->qmp_fd, fmt, ap); } QDict *qmp_fdv(int fd, const char *fmt, va_list ap) { - qmp_fd_sendv(fd, fmt, ap); + qmp_fd_vsend(fd, fmt, ap); return qmp_fd_receive(fd); } -QDict *qtest_qmpv(QTestState *s, const char *fmt, va_list ap) +QDict *qtest_vqmp(QTestState *s, const char *fmt, va_list ap) { - qtest_async_qmpv(s, fmt, ap); + qtest_qmp_vsend(s, fmt, ap); /* Receive reply */ return qtest_qmp_receive(s); @@ -558,7 +566,7 @@ void qmp_fd_send(int fd, const char *fmt, ...) va_list ap; va_start(ap, fmt); - qmp_fd_sendv(fd, fmt, ap); + qmp_fd_vsend(fd, fmt, ap); va_end(ap); } @@ -568,35 +576,48 @@ QDict *qtest_qmp(QTestState *s, const char *fmt, ...) QDict *response; va_start(ap, fmt); - response = qtest_qmpv(s, fmt, ap); + response = qtest_vqmp(s, fmt, ap); va_end(ap); return response; } -void qtest_async_qmp(QTestState *s, const char *fmt, ...) +void qtest_qmp_send(QTestState *s, const char *fmt, ...) { va_list ap; va_start(ap, fmt); - qtest_async_qmpv(s, fmt, ap); + qtest_qmp_vsend(s, fmt, ap); va_end(ap); } -void qtest_qmpv_discard_response(QTestState *s, const char *fmt, va_list ap) +void qmp_fd_vsend_raw(int fd, const char *fmt, va_list ap) { - QDict *response = qtest_qmpv(s, fmt, ap); - QDECREF(response); + bool log = getenv("QTEST_LOG") != NULL; + char *str = g_strdup_vprintf(fmt, ap); + + if (log) { + fprintf(stderr, "%s", str); + } + socket_send(fd, str, strlen(str)); + g_free(str); } -void qtest_qmp_discard_response(QTestState *s, const char *fmt, ...) +void qmp_fd_send_raw(int fd, const char *fmt, ...) { va_list ap; - QDict *response; va_start(ap, fmt); - response = qtest_qmpv(s, fmt, ap); + qmp_fd_vsend_raw(fd, fmt, ap); + va_end(ap); +} + +void qtest_qmp_send_raw(QTestState *s, const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + qmp_fd_vsend_raw(s->qmp_fd, fmt, ap); va_end(ap); - QDECREF(response); } QDict *qtest_qmp_eventwait_ref(QTestState *s, const char *event) @@ -609,7 +630,7 @@ QDict *qtest_qmp_eventwait_ref(QTestState *s, const char *event) (strcmp(qdict_get_str(response, "event"), event) == 0)) { return response; } - QDECREF(response); + qobject_unref(response); } } @@ -618,10 +639,10 @@ void qtest_qmp_eventwait(QTestState *s, const char *event) QDict *response; response = qtest_qmp_eventwait_ref(s, event); - QDECREF(response); + qobject_unref(response); } -char *qtest_hmpv(QTestState *s, const char *fmt, va_list ap) +char *qtest_vhmp(QTestState *s, const char *fmt, va_list ap) { char *cmd; QDict *resp; @@ -634,12 +655,12 @@ char *qtest_hmpv(QTestState *s, const char *fmt, va_list ap) ret = g_strdup(qdict_get_try_str(resp, "return")); while (ret == NULL && qdict_get_try_str(resp, "event")) { /* Ignore asynchronous QMP events */ - QDECREF(resp); + qobject_unref(resp); resp = qtest_qmp_receive(s); ret = g_strdup(qdict_get_try_str(resp, "return")); } g_assert(ret); - QDECREF(resp); + qobject_unref(resp); g_free(cmd); return ret; } @@ -650,7 +671,7 @@ char *qtest_hmp(QTestState *s, const char *fmt, ...) char *ret; va_start(ap, fmt); - ret = qtest_hmpv(s, fmt, ap); + ret = qtest_vhmp(s, fmt, ap); va_end(ap); return ret; } @@ -956,35 +977,27 @@ QDict *qmp(const char *fmt, ...) QDict *response; va_start(ap, fmt); - response = qtest_qmpv(global_qtest, fmt, ap); + response = qtest_vqmp(global_qtest, fmt, ap); va_end(ap); return response; } -void qmp_async(const char *fmt, ...) +void qmp_send(const char *fmt, ...) { va_list ap; va_start(ap, fmt); - qtest_async_qmpv(global_qtest, fmt, ap); + qtest_qmp_vsend(global_qtest, fmt, ap); va_end(ap); } -void qmp_discard_response(const char *fmt, ...) -{ - va_list ap; - - va_start(ap, fmt); - qtest_qmpv_discard_response(global_qtest, fmt, ap); - va_end(ap); -} char *hmp(const char *fmt, ...) { va_list ap; char *ret; va_start(ap, fmt); - ret = qtest_hmpv(global_qtest, fmt, ap); + ret = qtest_vhmp(global_qtest, fmt, ap); va_end(ap); return ret; } @@ -994,7 +1007,53 @@ bool qtest_big_endian(QTestState *s) return s->big_endian; } -void qtest_cb_for_every_machine(void (*cb)(const char *machine)) +static bool qtest_check_machine_version(const char *mname, const char *basename, + int major, int minor) +{ + char *newname; + bool is_equal; + + newname = g_strdup_printf("%s-%i.%i", basename, major, minor); + is_equal = g_str_equal(mname, newname); + g_free(newname); + + return is_equal; +} + +static bool qtest_is_old_versioned_machine(const char *mname) +{ + const char *dash = strrchr(mname, '-'); + const char *dot = strrchr(mname, '.'); + const char *chr; + char *bname; + const int major = QEMU_VERSION_MAJOR; + const int minor = QEMU_VERSION_MINOR; + bool res = false; + + if (dash && dot && dot > dash) { + for (chr = dash + 1; *chr; chr++) { + if (!qemu_isdigit(*chr) && *chr != '.') { + return false; + } + } + /* + * Now check if it is one of the latest versions. Check major + 1 + * and minor + 1 versions as well, since they might already exist + * in the development branch. + */ + bname = g_strdup(mname); + bname[dash - mname] = 0; + res = !qtest_check_machine_version(mname, bname, major + 1, 0) && + !qtest_check_machine_version(mname, bname, major, minor + 1) && + !qtest_check_machine_version(mname, bname, major, minor); + g_free(bname); + } + + return res; +} + +void qtest_cb_for_every_machine(void (*cb)(const char *machine), + bool skip_old_versioned) { QDict *response, *minfo; QList *list; @@ -1017,40 +1076,74 @@ void qtest_cb_for_every_machine(void (*cb)(const char *machine)) qstr = qobject_to(QString, qobj); g_assert(qstr); mname = qstring_get_str(qstr); - cb(mname); + if (!skip_old_versioned || !qtest_is_old_versioned_machine(mname)) { + cb(mname); + } } qtest_end(); - QDECREF(response); + qobject_unref(response); +} + +QDict *qtest_qmp_receive_success(QTestState *s, + void (*event_cb)(void *opaque, + const char *event, + QDict *data), + void *opaque) +{ + QDict *response, *ret, *data; + const char *event; + + for (;;) { + response = qtest_qmp_receive(s); + g_assert(!qdict_haskey(response, "error")); + ret = qdict_get_qdict(response, "return"); + if (ret) { + break; + } + event = qdict_get_str(response, "event"); + data = qdict_get_qdict(response, "data"); + if (event_cb) { + event_cb(opaque, event, data); + } + qobject_unref(response); + } + + qobject_ref(ret); + qobject_unref(response); + return ret; } /* * Generic hot-plugging test via the device_add QMP command. */ -void qtest_qmp_device_add(const char *driver, const char *id, const char *fmt, - ...) +void qtest_qmp_device_add(const char *driver, const char *id, + const char *fmt, ...) { - QDict *response; - char *cmd, *opts = NULL; - va_list va; + QDict *args, *response; + va_list ap; - if (fmt) { - va_start(va, fmt); - opts = g_strdup_vprintf(fmt, va); - va_end(va); - } + va_start(ap, fmt); + args = qdict_from_vjsonf_nofail(fmt, ap); + va_end(ap); - cmd = g_strdup_printf("{'execute': 'device_add'," - " 'arguments': { 'driver': '%s', 'id': '%s'%s%s }}", - driver, id, opts ? ", " : "", opts ? opts : ""); - g_free(opts); + g_assert(!qdict_haskey(args, "driver") && !qdict_haskey(args, "id")); + qdict_put_str(args, "driver", driver); + qdict_put_str(args, "id", id); - response = qmp(cmd); - g_free(cmd); + response = qmp("{'execute': 'device_add', 'arguments': %p}", args); g_assert(response); g_assert(!qdict_haskey(response, "event")); /* We don't expect any events */ g_assert(!qdict_haskey(response, "error")); - QDECREF(response); + qobject_unref(response); +} + +static void device_deleted_cb(void *opaque, const char *name, QDict *data) +{ + bool *got_event = opaque; + + g_assert_cmpstr(name, ==, "DEVICE_DELETED"); + *got_event = true; } /* @@ -1073,28 +1166,37 @@ void qtest_qmp_device_add(const char *driver, const char *id, const char *fmt, */ void qtest_qmp_device_del(const char *id) { - QDict *response1, *response2, *event = NULL; - char *cmd; + bool got_event = false; + QDict *rsp; + + qtest_qmp_send(global_qtest, + "{'execute': 'device_del', 'arguments': {'id': %s}}", + id); + rsp = qtest_qmp_receive_success(global_qtest, device_deleted_cb, + &got_event); + qobject_unref(rsp); + if (!got_event) { + rsp = qmp_receive(); + g_assert_cmpstr(qdict_get_try_str(rsp, "event"), + ==, "DEVICE_DELETED"); + qobject_unref(rsp); + } +} - cmd = g_strdup_printf("{'execute': 'device_del'," - " 'arguments': { 'id': '%s' }}", id); - response1 = qmp(cmd); - g_free(cmd); - g_assert(response1); - g_assert(!qdict_haskey(response1, "error")); +bool qmp_rsp_is_err(QDict *rsp) +{ + QDict *error = qdict_get_qdict(rsp, "error"); + qobject_unref(rsp); + return !!error; +} - response2 = qmp(""); - g_assert(response2); - g_assert(!qdict_haskey(response2, "error")); +void qmp_assert_error_class(QDict *rsp, const char *class) +{ + QDict *error = qdict_get_qdict(rsp, "error"); - if (qdict_haskey(response1, "event")) { - event = response1; - } else if (qdict_haskey(response2, "event")) { - event = response2; - } - g_assert(event); - g_assert_cmpstr(qdict_get_str(event, "event"), ==, "DEVICE_DELETED"); + g_assert_cmpstr(qdict_get_try_str(error, "class"), ==, class); + g_assert_nonnull(qdict_get_try_str(error, "desc")); + g_assert(!qdict_haskey(rsp, "return")); - QDECREF(response1); - QDECREF(response2); + qobject_unref(rsp); }