#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"
{
int fd;
int qmp_fd;
- bool irq_level[MAX_IRQ];
- GString *rx;
pid_t qemu_pid; /* our child QEMU process */
+ int wstatus;
bool big_endian;
+ bool irq_level[MAX_IRQ];
+ GString *rx;
};
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;
}
return ret;
}
-static void kill_qemu(QTestState *s)
+bool qtest_probe_child(QTestState *s)
{
- if (s->qemu_pid != -1) {
- int wstatus = 0;
- pid_t pid;
+ pid_t pid = s->qemu_pid;
- kill(s->qemu_pid, SIGTERM);
- TFR(pid = waitpid(s->qemu_pid, &wstatus, 0));
+ if (pid != -1) {
+ pid = waitpid(pid, &s->wstatus, WNOHANG);
+ if (pid == 0) {
+ return true;
+ }
+ s->qemu_pid = -1;
+ }
+ return false;
+}
+
+static void kill_qemu(QTestState *s)
+{
+ pid_t pid = s->qemu_pid;
+ int wstatus;
+ /* Skip wait if qtest_probe_child already reaped. */
+ if (pid != -1) {
+ kill(pid, SIGTERM);
+ TFR(pid = waitpid(s->qemu_pid, &s->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();
+ }
+
+ /*
+ * We expect qemu to exit with status 0; anything else is
+ * fishy and should be logged with as much detail as possible.
+ */
+ wstatus = s->wstatus;
+ 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();
}
}
return qemu_bin;
}
-QTestState *qtest_init_without_qmp_handshake(bool use_oob,
- const char *extra_args)
+QTestState *qtest_init_without_qmp_handshake(const char *extra_args)
{
QTestState *s;
int sock, qmpsock, i;
qtest_add_abrt_handler(kill_qemu_hook_func, s);
+ command = g_strdup_printf("exec %s "
+ "-qtest unix:%s "
+ "-qtest-log %s "
+ "-chardev socket,path=%s,id=char0 "
+ "-mon chardev=char0,mode=control "
+ "-machine accel=qtest "
+ "-display none "
+ "%s", qemu_binary, socket_path,
+ getenv("QTEST_LOG") ? "/dev/fd/2" : "/dev/null",
+ qmp_socket_path,
+ extra_args ?: "");
+
+ g_test_message("starting QEMU: %s", command);
+
+ s->wstatus = 0;
s->qemu_pid = fork();
if (s->qemu_pid == 0) {
setenv("QEMU_AUDIO_DRV", "none", true);
- command = g_strdup_printf("exec %s "
- "-qtest unix:%s,nowait "
- "-qtest-log %s "
- "-chardev socket,path=%s,nowait,id=char0 "
- "-mon chardev=char0,mode=control%s "
- "-machine accel=qtest "
- "-display none "
- "%s", qemu_binary, socket_path,
- getenv("QTEST_LOG") ? "/dev/fd/2" : "/dev/null",
- qmp_socket_path, use_oob ? ",x-oob=on" : "",
- extra_args ?: "");
execlp("/bin/sh", "sh", "-c", command, NULL);
exit(1);
}
+ g_free(command);
s->fd = socket_accept(sock);
if (s->fd >= 0) {
s->qmp_fd = socket_accept(qmpsock);
QTestState *qtest_init(const char *extra_args)
{
- QTestState *s = qtest_init_without_qmp_handshake(false, extra_args);
+ QTestState *s = qtest_init_without_qmp_handshake(extra_args);
QDict *greeting;
/* Read the QMP greeting and then do the handshake */
return s;
}
+QTestState *qtest_init_with_serial(const char *extra_args, int *sock_fd)
+{
+ int sock_fd_init;
+ char *sock_path, sock_dir[] = "/tmp/qtest-serial-XXXXXX";
+ QTestState *qts;
+
+ g_assert_true(mkdtemp(sock_dir) != NULL);
+ sock_path = g_strdup_printf("%s/sock", sock_dir);
+
+ sock_fd_init = init_socket(sock_path);
+
+ qts = qtest_initf("-chardev socket,id=s0,path=%s -serial chardev:s0 %s",
+ sock_path, extra_args);
+
+ *sock_fd = socket_accept(sock_fd_init);
+
+ unlink(sock_path);
+ g_free(sock_path);
+ rmdir(sock_dir);
+
+ g_assert_true(*sock_fd >= 0);
+
+ return qts;
+}
+
void qtest_quit(QTestState *s)
{
g_hook_destroy_link(&abrt_hooks, g_hook_find_data(&abrt_hooks, TRUE, s));
continue;
}
- g_assert_no_errno(len);
g_assert_cmpint(len, >, 0);
offset += 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");
+ assert(!obj != !err);
+
+ if (err) {
+ error_prepend(&err, "QMP JSON response parsing failed: ");
+ error_report_err(err);
abort();
}
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;
{
QObject *qobj;
- /*
- * qobject_from_vjsonf_nofail() chokes on leading 0xff as invalid
- * JSON, but tests/test-qga.c needs to send that to test QGA
- * synchronization
- */
- if (*fmt == '\377') {
- socket_send(fd, fmt, 1);
- fmt++;
- }
-
/* Going through qobject ensures we escape strings properly */
qobj = qobject_from_vjsonf_nofail(fmt, ap);
va_end(ap);
}
+void qmp_fd_vsend_raw(int fd, const char *fmt, va_list ap)
+{
+ 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 qmp_fd_send_raw(int fd, const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ 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);
+}
+
QDict *qtest_qmp_eventwait_ref(QTestState *s, const char *event)
{
QDict *response;
qtest_rsp(s, 0);
}
+void qtest_set_irq_in(QTestState *s, const char *qom_path, const char *name,
+ int num, int level)
+{
+ if (!name) {
+ name = "unnamed-gpio-in";
+ }
+ qtest_sendf(s, "set_irq_in %s %s %d %d\n", qom_path, name, num, level);
+ qtest_rsp(s, 0);
+}
+
static void qtest_out(QTestState *s, const char *cmd, uint16_t addr, uint32_t value)
{
qtest_sendf(s, "%s 0x%x 0x%x\n", cmd, addr, value);
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(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);
+}