Patchwork [14/15] qapi: add test-libqmp

login
register
mail settings
Submitter Anthony Liguori
Date March 11, 2011, 11:05 p.m.
Message ID <1299884745-521-15-git-send-email-aliguori@us.ibm.com>
Download mbox | patch
Permalink /patch/86479/
State New
Headers show

Comments

Anthony Liguori - March 11, 2011, 11:05 p.m.
This provides a glib-test based testing framework for QMP

Signed-off-by: Anthony Liguori <aliguori@us.ibm.com>
Blue Swirl - March 12, 2011, 11:23 a.m.
On Sat, Mar 12, 2011 at 1:05 AM, Anthony Liguori <aliguori@us.ibm.com> wrote:
> This provides a glib-test based testing framework for QMP
>
> Signed-off-by: Anthony Liguori <aliguori@us.ibm.com>
>
> diff --git a/Makefile b/Makefile
> index 5170675..1d363d7 100644
> --- a/Makefile
> +++ b/Makefile
> @@ -72,6 +72,8 @@ defconfig:
>
>  -include config-all-devices.mak
>
> +TOOLS += test-libqmp
> +
>  build-all: $(DOCS) $(TOOLS) recurse-all
>
>  config-host.h: config-host.h-timestamp
> @@ -205,6 +207,15 @@ check-qlist: check-qlist.o qlist.o qint.o $(CHECK_PROG_DEPS)
>  check-qfloat: check-qfloat.o qfloat.o $(CHECK_PROG_DEPS)
>  check-qjson: check-qjson.o qfloat.o qint.o qdict.o qstring.o qlist.o qbool.o qjson.o json-streamer.o json-lexer.o json-parser.o $(CHECK_PROG_DEPS)
>
> +LIBQMP_OBJS := qmp-types.o libqmp.o error.o libqmp-core.o
> +LIBQMP_OBJS += qmp-marshal-types-core.o qmp-marshal-types.o
> +LIBQMP_OBJS += qfloat.o qint.o qdict.o qstring.o qlist.o qbool.o qjson.o
> +LIBQMP_OBJS += qerror.o
> +LIBQMP_OBJS += json-streamer.o json-lexer.o json-parser.o
> +LIBQMP_OBJS += $(oslib-obj-y) $(trace-obj-y) qemu-malloc.o
> +
> +test-libqmp: test-libqmp.o $(LIBQMP_OBJS) qemu-timer-common.o
> +
>  clean:
>  # avoid old build problems by removing potentially incorrect old files
>        rm -f config.mak op-i386.h opc-i386.h gen-op-i386.h op-arm.h opc-arm.h gen-op-arm.h
> diff --git a/test-libqmp.c b/test-libqmp.c
> new file mode 100644

I'd put this to tests/.

