#include <sys/un.h>
#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"
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)
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);
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;
}
pid_t pid;
kill(s->qemu_pid, SIGTERM);
- pid = waitpid(s->qemu_pid, &wstatus, 0);
+ TFR(pid = waitpid(s->qemu_pid, &wstatus, 0));
- if (pid == s->qemu_pid && WIFSIGNALED(wstatus)) {
- assert(!WCOREDUMP(wstatus));
+ 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();
}
}
}
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;
}
continue;
}
- g_assert_no_errno(len);
g_assert_cmpint(len, >, 0);
offset += len;
if (len == -1 || len == 0) {
fprintf(stderr, "Broken pipe\n");
- exit(1);
+ abort();
}
g_string_append_len(s->rx, buffer, len);
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);
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;
if (len == -1 || len == 0) {
fprintf(stderr, "Broken pipe\n");
- exit(1);
+ abort();
}
if (log) {
* 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) {
}
}
-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);
va_list ap;
va_start(ap, fmt);
- qmp_fd_sendv(fd, fmt, ap);
+ qmp_fd_vsend(fd, fmt, ap);
va_end(ap);
}
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);
- qobject_unref(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);
- qobject_unref(response);
}
QDict *qtest_qmp_eventwait_ref(QTestState *s, const char *event)
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;
char *ret;
va_start(ap, fmt);
- ret = qtest_hmpv(s, fmt, ap);
+ ret = qtest_vhmp(s, fmt, ap);
va_end(ap);
return ret;
}
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;
}
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;
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();
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"));
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;
+}
+
/*
* Generic hot-unplugging test via the device_del QMP command.
* Device deletion will get one response and one event. For example:
*/
void qtest_qmp_device_del(const char *id)
{
- QDict *response1, *response2, *event = NULL;
- char *cmd;
-
- 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"));
-
- response2 = qmp("");
- g_assert(response2);
- g_assert(!qdict_haskey(response2, "error"));
+ bool got_event = false;
+ QDict *rsp;
- if (qdict_haskey(response1, "event")) {
- event = response1;
- } else if (qdict_haskey(response2, "event")) {
- event = response2;
+ 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);
}
- g_assert(event);
- g_assert_cmpstr(qdict_get_str(event, "event"), ==, "DEVICE_DELETED");
-
- qobject_unref(response1);
- qobject_unref(response2);
}
bool qmp_rsp_is_err(QDict *rsp)
qobject_unref(rsp);
return !!error;
}
+
+void qmp_assert_error_class(QDict *rsp, const char *class)
+{
+ QDict *error = qdict_get_qdict(rsp, "error");
+
+ 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"));
+
+ qobject_unref(rsp);
+}