> index 0000000..9b73987
> --- /dev/null
> +++ b/test-libqmp.c
> @@ -0,0 +1,170 @@
> +/*
> + * QAPI
> + *
> + * Copyright IBM, Corp. 2011
> + *
> + * Authors:
> + *  Anthony Liguori   <aliguori@us.ibm.com>
> + *
> + * This work is licensed under the terms of the GNU LGPL, version 2.  See
> + * the COPYING.LIB file in the top-level directory.
> + */
> +#include <stdio.h>
> +#include <sys/socket.h>
> +#include <netinet/in.h>
> +#include <netinet/tcp.h>
> +#include <arpa/inet.h>
> +#include <sys/un.h>
> +#include <stdlib.h>
> +#include <glib.h>
> +#include <sys/wait.h>
> +#include "config-host.h"
> +#include "libqmp.h"
> +#include "qerror.h"
> +
> +#define g_assert_noerr(err) g_assert(err == NULL);
> +#define g_assert_anyerr(err) g_assert(err != NULL);
> +#define g_assert_cmperr(err, op, type) do {                   \
> +    g_assert_anyerr(err);                                        \
> +    g_assert_cmpstr(error_get_field(err, "class"), op, type); \
> +} while (0)
> +
> +static pid_t last_qemu_pid = -1;
> +
> +static QmpSession *qemu(const char *fmt, ...)
> +{
> +    char buffer0[4096];
> +    char buffer1[4096];
> +    const char *pid_filename = "/tmp/test-libqmp-qemu.pid";
> +    const char *path = "/tmp/test-libqmp-qemu.sock";

Very insecure filenames.

> +    struct sockaddr_un addr;
> +    va_list ap;
> +    int ret;
> +    int fd;
> +
> +    va_start(ap, fmt);
> +    vsnprintf(buffer0, sizeof(buffer0), fmt, ap);
> +    va_end(ap);
> +
> +    snprintf(buffer1, sizeof(buffer1),
> +             "i386-softmmu/qemu "
> +             "-enable-kvm "
> +             "-name test-libqmp "
> +             "-qmp2 qmp "
> +             "-chardev socket,id=qmp,path=%s,server=on,wait=off "
> +             "-vnc none "
> +             "-daemonize "
> +             "-pidfile %s "
> +             "%s", path, pid_filename, buffer0);
> +    g_test_message("Executing %s\n", buffer1);
> +    ret = system(buffer1);
> +    g_assert(ret != -1);
> +
> +    {
> +        FILE *f;
> +        char buffer[1024];
> +        char *ptr;
> +
> +        f = fopen(pid_filename, "r");
> +        g_assert(f != NULL);
> +
> +        ptr = fgets(buffer, sizeof(buffer), f);
> +        g_assert(ptr != NULL);
> +
> +        fclose(f);
> +
> +        last_qemu_pid = atoi(buffer);
> +    }
> +
> +    fd = socket(PF_UNIX, SOCK_STREAM, 0);
> +    g_assert(fd != -1);
> +
> +    addr.sun_family = AF_UNIX;
> +    snprintf(addr.sun_path, sizeof(addr.sun_path), "%s", path);
> +    ret = connect(fd, (struct sockaddr *)&addr, sizeof(addr));
> +    g_assert(ret != -1);
> +
> +    return qmp_session_new(fd);
> +}
> +
> +static void wait_for_pid_exit(pid_t pid)
> +{
> +    FILE *f = NULL;
> +
> +    /* This is ugly but I don't know of a better way */

man waitpid?

> +    do {
> +        char buffer[1024];
> +
> +        if (f) {
> +            fclose(f);
> +            usleep(10000);
> +        }
> +
> +        snprintf(buffer, sizeof(buffer), "/proc/%d/stat", pid);
> +        f = fopen(buffer, "r");
> +    } while (f);
> +}
> +
> +static void qemu_destroy(QmpSession *sess)
> +{
> +    wait_for_pid_exit(last_qemu_pid);
> +    last_qemu_pid = -1;
> +    qmp_session_destroy(sess);
> +}
> +
> +static void test_version(void)
> +{
> +    QmpSession *sess = NULL;
> +    VersionInfo *info;
> +    char version[1024];
> +    char *ptr, *end;
> +    int major, minor, micro;
> +
> +    /* Even though we use the same string as the source input, we do parse it
> +     * a little bit different for no other reason that to make sure we catch
> +     * potential bugs.
> +     */
> +    snprintf(version, sizeof(version), "%s", QEMU_VERSION);
> +    ptr = version;
> +
> +    end = strchr(ptr, '.');
> +    g_assert(end != NULL);
> +    *end = 0;
> +    major = atoi(ptr);

strtoll(), also below.

> +    ptr = end + 1;
> +
> +    end = strchr(ptr, '.');
> +    g_assert(end != NULL);
> +    *end = 0;
> +    minor = atoi(ptr);
> +    ptr = end + 1;
> +
> +    micro = atoi(ptr);
> +    while (g_ascii_isdigit(*ptr)) ptr++;
> +
> +    sess = qemu("-S");
> +
> +    info = libqmp_query_version(sess, NULL);
> +
> +    g_assert_cmpint(major, ==, info->qemu.major);
> +    g_assert_cmpint(minor, ==, info->qemu.minor);
> +    g_assert_cmpint(micro, ==, info->qemu.micro);
> +    g_assert_cmpstr(ptr, ==, info->package);
> +
> +    qmp_free_version_info(info);
> +
> +    libqmp_quit(sess, NULL);
> +
> +    qemu_destroy(sess);
> +}
> +
> +int main(int argc, char **argv)
> +{
> +    g_test_init(&argc, &argv, NULL);
> +
> +    g_test_add_func("/0.14/misc/version", test_version);
> +
> +    g_test_run();
> +
> +    return 0;
> +}
> --
> 1.7.0.4
>
>
>
Anthony Liguori - March 12, 2011, 2:59 p.m.
On 03/12/2011 05:23 AM, Blue Swirl wrote:
> On Sat, Mar 12, 2011 at 1:05 AM, Anthony Liguori<aliguori@us.ibm.com>  wrote:
>> This provides a glib-test based testing framework for QMP
>>
>> Signed-off-by: Anthony Liguori<aliguori@us.ibm.com>
>>
>> diff --git a/Makefile b/Makefile
>> index 5170675..1d363d7 100644
>> --- a/Makefile
>> +++ b/Makefile
>> @@ -72,6 +72,8 @@ defconfig:
>>
>>   -include config-all-devices.mak
>>
>> +TOOLS += test-libqmp
>> +
>>   build-all: $(DOCS) $(TOOLS) recurse-all
>>
>>   config-host.h: config-host.h-timestamp
>> @@ -205,6 +207,15 @@ check-qlist: check-qlist.o qlist.o qint.o $(CHECK_PROG_DEPS)
>>   check-qfloat: check-qfloat.o qfloat.o $(CHECK_PROG_DEPS)
>>   check-qjson: check-qjson.o qfloat.o qint.o qdict.o qstring.o qlist.o qbool.o qjson.o json-streamer.o json-lexer.o json-parser.o $(CHECK_PROG_DEPS)
>>
>> +LIBQMP_OBJS := qmp-types.o libqmp.o error.o libqmp-core.o
>> +LIBQMP_OBJS += qmp-marshal-types-core.o qmp-marshal-types.o
>> +LIBQMP_OBJS += qfloat.o qint.o qdict.o qstring.o qlist.o qbool.o qjson.o
>> +LIBQMP_OBJS += qerror.o
>> +LIBQMP_OBJS += json-streamer.o json-lexer.o json-parser.o
>> +LIBQMP_OBJS += $(oslib-obj-y) $(trace-obj-y) qemu-malloc.o
>> +
>> +test-libqmp: test-libqmp.o $(LIBQMP_OBJS) qemu-timer-common.o
>> +
>>   clean:
>>   # avoid old build problems by removing potentially incorrect old files
>>         rm -f config.mak op-i386.h opc-i386.h gen-op-i386.h op-arm.h opc-arm.h gen-op-arm.h
>> diff --git a/test-libqmp.c b/test-libqmp.c
>> new file mode 100644
> I'd put this to tests/.

tests/ lives outside of the QEMU build system today.  It's also very TCG 
specific.

How about taking the current contents of tests/ and moving it to 
tests/tcg and moving test-libqmp and check-*.c to tests/?

>> index 0000000..9b73987
>> --- /dev/null
>> +++ b/test-libqmp.c
>> @@ -0,0 +1,170 @@
>> +/*
>> + * QAPI
>> + *
>> + * Copyright IBM, Corp. 2011
>> + *
>> + * Authors:
>> + *  Anthony Liguori<aliguori@us.ibm.com>
>> + *
>> + * This work is licensed under the terms of the GNU LGPL, version 2.  See
>> + * the COPYING.LIB file in the top-level directory.
>> + */
>> +#include<stdio.h>
>> +#include<sys/socket.h>
>> +#include<netinet/in.h>
>> +#include<netinet/tcp.h>
>> +#include<arpa/inet.h>
>> +#include<sys/un.h>
>> +#include<stdlib.h>
>> +#include<glib.h>
>> +#include<sys/wait.h>
>> +#include "config-host.h"
>> +#include "libqmp.h"
>> +#include "qerror.h"
>> +
>> +#define g_assert_noerr(err) g_assert(err == NULL);
>> +#define g_assert_anyerr(err) g_assert(err != NULL);
>> +#define g_assert_cmperr(err, op, type) do {                   \
>> +    g_assert_anyerr(err);                                        \
>> +    g_assert_cmpstr(error_get_field(err, "class"), op, type); \
>> +} while (0)
>> +
>> +static pid_t last_qemu_pid = -1;
>> +
>> +static QmpSession *qemu(const char *fmt, ...)
>> +{
>> +    char buffer0[4096];
>> +    char buffer1[4096];
>> +    const char *pid_filename = "/tmp/test-libqmp-qemu.pid";
>> +    const char *path = "/tmp/test-libqmp-qemu.sock";
> Very insecure filenames.

This disappears in round 3 when I introduce discovery support in libqmp.

Even so, I don't think security is a major concern here.

>> +static void wait_for_pid_exit(pid_t pid)
>> +{
>> +    FILE *f = NULL;
>> +
>> +    /* This is ugly but I don't know of a better way */
> man waitpid?

waitpid only works for child processes.  Since we launch with 
-daemonize, that the QEMU instance is no longer a child process.

We use -daemonize because it avoids the race condition where we try to 
connect to a QMP socket but QEMU hasn't created the socket yet.  It also 
means that we can just use system() to invoke QEMU which makes life a 
whole lot simpler.

Regards,

Anthony Liguori

Patch

diff --git a/Makefile b/Makefile
index 5170675..1d363d7 100644
--- a/Makefile
+++ b/Makefile
@@ -72,6 +72,8 @@  defconfig:
 
 -include config-all-devices.mak
 
+TOOLS += test-libqmp
+
 build-all: $(DOCS) $(TOOLS) recurse-all
 
 config-host.h: config-host.h-timestamp
@@ -205,6 +207,15 @@  check-qlist: check-qlist.o qlist.o qint.o $(CHECK_PROG_DEPS)
 check-qfloat: check-qfloat.o qfloat.o $(CHECK_PROG_DEPS)
 check-qjson: check-qjson.o qfloat.o qint.o qdict.o qstring.o qlist.o qbool.o qjson.o json-streamer.o json-lexer.o json-parser.o $(CHECK_PROG_DEPS)
 
+LIBQMP_OBJS := qmp-types.o libqmp.o error.o libqmp-core.o
+LIBQMP_OBJS += qmp-marshal-types-core.o qmp-marshal-types.o
+LIBQMP_OBJS += qfloat.o qint.o qdict.o qstring.o qlist.o qbool.o qjson.o
+LIBQMP_OBJS += qerror.o
+LIBQMP_OBJS += json-streamer.o json-lexer.o json-parser.o
+LIBQMP_OBJS += $(oslib-obj-y) $(trace-obj-y) qemu-malloc.o
+
+test-libqmp: test-libqmp.o $(LIBQMP_OBJS) qemu-timer-common.o
+
 clean:
 # avoid old build problems by removing potentially incorrect old files
 	rm -f config.mak op-i386.h opc-i386.h gen-op-i386.h op-arm.h opc-arm.h gen-op-arm.h
diff --git a/test-libqmp.c b/test-libqmp.c
new file mode 100644
index 0000000..9b73987
--- /dev/null
+++ b/test-libqmp.c
@@ -0,0 +1,170 @@ 
+/*
+ * QAPI
+ *
+ * Copyright IBM, Corp. 2011
+ *
+ * Authors:
+ *  Anthony Liguori   <aliguori@us.ibm.com>
+ *
+ * This work is licensed under the terms of the GNU LGPL, version 2.  See
+ * the COPYING.LIB file in the top-level directory.
+ */
+#include <stdio.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <netinet/tcp.h>
+#include <arpa/inet.h>
+#include <sys/un.h>
+#include <stdlib.h>
+#include <glib.h>
+#include <sys/wait.h>
+#include "config-host.h"
+#include "libqmp.h"
+#include "qerror.h"
+
+#define g_assert_noerr(err) g_assert(err == NULL);
+#define g_assert_anyerr(err) g_assert(err != NULL);
+#define g_assert_cmperr(err, op, type) do {                   \
+    g_assert_anyerr(err);                                        \
+    g_assert_cmpstr(error_get_field(err, "class"), op, type); \
+} while (0)
+
+static pid_t last_qemu_pid = -1;
+
+static QmpSession *qemu(const char *fmt, ...)
+{
+    char buffer0[4096];
+    char buffer1[4096];
+    const char *pid_filename = "/tmp/test-libqmp-qemu.pid";
+    const char *path = "/tmp/test-libqmp-qemu.sock";
+    struct sockaddr_un addr;
+    va_list ap;
+    int ret;
+    int fd;
+    
+    va_start(ap, fmt);
+    vsnprintf(buffer0, sizeof(buffer0), fmt, ap);
+    va_end(ap);
+
+    snprintf(buffer1, sizeof(buffer1),
+             "i386-softmmu/qemu "
+             "-enable-kvm "
+             "-name test-libqmp "
+             "-qmp2 qmp "
+             "-chardev socket,id=qmp,path=%s,server=on,wait=off "
+             "-vnc none "
+             "-daemonize "
+             "-pidfile %s "
+             "%s", path, pid_filename, buffer0);
+    g_test_message("Executing %s\n", buffer1);
+    ret = system(buffer1);
+    g_assert(ret != -1);
+
+    {
+        FILE *f;
+        char buffer[1024];
+        char *ptr;
+
+        f = fopen(pid_filename, "r");
+        g_assert(f != NULL);
+
+        ptr = fgets(buffer, sizeof(buffer), f);
+        g_assert(ptr != NULL);
+
+        fclose(f);
+
+        last_qemu_pid = atoi(buffer);
+    }
+
+    fd = socket(PF_UNIX, SOCK_STREAM, 0);
+    g_assert(fd != -1);
+
+    addr.sun_family = AF_UNIX;
+    snprintf(addr.sun_path, sizeof(addr.sun_path), "%s", path);
+    ret = connect(fd, (struct sockaddr *)&addr, sizeof(addr));
+    g_assert(ret != -1);
+
+    return qmp_session_new(fd);
+}
+
+static void wait_for_pid_exit(pid_t pid)
+{
+    FILE *f = NULL;
+
+    /* This is ugly but I don't know of a better way */
+    do {
+        char buffer[1024];
+
+        if (f) {
+            fclose(f);
+            usleep(10000);
+        }
+
+        snprintf(buffer, sizeof(buffer), "/proc/%d/stat", pid);
+        f = fopen(buffer, "r");
+    } while (f);
+}
+
+static void qemu_destroy(QmpSession *sess)
+{
+    wait_for_pid_exit(last_qemu_pid);
+    last_qemu_pid = -1;
+    qmp_session_destroy(sess);
+}
+
+static void test_version(void)
+{
+    QmpSession *sess = NULL;
+    VersionInfo *info;
+    char version[1024];
+    char *ptr, *end;
+    int major, minor, micro;
+
+    /* Even though we use the same string as the source input, we do parse it
+     * a little bit different for no other reason that to make sure we catch
+     * potential bugs.
+     */
+    snprintf(version, sizeof(version), "%s", QEMU_VERSION);
+    ptr = version;
+    
+    end = strchr(ptr, '.');
+    g_assert(end != NULL);
+    *end = 0;
+    major = atoi(ptr);
+    ptr = end + 1;
+
+    end = strchr(ptr, '.');
+    g_assert(end != NULL);
+    *end = 0;
+    minor = atoi(ptr);
+    ptr = end + 1;
+    
+    micro = atoi(ptr);
+    while (g_ascii_isdigit(*ptr)) ptr++;
+
+    sess = qemu("-S");
+
+    info = libqmp_query_version(sess, NULL);
+
+    g_assert_cmpint(major, ==, info->qemu.major);
+    g_assert_cmpint(minor, ==, info->qemu.minor);
+    g_assert_cmpint(micro, ==, info->qemu.micro);
+    g_assert_cmpstr(ptr, ==, info->package);
+
+    qmp_free_version_info(info);
+
+    libqmp_quit(sess, NULL);
+
+    qemu_destroy(sess);
+}
+
+int main(int argc, char **argv)
+{
+    g_test_init(&argc, &argv, NULL);
+
+    g_test_add_func("/0.14/misc/version", test_version);
+
+    g_test_run();
+
+    return 0;
+}