qemu-devel.nongnu.org archive mirror
 help / color / mirror / Atom feed
* [Qemu-devel] [PATCH 1/6] qtest: add test framework
@ 2012-01-13 18:32 Anthony Liguori
  2012-01-13 18:32 ` [Qemu-devel] [PATCH 2/6] qtest: add support for -M pc Anthony Liguori
                   ` (5 more replies)
  0 siblings, 6 replies; 25+ messages in thread
From: Anthony Liguori @ 2012-01-13 18:32 UTC (permalink / raw)
  To: qemu-devel; +Cc: Kevin Wolf, Paolo Bonzini, Anthony Liguori, Stefan Hajnoczi

The idea behind qtest is pretty simple.  Instead of executing a CPU via TCG or
KVM, rely on an external process to send events to the device model that the CPU
would normally generate.

qtest presents itself as an accelerator.  In addition, a new option is added to
establish a qtest server (-qtest) that takes a character device.  This is what
allows the external process to send CPU events to the device model.

This is currently modelled after Xen since the Xen device model does something
very similar.  Instead of hooking cpu_exec, Xen sticks the CPU in the halted
state making sure it never gets to execute.  In addition, Xen replaces the LAPIC
with a dummy interrupt controller that forwards interrupt requests.

qtest does the exact same thing and uses a simple line based protocol to send
the events.  Documentation of that protocol is in qtest.c.

I considered reusing the monitor for this job.  Adding interrupts would be a bit
difficult.  In addition, logging would also be difficult.

qtest has extensive logging support.  All protocol commands are logged with
time stamps using a new command line option (-qtest-log).  Logging is important
since ultimately, this is a feature for debugging.

Signed-off-by: Anthony Liguori <aliguori@us.ibm.com>
---
 Makefile.objs   |    2 +
 cpu-exec.c      |    5 +
 qemu-options.hx |    8 ++
 qtest.c         |  357 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
 qtest.h         |   37 ++++++
 vl.c            |    8 ++
 6 files changed, 417 insertions(+), 0 deletions(-)
 create mode 100644 qtest.c
 create mode 100644 qtest.h

diff --git a/Makefile.objs b/Makefile.objs
index 4f6d26c..52b2faf 100644
--- a/Makefile.objs
+++ b/Makefile.objs
@@ -292,6 +292,8 @@ hw-obj-$(CONFIG_DP8393X) += dp8393x.o
 hw-obj-$(CONFIG_DS1225Y) += ds1225y.o
 hw-obj-$(CONFIG_MIPSNET) += mipsnet.o
 
+hw-obj-y += qtest.o
+
 # Sound
 sound-obj-y =
 sound-obj-$(CONFIG_SB16) += sb16.o
diff --git a/cpu-exec.c b/cpu-exec.c
index a9fa608..bf5a2aa 100644
--- a/cpu-exec.c
+++ b/cpu-exec.c
@@ -21,6 +21,7 @@
 #include "disas.h"
 #include "tcg.h"
 #include "qemu-barrier.h"
+#include "qtest.h"
 
 int tb_invalidated_flag;
 
@@ -188,6 +189,10 @@ int cpu_exec(CPUState *env)
     uint8_t *tc_ptr;
     unsigned long next_tb;
 
+    if (qtest_enabled()) {
+        env->halted = 1;
+    }
+
     if (env->halted) {
         if (!cpu_has_work(env)) {
             return EXCP_HALTED;
diff --git a/qemu-options.hx b/qemu-options.hx
index 6295cde..da0c648 100644
--- a/qemu-options.hx
+++ b/qemu-options.hx
@@ -2622,6 +2622,14 @@ the @var{simple} tracing backend.
 @end table
 ETEXI
 
+DEF("qtest", HAS_ARG, QEMU_OPTION_qtest,
+    "-qtest CHR      specify tracing options\n",
+    QEMU_ARCH_ALL)
+
+DEF("qtest-log", HAS_ARG, QEMU_OPTION_qtest_log,
+    "-qtest-log LOG  specify tracing options\n",
+    QEMU_ARCH_ALL)
+
 HXCOMM This is the last statement. Insert new options before this line!
 STEXI
 @end table
diff --git a/qtest.c b/qtest.c
new file mode 100644
index 0000000..f41a9c3
--- /dev/null
+++ b/qtest.c
@@ -0,0 +1,357 @@
+/*
+ * Test Server
+ *
+ * Copyright IBM, Corp. 2011
+ *
+ * Authors:
+ *  Anthony Liguori   <aliguori@us.ibm.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 "qtest.h"
+#include "qemu-char.h"
+#include "ioport.h"
+#include "memory.h"
+#include "hw/irq.h"
+#include "sysemu.h"
+
+#define MAX_IRQ 256
+
+const char *qtest_chrdev;
+const char *qtest_log;
+int qtest_allowed = 0;
+
+static FILE *qtest_log_fp;
+static CharDriverState *qtest_chr;
+static GString *inbuf;
+static int irq_levels[MAX_IRQ];
+static struct timeval start_time;
+static bool qtest_opened;
+
+#define FMT_timeval "%" PRId64 ".%06" PRId64
+
+/**
+ * QTest Protocol
+ *
+ * Line based protocol, request/response based.  Server can send async messages
+ * so clients should always handle many async messages before the response
+ * comes in.
+ *
+ * Valid requests
+ *
+ *  > outb ADDR VALUE
+ *  < OK
+ *
+ *  > outw ADDR VALUE
+ *  < OK
+ *
+ *  > outl ADDR VALUE
+ *  < OK
+ *
+ *  > inb ADDR
+ *  < OK VALUE
+ *
+ *  > inw ADDR
+ *  < OK VALUE
+ *
+ *  > inl ADDR
+ *  < OK VALUE
+ *
+ *  > read ADDR SIZE
+ *  < OK DATA
+ *
+ *  > write ADDR SIZE DATA
+ *  < OK
+ *
+ * Valid async messages:
+ *
+ *  IRQ raise NUM
+ *  IRQ lower NUM
+ *
+ * ADDR, SIZE, VALUE are all integers parsed with strtoul() with a base of 0.
+ *
+ * DATA is an arbitrarily long hex number prefixed with '0x'.  If it's smaller
+ * than the expected size, the value will be zero filled at the end of the data
+ * sequence.
+ *
+ * NUM is an IRQ number.
+ */
+
+static int hex2nib(char ch)
+{
+    if (ch >= '0' && ch <= '9') {
+        return ch - '0';
+    } else if (ch >= 'a' && ch <= 'f') {
+        return 10 + (ch - 'a');
+    } else if (ch >= 'A' && ch <= 'F') {
+        return 10 + (ch - 'a');
+    } else {
+        return -1;
+    }
+}
+
+static void qtest_get_time(struct timeval *tv)
+{
+    gettimeofday(tv, NULL);
+    tv->tv_sec -= start_time.tv_sec;
+    tv->tv_usec -= start_time.tv_usec;
+    if (tv->tv_usec < 0) {
+        tv->tv_usec += 1000000;
+        tv->tv_sec -= 1;
+    }
+}
+
+static void qtest_send_prefix(CharDriverState *chr)
+{
+    struct timeval tv;
+
+    if (!qtest_log_fp || !qtest_opened) {
+        return;
+    }
+
+    qtest_get_time(&tv);
+    fprintf(qtest_log_fp, "[S +" FMT_timeval "] ",
+            tv.tv_sec, tv.tv_usec);
+}
+
+static void qtest_send(CharDriverState *chr, const char *fmt, ...)
+{
+    va_list ap;
+    char buffer[1024];
+    size_t len;
+
+    va_start(ap, fmt);
+    len = vsnprintf(buffer, sizeof(buffer), fmt, ap);
+    va_end(ap);
+
+    qemu_chr_fe_write(chr, (uint8_t *)buffer, len);
+    if (qtest_log_fp && qtest_opened) {
+        fprintf(qtest_log_fp, "%s", buffer);
+    }
+}
+
+static void qtest_process_command(CharDriverState *chr, gchar **words)
+{
+    const gchar *command;
+
+    g_assert(words);
+
+    command = words[0];
+
+    if (qtest_log_fp) {
+        struct timeval tv;
+        int i;
+
+        qtest_get_time(&tv);
+        fprintf(qtest_log_fp, "[R +" FMT_timeval "]",
+                tv.tv_sec, tv.tv_usec);
+        for (i = 0; words[i]; i++) {
+            fprintf(qtest_log_fp, " %s", words[i]);
+        }
+        fprintf(qtest_log_fp, "\n");
+    }
+
+    g_assert(command);
+    if (strcmp(words[0], "outb") == 0 ||
+        strcmp(words[0], "outw") == 0 ||
+        strcmp(words[0], "outl") == 0) {
+        uint16_t addr;
+        uint32_t value;
+
+        g_assert(words[1] && words[2]);
+        addr = strtol(words[1], NULL, 0);
+        value = strtol(words[2], NULL, 0);
+
+        if (words[0][3] == 'b') {
+            cpu_outb(addr, value);
+        } else if (words[0][3] == 'w') {
+            cpu_outw(addr, value);
+        } else if (words[0][3] == 'l') {
+            cpu_outl(addr, value);
+        }
+        qtest_send_prefix(chr);
+        qtest_send(chr, "OK\n");
+    } else if (strcmp(words[0], "inb") == 0 ||
+        strcmp(words[0], "inw") == 0 ||
+        strcmp(words[0], "inl") == 0) {
+        uint16_t addr;
+        uint32_t value = -1U;
+
+        g_assert(words[1]);
+        addr = strtol(words[1], NULL, 0);
+
+        if (words[0][2] == 'b') {
+            value = cpu_inb(addr);
+        } else if (words[0][2] == 'w') {
+            value = cpu_inw(addr);
+        } else if (words[0][2] == 'l') {
+            value = cpu_inl(addr);
+        }
+        qtest_send_prefix(chr);
+        qtest_send(chr, "OK 0x%04x\n", value);
+    } else if (strcmp(words[0], "read") == 0) {
+        uint64_t addr, len, i;
+        uint8_t *data;
+
+        g_assert(words[1] && words[2]);
+        addr = strtoul(words[1], NULL, 0);
+        len = strtoul(words[2], NULL, 0);
+
+        data = g_malloc(len);
+        cpu_physical_memory_read(addr, data, len);
+
+        qtest_send_prefix(chr);
+        qtest_send(chr, "OK 0x");
+        for (i = 0; i < len; i++) {
+            qtest_send(chr, "%02x", data[i]);
+        }
+        qtest_send(chr, "\n");
+
+        g_free(data);
+    } else if (strcmp(words[0], "write") == 0) {
+        uint64_t addr, len, i;
+        uint8_t *data;
+        size_t data_len;
+
+        g_assert(words[1] && words[2] && words[3]);
+        addr = strtoul(words[1], NULL, 0);
+        len = strtoul(words[2], NULL, 0);
+
+        data_len = strlen(words[3]);
+        if (data_len < 3) {
+            qtest_send(chr, "ERR invalid argument size\n");
+            return;
+        }
+
+        data = g_malloc(len);
+        for (i = 0; i < len; i++) {
+            if ((i * 2 + 4) <= data_len) {
+                data[i] = hex2nib(words[3][i * 2 + 2]) << 4;
+                data[i] |= hex2nib(words[3][i * 2 + 3]);
+            } else {
+                data[i] = 0;
+            }
+        }
+        cpu_physical_memory_write(addr, data, len);
+        g_free(data);
+
+        qtest_send_prefix(chr);
+        qtest_send(chr, "OK\n");
+    } else {
+        fprintf(stderr, "Unknown command `%s'\n", words[0]);
+    }
+}
+
+static void qtest_process_inbuf(CharDriverState *chr, GString *inbuf)
+{
+    char *end;
+
+    while ((end = strchr(inbuf->str, '\n')) != NULL) {
+        size_t offset;
+        GString *cmd;
+        gchar **words;
+
+        offset = end - inbuf->str;
+
+        cmd = g_string_new_len(inbuf->str, offset);
+        g_string_erase(inbuf, 0, offset + 1);
+
+        words = g_strsplit(cmd->str, " ", 0);
+        qtest_process_command(chr, words);
+        g_strfreev(words);
+
+        g_string_free(cmd, TRUE);
+    }
+}
+
+static void qtest_read(void *opaque, const uint8_t *buf, int size)
+{
+    CharDriverState *chr = opaque;
+
+    g_string_append_len(inbuf, (const gchar *)buf, size);
+    qtest_process_inbuf(chr, inbuf);
+}
+
+static int qtest_can_read(void *opaque)
+{
+    return 1024;
+}
+
+static void qtest_event(void *opaque, int event)
+{
+    int i;
+
+    switch (event) {
+    case CHR_EVENT_OPENED:
+        qemu_system_reset(false);
+        for (i = 0; i < ARRAY_SIZE(irq_levels); i++) {
+            irq_levels[i] = 0;
+        }
+        gettimeofday(&start_time, NULL);
+        qtest_opened = true;
+        if (qtest_log_fp) {
+            fprintf(qtest_log_fp, "[I " FMT_timeval "] OPENED\n",
+                    start_time.tv_sec, start_time.tv_usec);
+        }
+        break;
+    case CHR_EVENT_CLOSED:
+        qtest_opened = false;
+        if (qtest_log_fp) {
+            struct timeval tv;
+            qtest_get_time(&tv);
+            fprintf(qtest_log_fp, "[I +" FMT_timeval "] CLOSED\n",
+                    tv.tv_sec, tv.tv_usec);
+        }
+        break;
+    default:
+        break;
+    }
+}
+
+static void qtest_set_irq(void *opaque, int irq, int level)
+{
+    CharDriverState *chr = qtest_chr;
+    bool changed;
+
+    changed = (irq_levels[irq] != level);
+    irq_levels[irq] = level;
+
+    if (changed) {
+        qtest_send_prefix(chr);
+        qtest_send(chr, "IRQ %s %d\n",
+                   level ? "raise" : "lower", irq);
+    }
+}
+
+qemu_irq *qtest_interrupt_controller_init(void)
+{
+    return qemu_allocate_irqs(qtest_set_irq, NULL, MAX_IRQ);
+}
+
+int qtest_init(void)
+{
+    CharDriverState *chr;
+
+    g_assert(qtest_chrdev != NULL);
+
+    chr = qemu_chr_new("qtest", qtest_chrdev, NULL);
+
+    qemu_chr_add_handlers(chr, qtest_can_read, qtest_read, qtest_event, chr);
+
+    inbuf = g_string_new("");
+
+    if (qtest_log) {
+        if (strcmp(qtest_log, "none") != 0) {
+            qtest_log_fp = fopen(qtest_log, "w+");
+        }
+    } else {
+        qtest_log_fp = stderr;
+    }
+
+    qtest_chr = chr;
+
+    return 0;
+}
diff --git a/qtest.h b/qtest.h
new file mode 100644
index 0000000..f0e1377
--- /dev/null
+++ b/qtest.h
@@ -0,0 +1,37 @@
+/*
+ * Test Server
+ *
+ * Copyright IBM, Corp. 2011
+ *
+ * Authors:
+ *  Anthony Liguori   <aliguori@us.ibm.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.
+ *
+ */
+
+#ifndef QTEST_H
+#define QTEST_H
+
+#include "qemu-common.h"
+
+extern int qtest_allowed;
+extern const char *qtest_chrdev;
+extern const char *qtest_log;
+
+static inline bool qtest_enabled(void)
+{
+    return qtest_allowed;
+}
+
+static inline int qtest_available(void)
+{
+    return 1;
+}
+
+int qtest_init(void);
+
+qemu_irq *qtest_interrupt_controller_init(void);
+
+#endif
diff --git a/vl.c b/vl.c
index ba55b35..58fb5d9 100644
--- a/vl.c
+++ b/vl.c
@@ -152,6 +152,7 @@ int main(int argc, char **argv)
 #ifdef CONFIG_VIRTFS
 #include "fsdev/qemu-fsdev.h"
 #endif
+#include "qtest.h"
 
 #include "disas.h"
 
@@ -1988,6 +1989,7 @@ static struct {
     { "tcg", "tcg", tcg_available, tcg_init, &tcg_allowed },
     { "xen", "Xen", xen_available, xen_init, &xen_allowed },
     { "kvm", "KVM", kvm_available, kvm_init, &kvm_allowed },
+    { "qtest", "QTest", qtest_available, qtest_init, &qtest_allowed },
 };
 
 static int configure_accelerator(void)
@@ -3053,6 +3055,12 @@ int main(int argc, char **argv, char **envp)
                     fclose(fp);
                     break;
                 }
+            case QEMU_OPTION_qtest:
+                qtest_chrdev = optarg;
+                break;
+            case QEMU_OPTION_qtest_log:
+                qtest_log = optarg;
+                break;
             default:
                 os_parse_cmd_args(popt->index, optarg);
             }
-- 
1.7.4.1

^ permalink raw reply related	[flat|nested] 25+ messages in thread

* [Qemu-devel] [PATCH 2/6] qtest: add support for -M pc
  2012-01-13 18:32 [Qemu-devel] [PATCH 1/6] qtest: add test framework Anthony Liguori
@ 2012-01-13 18:32 ` Anthony Liguori
  2012-01-13 18:32 ` [Qemu-devel] [PATCH 3/6] qtest: add C version of test infrastructure Anthony Liguori
                   ` (4 subsequent siblings)
  5 siblings, 0 replies; 25+ messages in thread
From: Anthony Liguori @ 2012-01-13 18:32 UTC (permalink / raw)
  To: qemu-devel; +Cc: Kevin Wolf, Paolo Bonzini, Anthony Liguori, Stefan Hajnoczi

This involves replacing the local APIC with the qtest interrupt controller.

It should be pretty straight forward to do the same for other machine types.

Signed-off-by: Anthony Liguori <aliguori@us.ibm.com>
---
 hw/pc_piix.c |    9 ++++++---
 1 files changed, 6 insertions(+), 3 deletions(-)

diff --git a/hw/pc_piix.c b/hw/pc_piix.c
index b70431f..2aba89c 100644
--- a/hw/pc_piix.c
+++ b/hw/pc_piix.c
@@ -46,6 +46,7 @@
 #ifdef CONFIG_XEN
 #  include <xen/hvm/hvm_info_table.h>
 #endif
+#include "qtest.h"
 
 #define MAX_IDE_BUS 2
 
@@ -154,11 +155,13 @@ static void pc_init1(MemoryRegion *system_memory,
     }
     isa_bus_irqs(isa_bus, gsi);
 
-    if (!xen_enabled()) {
+    if (xen_enabled()) {
+        i8259 = xen_interrupt_controller_init();
+    } else if (qtest_enabled()) {
+        i8259 = qtest_interrupt_controller_init();
+    } else {
         cpu_irq = pc_allocate_cpu_irq();
         i8259 = i8259_init(isa_bus, cpu_irq[0]);
-    } else {
-        i8259 = xen_interrupt_controller_init();
     }
 
     for (i = 0; i < ISA_NUM_IRQS; i++) {
-- 
1.7.4.1

^ permalink raw reply related	[flat|nested] 25+ messages in thread

* [Qemu-devel] [PATCH 3/6] qtest: add C version of test infrastructure
  2012-01-13 18:32 [Qemu-devel] [PATCH 1/6] qtest: add test framework Anthony Liguori
  2012-01-13 18:32 ` [Qemu-devel] [PATCH 2/6] qtest: add support for -M pc Anthony Liguori
@ 2012-01-13 18:32 ` Anthony Liguori
  2012-01-17 11:33   ` Stefan Hajnoczi
                     ` (2 more replies)
  2012-01-13 18:32 ` [Qemu-devel] [PATCH 4/6] make: add check targets based on gtester Anthony Liguori
                   ` (3 subsequent siblings)
  5 siblings, 3 replies; 25+ messages in thread
From: Anthony Liguori @ 2012-01-13 18:32 UTC (permalink / raw)
  To: qemu-devel; +Cc: Kevin Wolf, Paolo Bonzini, Anthony Liguori, Stefan Hajnoczi

This also includes a qtest wrapper script to make it easier to launch qtest
tests directly.

Signed-off-by: Anthony Liguori <aliguori@us.ibm.com>
---
 scripts/qtest    |    5 +
 tests/Makefile   |    2 +
 tests/libqtest.c |  334 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
 tests/libqtest.h |   63 ++++++++++
 4 files changed, 404 insertions(+), 0 deletions(-)
 create mode 100755 scripts/qtest
 create mode 100644 tests/libqtest.c
 create mode 100644 tests/libqtest.h

diff --git a/scripts/qtest b/scripts/qtest
new file mode 100755
index 0000000..5cff3d4
--- /dev/null
+++ b/scripts/qtest
@@ -0,0 +1,5 @@
+#!/bin/sh
+
+export QTEST_QEMU_BINARY=$1
+shift
+eval "$@"
diff --git a/tests/Makefile b/tests/Makefile
index efde63a..92d462a 100644
--- a/tests/Makefile
+++ b/tests/Makefile
@@ -34,6 +34,8 @@ test-qmp-input-visitor: test-qmp-input-visitor.o $(qobject-obj-y) $(qapi-obj-y)
 test-qmp-commands.o: $(addprefix $(qapi-dir)/, test-qapi-types.c test-qapi-types.h test-qapi-visit.c test-qapi-visit.h test-qmp-marshal.c test-qmp-commands.h) $(qapi-obj-y)
 test-qmp-commands: test-qmp-commands.o $(qobject-obj-y) $(qapi-obj-y) $(tools-obj-y) $(qapi-dir)/test-qapi-visit.o $(qapi-dir)/test-qapi-types.o $(qapi-dir)/test-qmp-marshal.o module.o
 
+tests/rtc-test: tests/rtc-test.o tests/libqtest.o
+
 .PHONY: check
 check: $(CHECKS)
 	gtester $(CHECKS)
diff --git a/tests/libqtest.c b/tests/libqtest.c
new file mode 100644
index 0000000..dd07b07
--- /dev/null
+++ b/tests/libqtest.c
@@ -0,0 +1,334 @@
+/*
+ * QTest
+ *
+ * Copyright IBM, Corp. 2012
+ *
+ * Authors:
+ *  Anthony Liguori   <aliguori@us.ibm.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 "libqtest.h"
+
+#include <glib.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/wait.h>
+#include <sys/un.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+
+#define MAX_IRQ 256
+
+QTestState *global_qtest;
+
+struct QTestState
+{
+    int fd;
+    bool irq_level[MAX_IRQ];
+    GString *rx;
+    gchar *pid_file;
+};
+
+#define g_assert_no_errno(ret) do { \
+    g_assert_cmpint(ret, !=, -1); \
+} while (0)
+
+QTestState *qtest_init(const char *extra_args)
+{
+    QTestState *s;
+    struct sockaddr_un addr;
+    int sock, ret, i;
+    gchar *socket_path;
+    gchar *pid_file;
+    gchar *command;
+    const char *qemu_binary;
+    pid_t pid;
+
+    qemu_binary = getenv("QTEST_QEMU_BINARY");
+    g_assert(qemu_binary != NULL);
+
+    socket_path = g_strdup_printf("/tmp/qtest-%d.sock", getpid());
+    pid_file = g_strdup_printf("/tmp/qtest-%d.pid", getpid());
+
+    s = g_malloc(sizeof(*s));
+
+    sock = socket(PF_UNIX, SOCK_STREAM, 0);
+    g_assert_no_errno(sock);
+
+    addr.sun_family = AF_UNIX;
+    snprintf(addr.sun_path, sizeof(addr.sun_path), "%s", socket_path);
+
+    pid = fork();
+    if (pid == 0) {
+        command = g_strdup_printf("%s "
+                                  "-qtest unix:%s,server,nowait "
+                                  "-qtest-log /dev/null "
+                                  "-pidfile %s "
+                                  "-machine accel=qtest "
+                                  "%s", qemu_binary, socket_path,
+                                  pid_file,
+                                  extra_args ?: "");
+
+        ret = system(command);
+        exit(ret);
+        g_free(command);
+    }
+
+    do {
+        sleep(1);
+        ret = connect(sock, (struct sockaddr *)&addr, sizeof(addr));
+    } while (ret == -1);
+    g_assert_no_errno(ret);
+
+    s->fd = sock;
+    s->rx = g_string_new("");
+    s->pid_file = pid_file;
+    for (i = 0; i < MAX_IRQ; i++) {
+        s->irq_level[i] = false;
+    }
+
+    g_free(socket_path);
+
+    return s;
+}
+
+void qtest_quit(QTestState *s)
+{
+    FILE *f;
+    char buffer[1024];
+
+    f = fopen(s->pid_file, "r");
+    if (f) {
+        if (fgets(buffer, sizeof(buffer), f)) {
+            pid_t pid = atoi(buffer);
+            int status = 0;
+
+            kill(pid, SIGTERM);
+            waitpid(pid, &status, 0);
+        }
+
+        fclose(f);
+    }
+}
+
+static void qtest_sendf(QTestState *s, const char *fmt, ...)
+{
+    va_list ap;
+    gchar *str;
+    size_t size, offset;
+
+    va_start(ap, fmt);
+    str = g_strdup_vprintf(fmt, ap);
+    va_end(ap);
+    size = strlen(str);
+
+    offset = 0;
+    while (offset < size) {
+        ssize_t len;
+
+        len = write(s->fd, str + offset, size - offset);
+        if (len == -1 && errno == EINTR) {
+            continue;
+        }
+
+        g_assert_no_errno(len);
+        g_assert_cmpint(len, >, 0);
+
+        offset += len;
+    }
+}
+
+static GString *qtest_recv_line(QTestState *s)
+{
+    GString *line;
+    size_t offset;
+    char *eol;
+
+    while ((eol = strchr(s->rx->str, '\n')) == NULL) {
+        ssize_t len;
+        char buffer[1024];
+
+        len = read(s->fd, buffer, sizeof(buffer));
+        if (len == -1 && errno == EINTR) {
+            continue;
+        }
+
+        if (len == -1 || len == 0) {
+            fprintf(stderr, "Broken pipe\n");
+            exit(1);
+        }
+
+        g_string_append_len(s->rx, buffer, len);
+    }
+
+    offset = eol - s->rx->str;
+    line = g_string_new_len(s->rx->str, offset);
+    g_string_erase(s->rx, 0, offset + 1);
+
+    return line;
+}
+
+static gchar **qtest_rsp(QTestState *s, int expected_args)
+{
+    GString *line;
+    gchar **words;
+    int i;
+
+redo:
+    line = qtest_recv_line(s);
+    words = g_strsplit(line->str, " ", 0);
+    g_string_free(line, TRUE);
+
+    if (strcmp(words[0], "IRQ") == 0) {
+        int irq;
+
+        g_assert(words[1] != NULL);
+        g_assert(words[2] != NULL);
+
+        irq = strtoul(words[2], NULL, 0);
+        g_assert_cmpint(irq, >=, 0);
+        g_assert_cmpint(irq, <, MAX_IRQ);
+
+        if (strcmp(words[1], "raise") == 0) {
+            s->irq_level[irq] = true;
+        } else {
+            s->irq_level[irq] = false;
+        }
+
+        g_strfreev(words);
+        goto redo;
+    }
+
+    g_assert(words[0] != NULL);
+    g_assert_cmpstr(words[0], ==, "OK");
+
+    if (expected_args) {
+        for (i = 0; i < expected_args; i++) {
+            g_assert(words[i] != NULL);
+        }
+    } else {
+        g_strfreev(words);
+    }
+    
+    return words;
+}
+
+const char *qtest_get_arch(void)
+{
+    const char *qemu = getenv("QTEST_QEMU_BINARY");
+    const char *end = strrchr(qemu, '/');
+
+    return end + strlen("/qemu-system-");
+}
+
+bool qtest_get_irq(QTestState *s, int num)
+{
+    /* dummy operation in order to make sure irq is up to date */
+    qtest_inb(s, 0);
+
+    return s->irq_level[num];
+}
+
+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);
+    qtest_rsp(s, 0);
+}
+
+void qtest_outb(QTestState *s, uint16_t addr, uint8_t value)
+{
+    qtest_out(s, "outb", addr, value);
+}
+
+void qtest_outw(QTestState *s, uint16_t addr, uint16_t value)
+{
+    qtest_out(s, "outw", addr, value);
+}
+
+void qtest_outl(QTestState *s, uint16_t addr, uint32_t value)
+{
+    qtest_out(s, "outl", addr, value);
+}
+
+static uint32_t qtest_in(QTestState *s, const char *cmd, uint16_t addr)
+{
+    gchar **args;
+    uint32_t value;
+
+    qtest_sendf(s, "%s 0x%x\n", cmd, addr);
+    args = qtest_rsp(s, 2);
+    value = strtoul(args[1], NULL, 0);
+    g_strfreev(args);
+
+    return value;
+}
+
+uint8_t qtest_inb(QTestState *s, uint16_t addr)
+{
+    return qtest_in(s, "inb", addr);
+}
+
+uint16_t qtest_inw(QTestState *s, uint16_t addr)
+{
+    return qtest_in(s, "inw", addr);
+}
+
+uint32_t qtest_inl(QTestState *s, uint16_t addr)
+{
+    return qtest_in(s, "inl", addr);
+}
+
+static int hex2nib(char ch)
+{
+    if (ch >= '0' && ch <= '9') {
+        return ch - '0';
+    } else if (ch >= 'a' && ch <= 'f') {
+        return 10 + (ch - 'a');
+    } else if (ch >= 'A' && ch <= 'F') {
+        return 10 + (ch - 'a');
+    } else {
+        return -1;
+    }
+}
+
+void qtest_memread(QTestState *s, uint64_t addr, void *data, size_t size)
+{
+    uint8_t *ptr = data;
+    gchar **args;
+    size_t i;
+
+    qtest_sendf(s, "read 0x%x 0x%x\n", addr, size);
+    args = qtest_rsp(s, 2);
+
+    for (i = 0; i < size; i++) {
+        ptr[i] = hex2nib(args[1][2 + (i * 2)]) << 4;
+        ptr[i] |= hex2nib(args[1][2 + (i * 2) + 1]);
+    }
+
+    g_strfreev(args);
+}
+
+void qtest_add_func(const char *str, void (*fn))
+{
+    gchar *path = g_strdup_printf("/%s/%s", qtest_get_arch(), str);
+    g_test_add_func(path, fn);
+}
+
+void qtest_memwrite(QTestState *s, uint64_t addr, const void *data, size_t size)
+{
+    const uint8_t *ptr = data;
+    size_t i;
+
+    qtest_sendf(s, "write 0x%x 0x%x 0x", addr, size);
+    for (i = 0; i < size; i++) {
+        qtest_sendf(s, "%02x", ptr[i]);
+    }
+    qtest_sendf(s, "\n");
+    qtest_rsp(s, 0);
+}
diff --git a/tests/libqtest.h b/tests/libqtest.h
new file mode 100644
index 0000000..dd82926
--- /dev/null
+++ b/tests/libqtest.h
@@ -0,0 +1,63 @@
+/*
+ * QTest
+ *
+ * Copyright IBM, Corp. 2012
+ *
+ * Authors:
+ *  Anthony Liguori   <aliguori@us.ibm.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.
+ *
+ */
+#ifndef LIBQTEST_H
+#define LIBQTEST_H
+
+#include <stdint.h>
+#include <stdbool.h>
+#include <sys/types.h>
+
+typedef struct QTestState QTestState;
+
+extern QTestState *global_qtest;
+
+QTestState *qtest_init(const char *extra_args);
+void qtest_quit(QTestState *s);
+
+bool qtest_get_irq(QTestState *s, int num);
+
+void qtest_outb(QTestState *s, uint16_t addr, uint8_t value);
+
+void qtest_outw(QTestState *s, uint16_t addr, uint16_t value);
+
+void qtest_outl(QTestState *s, uint16_t addr, uint32_t value);
+
+uint8_t qtest_inb(QTestState *s, uint16_t addr);
+
+uint16_t qtest_inw(QTestState *s, uint16_t addr);
+
+uint32_t qtest_inl(QTestState *s, uint16_t addr);
+
+void qtest_memread(QTestState *s, uint64_t addr, void *data, size_t size);
+
+void qtest_memwrite(QTestState *s, uint64_t addr, const void *data, size_t size);
+
+const char *qtest_get_arch(void);
+
+void qtest_add_func(const char *str, void (*fn));
+
+#define qtest_start(args) (            \
+    global_qtest = qtest_init((args)) \
+        )
+
+#define get_irq(num) qtest_get_irq(global_qtest, (num))
+#define outb(addr, val) qtest_outb(global_qtest, (addr), (val))
+#define outw(addr, val) qtest_outw(global_qtest, (addr), (val))
+#define outl(addr, val) qtest_outl(global_qtest, (addr), (val))
+#define inb(addr) qtest_inb(global_qtest, (addr))
+#define inw(addr) qtest_inw(global_qtest, (addr))
+#define inl(addr) qtest_inl(global_qtest, (addr))
+#define memread(addr, data, size) qtest_memread(global_qtest, (addr), (data), (size))
+#define memwrite(addr, data, size) qtest_memwrite(global_qtest, (addr), (data), (size))
+
+#endif
-- 
1.7.4.1

^ permalink raw reply related	[flat|nested] 25+ messages in thread

* [Qemu-devel] [PATCH 4/6] make: add check targets based on gtester
  2012-01-13 18:32 [Qemu-devel] [PATCH 1/6] qtest: add test framework Anthony Liguori
  2012-01-13 18:32 ` [Qemu-devel] [PATCH 2/6] qtest: add support for -M pc Anthony Liguori
  2012-01-13 18:32 ` [Qemu-devel] [PATCH 3/6] qtest: add C version of test infrastructure Anthony Liguori
@ 2012-01-13 18:32 ` Anthony Liguori
  2012-01-16 17:16   ` Paolo Bonzini
  2012-01-17 14:04   ` Paolo Bonzini
  2012-01-13 18:32 ` [Qemu-devel] [PATCH 5/6] rtc: split out macros into a header file and use in test case Anthony Liguori
                   ` (2 subsequent siblings)
  5 siblings, 2 replies; 25+ messages in thread
From: Anthony Liguori @ 2012-01-13 18:32 UTC (permalink / raw)
  To: qemu-devel; +Cc: Kevin Wolf, Paolo Bonzini, Anthony Liguori, Stefan Hajnoczi

This will run all tests through gtester.  The main targets are:

$ make check

Which will run each unit test and:

$ make check-report.html

Which will generate a nice HTML report of the test status.

Signed-off-by: Anthony Liguori <aliguori@us.ibm.com>
---
 scripts/gtester-cat |   32 ++++++++++++++++++++++++++++
 tests/Makefile      |   58 ++++++++++++++++++++++++++++++++++++++++++++++++--
 2 files changed, 87 insertions(+), 3 deletions(-)
 create mode 100755 scripts/gtester-cat

diff --git a/scripts/gtester-cat b/scripts/gtester-cat
new file mode 100755
index 0000000..afd8c3e
--- /dev/null
+++ b/scripts/gtester-cat
@@ -0,0 +1,32 @@
+#!/bin/sh
+#
+# Copyright IBM, Corp. 2012
+#
+# Authors:
+#  Anthony Liguori <aliguori@us.ibm.com>
+#
+# This work is licensed under the terms of the GNU GPLv2 or later.
+# See the COPYING file in the top-level directory.
+
+cat <<EOF
+<?xml version="1.0"?>
+<gtester>
+EOF
+
+for file in "$@"; do
+    first="yes"
+    cat $file | while read LINE; do
+	if test "$first" = "yes"; then
+	    first="no"
+	    continue
+	fi
+	if test "$LINE" = "<gtester>" -o "$LINE" = "</gtester>"; then
+	    continue
+	fi
+	echo $LINE
+    done
+done
+
+cat<<EOF
+</gtester>
+EOF
diff --git a/tests/Makefile b/tests/Makefile
index 92d462a..9db8553 100644
--- a/tests/Makefile
+++ b/tests/Makefile
@@ -2,6 +2,10 @@ CHECKS = check-qdict check-qfloat check-qint check-qstring check-qlist
 CHECKS += check-qjson test-qmp-output-visitor test-qmp-input-visitor
 CHECKS += test-coroutine
 
+HW_TESTS=
+
+TARGETS=$(patsubst %-softmmu,%, $(filter %-softmmu,$(TARGET_DIRS)))
+
 check-qint.o check-qstring.o check-qdict.o check-qlist.o check-qfloat.o check-qjson.o test-coroutine.o: $(GENERATED_HEADERS)
 
 check-qint: check-qint.o qint.o $(tools-obj-y)
@@ -36,6 +40,54 @@ test-qmp-commands: test-qmp-commands.o $(qobject-obj-y) $(qapi-obj-y) $(tools-ob
 
 tests/rtc-test: tests/rtc-test.o tests/libqtest.o
 
-.PHONY: check
-check: $(CHECKS)
-	gtester $(CHECKS)
+check-help:
+	@echo "Regression targets:"
+	@echo
+	@echo " make check                Run all tests"
+	@echo " make check-qtest          Run qtest tests"
+	@echo " make check-unit           Run qobject tests"
+	@echo " make check-report.html    Generates an HTML test report"
+	@echo
+	@echo "Please note that HTML reports do not regenerate if the unit tests"
+	@echo "has not changed."
+	@echo
+	@echo "The variable SPEED can be set to control the gtester speed setting"
+
+.SECONDARY:
+
+SPEED ?= quick
+
+# Reports
+check-report-qtest-%.log: $(HW_TESTS)
+	$(call quiet-command,QTEST_QEMU_BINARY=`basename $@ .log | cut -f4 -d-`-softmmu/qemu-system-`basename $@ .log | cut -f4 -d-` \
+	  gtester -k -q -o $@ -m=$(SPEED) $(HW_TESTS),"  TEST   $^ (`basename $@ .log | cut -f4 -d-`)")
+
+check-report-unit.log: $(CHECKS)
+	$(call quiet-command,gtester -k -q -m=$(SPEED) -o $@ $^, "  TEST   $^")
+
+check-report.log: check-report-unit.log $(patsubst %,check-report-qtest-%.log, $(TARGETS))
+	$(call quiet-command,$(SRC_PATH)/scripts/gtester-cat $^ > $@, "  GEN    $@")
+
+check-report.html: check-report.log
+	$(call quiet-command,gtester-report $< > $@, "  GEN    $@")
+
+# Check tests
+
+check-qtest-%: $(HW_TESTS)
+	@for test in $^; do \
+	    arch=`echo $@ | cut -f3- -d-`; \
+	    echo "Running '$$test' with qemu-system-$$arch..."; \
+	    $(SRC_PATH)/scripts/qtest $$arch-softmmu/qemu-system-$$arch $$test || exit $?; \
+	done
+
+check-qtest: $(patsubst %,check-qtest-%, $(TARGETS))
+
+check-unit: $(CHECKS)
+	@for test in $^; do \
+	    echo "Running '$$test'..."; \
+	    ./$$test || exit $?; \
+	done
+
+check: check-unit check-qtest
+
+.PHONY: check-help check-qtest check-unit check
-- 
1.7.4.1

^ permalink raw reply related	[flat|nested] 25+ messages in thread

* [Qemu-devel] [PATCH 5/6] rtc: split out macros into a header file and use in test case
  2012-01-13 18:32 [Qemu-devel] [PATCH 1/6] qtest: add test framework Anthony Liguori
                   ` (2 preceding siblings ...)
  2012-01-13 18:32 ` [Qemu-devel] [PATCH 4/6] make: add check targets based on gtester Anthony Liguori
@ 2012-01-13 18:32 ` Anthony Liguori
  2012-01-13 18:32 ` [Qemu-devel] [PATCH 6/6] qtest: add rtc-test test-case Anthony Liguori
  2012-01-16 16:59 ` [Qemu-devel] [PATCH 1/6] qtest: add test framework Stefan Hajnoczi
  5 siblings, 0 replies; 25+ messages in thread
From: Anthony Liguori @ 2012-01-13 18:32 UTC (permalink / raw)
  To: qemu-devel; +Cc: Kevin Wolf, Paolo Bonzini, Anthony Liguori, Stefan Hajnoczi

Signed-off-by: Anthony Liguori <aliguori@us.ibm.com>
---
 hw/mc146818rtc.c      |   33 --------------------------
 hw/mc146818rtc.h      |    3 +-
 hw/mc146818rtc_regs.h |   62 +++++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 63 insertions(+), 35 deletions(-)
 create mode 100644 hw/mc146818rtc_regs.h

diff --git a/hw/mc146818rtc.c b/hw/mc146818rtc.c
index 657fa10..258d3a8 100644
--- a/hw/mc146818rtc.c
+++ b/hw/mc146818rtc.c
@@ -46,39 +46,6 @@
 
 #define RTC_REINJECT_ON_ACK_COUNT 20
 
-#define RTC_SECONDS             0
-#define RTC_SECONDS_ALARM       1
-#define RTC_MINUTES             2
-#define RTC_MINUTES_ALARM       3
-#define RTC_HOURS               4
-#define RTC_HOURS_ALARM         5
-#define RTC_ALARM_DONT_CARE    0xC0
-
-#define RTC_DAY_OF_WEEK         6
-#define RTC_DAY_OF_MONTH        7
-#define RTC_MONTH               8
-#define RTC_YEAR                9
-
-#define RTC_REG_A               10
-#define RTC_REG_B               11
-#define RTC_REG_C               12
-#define RTC_REG_D               13
-
-#define REG_A_UIP 0x80
-
-#define REG_B_SET  0x80
-#define REG_B_PIE  0x40
-#define REG_B_AIE  0x20
-#define REG_B_UIE  0x10
-#define REG_B_SQWE 0x08
-#define REG_B_DM   0x04
-#define REG_B_24H  0x02
-
-#define REG_C_UF   0x10
-#define REG_C_IRQF 0x80
-#define REG_C_PF   0x40
-#define REG_C_AF   0x20
-
 typedef struct RTCState {
     ISADevice dev;
     MemoryRegion io;
diff --git a/hw/mc146818rtc.h b/hw/mc146818rtc.h
index f119930..f286b6a 100644
--- a/hw/mc146818rtc.h
+++ b/hw/mc146818rtc.h
@@ -2,8 +2,7 @@
 #define MC146818RTC_H
 
 #include "isa.h"
-
-#define RTC_ISA_IRQ 8
+#include "mc146818rtc_regs.h"
 
 ISADevice *rtc_init(ISABus *bus, int base_year, qemu_irq intercept_irq);
 void rtc_set_memory(ISADevice *dev, int addr, int val);
diff --git a/hw/mc146818rtc_regs.h b/hw/mc146818rtc_regs.h
new file mode 100644
index 0000000..3ab3770
--- /dev/null
+++ b/hw/mc146818rtc_regs.h
@@ -0,0 +1,62 @@
+/*
+ * QEMU MC146818 RTC emulation
+ *
+ * Copyright (c) 2003-2004 Fabrice Bellard
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+#ifndef RTC_REGS_H
+#define RTC_REGS_H
+
+#define RTC_ISA_IRQ 8
+
+#define RTC_SECONDS             0
+#define RTC_SECONDS_ALARM       1
+#define RTC_MINUTES             2
+#define RTC_MINUTES_ALARM       3
+#define RTC_HOURS               4
+#define RTC_HOURS_ALARM         5
+#define RTC_ALARM_DONT_CARE    0xC0
+
+#define RTC_DAY_OF_WEEK         6
+#define RTC_DAY_OF_MONTH        7
+#define RTC_MONTH               8
+#define RTC_YEAR                9
+
+#define RTC_REG_A               10
+#define RTC_REG_B               11
+#define RTC_REG_C               12
+#define RTC_REG_D               13
+
+#define REG_A_UIP 0x80
+
+#define REG_B_SET  0x80
+#define REG_B_PIE  0x40
+#define REG_B_AIE  0x20
+#define REG_B_UIE  0x10
+#define REG_B_SQWE 0x08
+#define REG_B_DM   0x04
+#define REG_B_24H  0x02
+
+#define REG_C_UF   0x10
+#define REG_C_IRQF 0x80
+#define REG_C_PF   0x40
+#define REG_C_AF   0x20
+
+#endif
-- 
1.7.4.1

^ permalink raw reply related	[flat|nested] 25+ messages in thread

* [Qemu-devel] [PATCH 6/6] qtest: add rtc-test test-case
  2012-01-13 18:32 [Qemu-devel] [PATCH 1/6] qtest: add test framework Anthony Liguori
                   ` (3 preceding siblings ...)
  2012-01-13 18:32 ` [Qemu-devel] [PATCH 5/6] rtc: split out macros into a header file and use in test case Anthony Liguori
@ 2012-01-13 18:32 ` Anthony Liguori
  2012-01-16 16:59 ` [Qemu-devel] [PATCH 1/6] qtest: add test framework Stefan Hajnoczi
  5 siblings, 0 replies; 25+ messages in thread
From: Anthony Liguori @ 2012-01-13 18:32 UTC (permalink / raw)
  To: qemu-devel; +Cc: Kevin Wolf, Paolo Bonzini, Anthony Liguori, Stefan Hajnoczi

Signed-off-by: Anthony Liguori <aliguori@us.ibm.com>
---
 tests/Makefile   |    2 +-
 tests/rtc-test.c |  281 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 282 insertions(+), 1 deletions(-)
 create mode 100644 tests/rtc-test.c

diff --git a/tests/Makefile b/tests/Makefile
index 9db8553..a6b993c 100644
--- a/tests/Makefile
+++ b/tests/Makefile
@@ -2,7 +2,7 @@ CHECKS = check-qdict check-qfloat check-qint check-qstring check-qlist
 CHECKS += check-qjson test-qmp-output-visitor test-qmp-input-visitor
 CHECKS += test-coroutine
 
-HW_TESTS=
+HW_TESTS=tests/rtc-test
 
 TARGETS=$(patsubst %-softmmu,%, $(filter %-softmmu,$(TARGET_DIRS)))
 
diff --git a/tests/rtc-test.c b/tests/rtc-test.c
new file mode 100644
index 0000000..7f0b590
--- /dev/null
+++ b/tests/rtc-test.c
@@ -0,0 +1,281 @@
+/*
+ * QTest
+ *
+ * Copyright IBM, Corp. 2012
+ *
+ * Authors:
+ *  Anthony Liguori   <aliguori@us.ibm.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 "libqtest.h"
+#include "hw/mc146818rtc_regs.h"
+
+#include <glib.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+static uint8_t base = 0x70;
+
+static int bcd2dec(int value)
+{
+    return (((value >> 4) & 0x0F) * 10) + (value & 0x0F);
+}
+
+static int dec2bcd(int value)
+{
+    return ((value / 10) << 4) | (value % 10);
+}
+
+static uint8_t cmos_read(uint8_t reg)
+{
+    outb(base + 0, reg);
+    return inb(base + 1);
+}
+
+static void cmos_write(uint8_t reg, uint8_t val)
+{
+    outb(base + 0, reg);
+    outb(base + 1, val);
+}
+
+static int tm_cmp(struct tm *lhs, struct tm *rhs)
+{
+    time_t a, b;
+    struct tm d1, d2;
+
+    memcpy(&d1, lhs, sizeof(d1));
+    memcpy(&d2, rhs, sizeof(d2));
+
+    a = mktime(&d1);
+    b = mktime(&d2);
+
+    if (a < b) {
+        return -1;
+    } else if (a > b) {
+        return 1;
+    }
+
+    return 0;
+}
+
+#if 0
+static void print_tm(struct tm *tm)
+{
+    printf("%04d-%02d-%02d %02d:%02d:%02d\n",
+           tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday, 
+           tm->tm_hour, tm->tm_min, tm->tm_sec, tm->tm_gmtoff);
+}
+#endif
+
+static void cmos_get_date_time(struct tm *date)
+{
+    int base_year = 2000, hour_offset;
+    int sec, min, hour, mday, mon, year;
+    time_t ts;
+    struct tm dummy;
+
+    sec = cmos_read(RTC_SECONDS);
+    min = cmos_read(RTC_MINUTES);
+    hour = cmos_read(RTC_HOURS);
+    mday = cmos_read(RTC_DAY_OF_MONTH);
+    mon = cmos_read(RTC_MONTH);
+    year = cmos_read(RTC_YEAR);
+
+    if ((cmos_read(RTC_REG_B) & REG_B_DM) == 0) {
+        sec = bcd2dec(sec);
+        min = bcd2dec(min);
+        hour = bcd2dec(hour);
+        mday = bcd2dec(mday);
+        mon = bcd2dec(mon);
+        year = bcd2dec(year);
+        hour_offset = 80;
+    } else {
+        hour_offset = 0x80;
+    }
+
+    if ((cmos_read(0x0B) & REG_B_24H) == 0) {
+        if (hour >= hour_offset) {
+            hour -= hour_offset;
+            hour += 12;
+        }
+    }
+
+    ts = time(NULL);
+    localtime_r(&ts, &dummy);
+
+    date->tm_isdst = dummy.tm_isdst;
+    date->tm_sec = sec;
+    date->tm_min = min;
+    date->tm_hour = hour;
+    date->tm_mday = mday;
+    date->tm_mon = mon - 1;
+    date->tm_year = base_year + year - 1900;
+    date->tm_gmtoff = 0;
+
+    ts = mktime(date);
+}
+
+static void check_time(int wiggle)
+{
+    struct tm start, date[4], end;
+    struct tm *datep;
+    time_t ts;
+
+    /*
+     * This check assumes a few things.  First, we cannot guarantee that we get
+     * a consistent reading from the wall clock because we may hit an edge of
+     * the clock while reading.  To work around this, we read four clock readings
+     * such that at least two of them should match.  We need to assume that one
+     * reading is corrupt so we need four readings to ensure that we have at
+     * least two consecutive identical readings
+     *
+     * It's also possible that we'll cross an edge reading the host clock so
+     * simply check to make sure that the clock reading is within the period of
+     * when we expect it to be.
+     */
+
+    ts = time(NULL);
+    gmtime_r(&ts, &start);
+
+    cmos_get_date_time(&date[0]);
+    cmos_get_date_time(&date[1]);
+    cmos_get_date_time(&date[2]);
+    cmos_get_date_time(&date[3]);
+
+    ts = time(NULL);
+    gmtime_r(&ts, &end);
+
+    if (tm_cmp(&date[0], &date[1]) == 0) {
+        datep = &date[0];
+    } else if (tm_cmp(&date[1], &date[2]) == 0) {
+        datep = &date[1];
+    } else if (tm_cmp(&date[2], &date[3]) == 0) {
+        datep = &date[2];
+    } else {
+        g_assert_not_reached();
+    }
+
+    if (!(tm_cmp(&start, datep) <= 0 && tm_cmp(datep, &end) <= 0)) {
+        time_t t, s;
+
+        start.tm_isdst = datep->tm_isdst;
+
+        t = mktime(datep);
+        s = mktime(&start);
+        if (t < s) {
+            g_test_message("RTC is %ld second(s) behind wall-clock\n", (s - t));
+        } else {
+            g_test_message("RTC is %ld second(s) ahead of wall-clock\n", (t - s));
+        }
+
+        g_assert_cmpint(ABS(t - s), <=, wiggle);
+    }
+}
+
+static int wiggle = 2;
+
+static void bcd_check_time(void)
+{
+    /* Set BCD mode */
+    cmos_write(RTC_REG_B, cmos_read(RTC_REG_B) & ~REG_B_DM);
+    check_time(wiggle);
+}
+
+static void dec_check_time(void)
+{
+    /* Set DEC mode */
+    cmos_write(RTC_REG_B, cmos_read(RTC_REG_B) | REG_B_DM);
+    check_time(wiggle);
+}
+
+static void set_alarm_time(struct tm *tm)
+{
+    int sec, min, hour;
+    int hour_offset;
+
+    sec = tm->tm_sec - 1;
+
+
+    if ((cmos_read(RTC_REG_B) & REG_B_DM) == 0) {
+        sec = dec2bcd(sec);
+        min = dec2bcd(min);
+        hour = dec2bcd(hour);
+        hour_offset = 80;
+    } else {
+        hour_offset = 0x80;
+    }
+
+    if ((cmos_read(RTC_REG_B) & REG_B_24H) != 0) {
+        if (hour >= 12) {
+            hour -= 12;
+            hour += hour_offset;
+        }
+    }
+
+    cmos_write(RTC_SECONDS_ALARM, sec);
+    cmos_write(RTC_MINUTES_ALARM, RTC_ALARM_DONT_CARE);
+    cmos_write(RTC_HOURS_ALARM, RTC_ALARM_DONT_CARE);
+}
+
+static void alarm_time(void)
+{
+    struct tm now;
+    time_t ts;
+    int i;
+
+    ts = time(NULL);
+    gmtime_r(&ts, &now);
+
+    /* set DEC mode */
+    cmos_write(RTC_REG_B, cmos_read(RTC_REG_B) | REG_B_DM);
+
+    g_assert(!get_irq(RTC_ISA_IRQ));
+
+    now.tm_sec += 1;
+    set_alarm_time(&now);
+    cmos_write(RTC_REG_B, cmos_read(RTC_REG_B) | REG_B_AIE);
+
+    for (i = 0; i < 1 + wiggle; i++) {
+        if (get_irq(RTC_ISA_IRQ)) {
+            break;
+        }
+
+        sleep(1);
+    }
+
+    g_assert(get_irq(RTC_ISA_IRQ));
+}
+
+int main(int argc, char **argv)
+{
+    const char *arch;
+    QTestState *s = NULL;
+    int ret;
+
+    g_test_init(&argc, &argv, NULL);
+
+    arch = qtest_get_arch();
+    /* These tests only work on i386 and x86_64 */
+    if (strcmp(arch, "i386") == 0 || strcmp(arch, "x86_64") == 0) {
+        s = qtest_start("-vnc none");
+
+        qtest_add_func("/rtc/bcd/check-time", bcd_check_time);
+        qtest_add_func("/rtc/dec/check-time", dec_check_time);
+        qtest_add_func("/rtc/alarm-time", alarm_time);
+    } else {
+        g_test_message("Skipping unsupported arch `%s'\n", arch);
+    }
+
+    ret = g_test_run();
+
+    if (s) {
+        qtest_quit(s);
+    }
+
+    return ret;
+}
-- 
1.7.4.1

^ permalink raw reply related	[flat|nested] 25+ messages in thread

* Re: [Qemu-devel] [PATCH 1/6] qtest: add test framework
  2012-01-13 18:32 [Qemu-devel] [PATCH 1/6] qtest: add test framework Anthony Liguori
                   ` (4 preceding siblings ...)
  2012-01-13 18:32 ` [Qemu-devel] [PATCH 6/6] qtest: add rtc-test test-case Anthony Liguori
@ 2012-01-16 16:59 ` Stefan Hajnoczi
  2012-01-16 17:08   ` Avi Kivity
  2012-01-16 17:08   ` Anthony Liguori
  5 siblings, 2 replies; 25+ messages in thread
From: Stefan Hajnoczi @ 2012-01-16 16:59 UTC (permalink / raw)
  To: Anthony Liguori
  Cc: Kevin Wolf, Paolo Bonzini, Avi Kivity, qemu-devel,
	Stefan Hajnoczi

On Fri, Jan 13, 2012 at 6:32 PM, Anthony Liguori <aliguori@us.ibm.com> wrote:
> +    if (strcmp(words[0], "outb") == 0 ||
> +        strcmp(words[0], "outw") == 0 ||
> +        strcmp(words[0], "outl") == 0) {
> +        uint16_t addr;
> +        uint32_t value;
> +
> +        g_assert(words[1] && words[2]);
> +        addr = strtol(words[1], NULL, 0);
> +        value = strtol(words[2], NULL, 0);
> +
> +        if (words[0][3] == 'b') {
> +            cpu_outb(addr, value);
> +        } else if (words[0][3] == 'w') {
> +            cpu_outw(addr, value);
> +        } else if (words[0][3] == 'l') {
> +            cpu_outl(addr, value);
> +        }
> +        qtest_send_prefix(chr);
> +        qtest_send(chr, "OK\n");
> +    } else if (strcmp(words[0], "inb") == 0 ||
> +        strcmp(words[0], "inw") == 0 ||
> +        strcmp(words[0], "inl") == 0) {
> +        uint16_t addr;
> +        uint32_t value = -1U;
> +
> +        g_assert(words[1]);
> +        addr = strtol(words[1], NULL, 0);
> +
> +        if (words[0][2] == 'b') {
> +            value = cpu_inb(addr);
> +        } else if (words[0][2] == 'w') {
> +            value = cpu_inw(addr);
> +        } else if (words[0][2] == 'l') {
> +            value = cpu_inl(addr);
> +        }
> +        qtest_send_prefix(chr);
> +        qtest_send(chr, "OK 0x%04x\n", value);

Endianness is a little weird here.  memory.c will byteswap if target
and device endianness differ.

Imagine the case where we're on an x86 host, running a ppc guest,
reading from PCI configuration space (little-endian).  Since ppc
(target endian) is big-endian and the device is little-endian the
value read/written will be byteswapped.  However, our qtest runs on
the host and therefore we don't want that automatic swap (or we need
to neutralize it by performing another byteswap on top).

Stefan

^ permalink raw reply	[flat|nested] 25+ messages in thread

* Re: [Qemu-devel] [PATCH 1/6] qtest: add test framework
  2012-01-16 16:59 ` [Qemu-devel] [PATCH 1/6] qtest: add test framework Stefan Hajnoczi
@ 2012-01-16 17:08   ` Avi Kivity
  2012-01-16 17:20     ` Anthony Liguori
  2012-01-16 17:08   ` Anthony Liguori
  1 sibling, 1 reply; 25+ messages in thread
From: Avi Kivity @ 2012-01-16 17:08 UTC (permalink / raw)
  To: Stefan Hajnoczi
  Cc: Kevin Wolf, Paolo Bonzini, Anthony Liguori, qemu-devel,
	Stefan Hajnoczi

On 01/16/2012 06:59 PM, Stefan Hajnoczi wrote:
> > +        }
> > +        qtest_send_prefix(chr);
> > +        qtest_send(chr, "OK 0x%04x\n", value);
>
> Endianness is a little weird here.  memory.c will byteswap if target
> and device endianness differ.
>
> Imagine the case where we're on an x86 host, running a ppc guest,
> reading from PCI configuration space (little-endian).  Since ppc
> (target endian) is big-endian and the device is little-endian the
> value read/written will be byteswapped.  However, our qtest runs on
> the host and therefore we don't want that automatic swap (or we need
> to neutralize it by performing another byteswap on top).
>

Good catch.  This is another example of how an access depends not only
on the destination, but also on the source.  Here the source is not the
cpu; it's qtest.

-- 
error compiling committee.c: too many arguments to function

^ permalink raw reply	[flat|nested] 25+ messages in thread

* Re: [Qemu-devel] [PATCH 1/6] qtest: add test framework
  2012-01-16 16:59 ` [Qemu-devel] [PATCH 1/6] qtest: add test framework Stefan Hajnoczi
  2012-01-16 17:08   ` Avi Kivity
@ 2012-01-16 17:08   ` Anthony Liguori
  2012-01-17 11:13     ` Stefan Hajnoczi
  1 sibling, 1 reply; 25+ messages in thread
From: Anthony Liguori @ 2012-01-16 17:08 UTC (permalink / raw)
  To: Stefan Hajnoczi
  Cc: Kevin Wolf, Anthony Liguori, Stefan Hajnoczi, qemu-devel,
	Avi Kivity, Paolo Bonzini

On 01/16/2012 10:59 AM, Stefan Hajnoczi wrote:
> On Fri, Jan 13, 2012 at 6:32 PM, Anthony Liguori<aliguori@us.ibm.com>  wrote:
>> +    if (strcmp(words[0], "outb") == 0 ||
>> +        strcmp(words[0], "outw") == 0 ||
>> +        strcmp(words[0], "outl") == 0) {
>> +        uint16_t addr;
>> +        uint32_t value;
>> +
>> +        g_assert(words[1]&&  words[2]);
>> +        addr = strtol(words[1], NULL, 0);
>> +        value = strtol(words[2], NULL, 0);
>> +
>> +        if (words[0][3] == 'b') {
>> +            cpu_outb(addr, value);
>> +        } else if (words[0][3] == 'w') {
>> +            cpu_outw(addr, value);
>> +        } else if (words[0][3] == 'l') {
>> +            cpu_outl(addr, value);
>> +        }
>> +        qtest_send_prefix(chr);
>> +        qtest_send(chr, "OK\n");
>> +    } else if (strcmp(words[0], "inb") == 0 ||
>> +        strcmp(words[0], "inw") == 0 ||
>> +        strcmp(words[0], "inl") == 0) {
>> +        uint16_t addr;
>> +        uint32_t value = -1U;
>> +
>> +        g_assert(words[1]);
>> +        addr = strtol(words[1], NULL, 0);
>> +
>> +        if (words[0][2] == 'b') {
>> +            value = cpu_inb(addr);
>> +        } else if (words[0][2] == 'w') {
>> +            value = cpu_inw(addr);
>> +        } else if (words[0][2] == 'l') {
>> +            value = cpu_inl(addr);
>> +        }
>> +        qtest_send_prefix(chr);
>> +        qtest_send(chr, "OK 0x%04x\n", value);
>
> Endianness is a little weird here.  memory.c will byteswap if target
> and device endianness differ.
>
> Imagine the case where we're on an x86 host, running a ppc guest,
> reading from PCI configuration space (little-endian).

These functions expect to get host native endian.  The qtest wire protocol is a 
string (which has no endianness) and converts it to host native endian.

>  Since ppc
> (target endian) is big-endian and the device is little-endian the
> value read/written will be byteswapped.  However, our qtest runs on
> the host and therefore we don't want that automatic swap (or we need
> to neutralize it by performing another byteswap on top).

ppc wouldn't use outb/inb.  It would do mmio to the PIO region which would send 
it through the host controller (which would do byte swapping as necessary).

So a qtest test case would have to do little endian MMIO to interact with the 
PCI bus.

Regards,

Anthony Liguori

>
> Stefan
>

^ permalink raw reply	[flat|nested] 25+ messages in thread

* Re: [Qemu-devel] [PATCH 4/6] make: add check targets based on gtester
  2012-01-13 18:32 ` [Qemu-devel] [PATCH 4/6] make: add check targets based on gtester Anthony Liguori
@ 2012-01-16 17:16   ` Paolo Bonzini
  2012-01-16 18:14     ` Anthony Liguori
  2012-01-17 14:04   ` Paolo Bonzini
  1 sibling, 1 reply; 25+ messages in thread
From: Paolo Bonzini @ 2012-01-16 17:16 UTC (permalink / raw)
  To: qemu-devel

On 01/13/2012 07:32 PM, Anthony Liguori wrote:
> This will run all tests through gtester.  The main targets are:
>
> $ make check
>
> Which will run each unit test and:
>
> $ make check-report.html
>
> Which will generate a nice HTML report of the test status.

Looks like there isn't any documentation about the protocol that gtester 
uses, and no Python implementation of the same in any other language. 
Or dually, no support for TAP output in gtester.  :/

I would have expected a bit less NIH from glib, but oh well...

Paolo

^ permalink raw reply	[flat|nested] 25+ messages in thread

* Re: [Qemu-devel] [PATCH 1/6] qtest: add test framework
  2012-01-16 17:08   ` Avi Kivity
@ 2012-01-16 17:20     ` Anthony Liguori
  0 siblings, 0 replies; 25+ messages in thread
From: Anthony Liguori @ 2012-01-16 17:20 UTC (permalink / raw)
  To: Avi Kivity
  Cc: Kevin Wolf, Stefan Hajnoczi, qemu-devel, Stefan Hajnoczi,
	Paolo Bonzini

On 01/16/2012 11:08 AM, Avi Kivity wrote:
> On 01/16/2012 06:59 PM, Stefan Hajnoczi wrote:
>>> +        }
>>> +        qtest_send_prefix(chr);
>>> +        qtest_send(chr, "OK 0x%04x\n", value);
>>
>> Endianness is a little weird here.  memory.c will byteswap if target
>> and device endianness differ.
>>
>> Imagine the case where we're on an x86 host, running a ppc guest,
>> reading from PCI configuration space (little-endian).  Since ppc
>> (target endian) is big-endian and the device is little-endian the
>> value read/written will be byteswapped.  However, our qtest runs on
>> the host and therefore we don't want that automatic swap (or we need
>> to neutralize it by performing another byteswap on top).
>>
>
> Good catch.  This is another example of how an access depends not only
> on the destination, but also on the source.  Here the source is not the
> cpu; it's qtest.

target-ppc never calls cpu_out.  It's prep_pci.c that calls it based on an MMIO 
operation and that's where the endianess conversion happens.

You wouldn't use outw() in a qtest harness when talking to a PCI device over PPC.

What we really want is to be able to send I/O directly to MemoryRegions but 
that's a bit different than what qtest is.

Regards,

Anthony Liguori

>

^ permalink raw reply	[flat|nested] 25+ messages in thread

* Re: [Qemu-devel] [PATCH 4/6] make: add check targets based on gtester
  2012-01-16 17:16   ` Paolo Bonzini
@ 2012-01-16 18:14     ` Anthony Liguori
  2012-01-17 10:42       ` Paolo Bonzini
  0 siblings, 1 reply; 25+ messages in thread
From: Anthony Liguori @ 2012-01-16 18:14 UTC (permalink / raw)
  To: Paolo Bonzini; +Cc: qemu-devel

On 01/16/2012 11:16 AM, Paolo Bonzini wrote:
> On 01/13/2012 07:32 PM, Anthony Liguori wrote:
>> This will run all tests through gtester. The main targets are:
>>
>> $ make check
>>
>> Which will run each unit test and:
>>
>> $ make check-report.html
>>
>> Which will generate a nice HTML report of the test status.
>
> Looks like there isn't any documentation about the protocol that gtester uses,
> and no Python implementation of the same in any other language. Or dually, no
> support for TAP output in gtester. :/
>
> I would have expected a bit less NIH from glib, but oh well...

Yes, I had a similar reaction.

But it's good enough for our purposes and only depends on our mandatory 
dependencies.

Regards,

Anthony Liguori

> Paolo
>
>
>

^ permalink raw reply	[flat|nested] 25+ messages in thread

* Re: [Qemu-devel] [PATCH 4/6] make: add check targets based on gtester
  2012-01-16 18:14     ` Anthony Liguori
@ 2012-01-17 10:42       ` Paolo Bonzini
  0 siblings, 0 replies; 25+ messages in thread
From: Paolo Bonzini @ 2012-01-17 10:42 UTC (permalink / raw)
  To: Anthony Liguori; +Cc: qemu-devel

On 01/16/2012 07:14 PM, Anthony Liguori wrote:
>>
>> Looks like there isn't any documentation about the protocol that
>> gtester uses,
>> and no Python implementation of the same in any other language. Or
>> dually, no
>> support for TAP output in gtester. :/
>>
>> I would have expected a bit less NIH from glib, but oh well...
>
> Yes, I had a similar reaction.
>
> But it's good enough for our purposes and only depends on our mandatory
> dependencies.

Agreed.  I'm looking into writing a gtest runner for pyunit.

Paolo

^ permalink raw reply	[flat|nested] 25+ messages in thread

* Re: [Qemu-devel] [PATCH 1/6] qtest: add test framework
  2012-01-16 17:08   ` Anthony Liguori
@ 2012-01-17 11:13     ` Stefan Hajnoczi
  2012-01-18  9:05       ` Stefan Hajnoczi
  0 siblings, 1 reply; 25+ messages in thread
From: Stefan Hajnoczi @ 2012-01-17 11:13 UTC (permalink / raw)
  To: Anthony Liguori
  Cc: Kevin Wolf, Anthony Liguori, Stefan Hajnoczi, qemu-devel,
	Avi Kivity, Paolo Bonzini

On Mon, Jan 16, 2012 at 5:08 PM, Anthony Liguori <anthony@codemonkey.ws> wrote:
> On 01/16/2012 10:59 AM, Stefan Hajnoczi wrote:
>>
>> On Fri, Jan 13, 2012 at 6:32 PM, Anthony Liguori<aliguori@us.ibm.com>
>>  wrote:
>>>
>>> +    if (strcmp(words[0], "outb") == 0 ||
>>> +        strcmp(words[0], "outw") == 0 ||
>>> +        strcmp(words[0], "outl") == 0) {
>>> +        uint16_t addr;
>>> +        uint32_t value;
>>> +
>>> +        g_assert(words[1]&&  words[2]);
>>> +        addr = strtol(words[1], NULL, 0);
>>> +        value = strtol(words[2], NULL, 0);
>>> +
>>> +        if (words[0][3] == 'b') {
>>> +            cpu_outb(addr, value);
>>> +        } else if (words[0][3] == 'w') {
>>> +            cpu_outw(addr, value);
>>> +        } else if (words[0][3] == 'l') {
>>> +            cpu_outl(addr, value);
>>> +        }
>>> +        qtest_send_prefix(chr);
>>> +        qtest_send(chr, "OK\n");
>>> +    } else if (strcmp(words[0], "inb") == 0 ||
>>> +        strcmp(words[0], "inw") == 0 ||
>>> +        strcmp(words[0], "inl") == 0) {
>>> +        uint16_t addr;
>>> +        uint32_t value = -1U;
>>> +
>>> +        g_assert(words[1]);
>>> +        addr = strtol(words[1], NULL, 0);
>>> +
>>> +        if (words[0][2] == 'b') {
>>> +            value = cpu_inb(addr);
>>> +        } else if (words[0][2] == 'w') {
>>> +            value = cpu_inw(addr);
>>> +        } else if (words[0][2] == 'l') {
>>> +            value = cpu_inl(addr);
>>> +        }
>>> +        qtest_send_prefix(chr);
>>> +        qtest_send(chr, "OK 0x%04x\n", value);
>>
>>
>> Endianness is a little weird here.  memory.c will byteswap if target
>> and device endianness differ.
>>
>> Imagine the case where we're on an x86 host, running a ppc guest,
>> reading from PCI configuration space (little-endian).
>
>
> These functions expect to get host native endian.  The qtest wire protocol
> is a string (which has no endianness) and converts it to host native endian.

The cpu_inl() return value is not host native endian.  We perform a
byteswap if the memory region endianness is different from the target
endianness.  For example, big-endian target and little-endian device
means we byteswap.  Little-endian target and little-endian device
means we do not byteswap.

Running the same test binary against qemu-system-x86_64 and
qemu-system-$be_arch results in 0 and 1 byteswaps, respectively.  Both
can't be right.

>>  Since ppc
>> (target endian) is big-endian and the device is little-endian the
>> value read/written will be byteswapped.  However, our qtest runs on
>> the host and therefore we don't want that automatic swap (or we need
>> to neutralize it by performing another byteswap on top).
>
>
> ppc wouldn't use outb/inb.  It would do mmio to the PIO region which would
> send it through the host controller (which would do byte swapping as
> necessary).
>
> So a qtest test case would have to do little endian MMIO to interact with
> the PCI bus.

Maybe ppc is a bad example, but are there other targets where in/out is used?

Stefan

^ permalink raw reply	[flat|nested] 25+ messages in thread

* Re: [Qemu-devel] [PATCH 3/6] qtest: add C version of test infrastructure
  2012-01-13 18:32 ` [Qemu-devel] [PATCH 3/6] qtest: add C version of test infrastructure Anthony Liguori
@ 2012-01-17 11:33   ` Stefan Hajnoczi
  2012-01-17 13:33     ` Paolo Bonzini
  2012-01-17 16:09   ` Paolo Bonzini
  2012-01-18 16:00   ` Kevin Wolf
  2 siblings, 1 reply; 25+ messages in thread
From: Stefan Hajnoczi @ 2012-01-17 11:33 UTC (permalink / raw)
  To: Anthony Liguori; +Cc: Kevin Wolf, Paolo Bonzini, qemu-devel, Stefan Hajnoczi

On Fri, Jan 13, 2012 at 6:32 PM, Anthony Liguori <aliguori@us.ibm.com> wrote:
> +    pid = fork();
> +    if (pid == 0) {
> +        command = g_strdup_printf("%s "
> +                                  "-qtest unix:%s,server,nowait "
> +                                  "-qtest-log /dev/null "
> +                                  "-pidfile %s "
> +                                  "-machine accel=qtest "
> +                                  "%s", qemu_binary, socket_path,
> +                                  pid_file,
> +                                  extra_args ?: "");
> +
> +        ret = system(command);

qtest_init() launches qemu with a pidfile so that we can send SIGTERM
later.  But we never get around to doing that if g_assert() fails - it
calls abort(3).  The result is a run-away qemu process.  I find the
qemu process consumes 100% in send_all() trying to write to the closed
qtest socket and SIGTERM no longer works since we're stuck in a tight
loop that never runs the event loop.

A simple solution is to handle SIGABRT in tests/libqtest.c and sent
SIGTERM to qemu when the test aborts.  The downside is that this only
covers the abort(3) case - a segfault or other abnormal termination
would still leave the run-away qemu process.

I was wondering about a qemu-side solution where a closed qtest socket
means we need to shut down, but am not sure if the chardev code lets
us do that.  (Really we want POLLHUP but we only seem to have
POLLIN/POLLOUT handlers.)

Thoughts?

Stefan

^ permalink raw reply	[flat|nested] 25+ messages in thread

* Re: [Qemu-devel] [PATCH 3/6] qtest: add C version of test infrastructure
  2012-01-17 11:33   ` Stefan Hajnoczi
@ 2012-01-17 13:33     ` Paolo Bonzini
  2012-01-17 13:39       ` Stefan Hajnoczi
  0 siblings, 1 reply; 25+ messages in thread
From: Paolo Bonzini @ 2012-01-17 13:33 UTC (permalink / raw)
  To: Stefan Hajnoczi; +Cc: Kevin Wolf, Anthony Liguori, qemu-devel, Stefan Hajnoczi

On 01/17/2012 12:33 PM, Stefan Hajnoczi wrote:
> I was wondering about a qemu-side solution where a closed qtest socket
> means we need to shut down, but am not sure if the chardev code lets
> us do that.  (Really we want POLLHUP but we only seem to have
> POLLIN/POLLOUT handlers.)

For poll, both POLLIN and POLLOUT are always reported together with 
POLLHUP.  I think the same happens with select().  If you get a 
zero-read in the qtest chardev handler you can shut down.

Paolo

^ permalink raw reply	[flat|nested] 25+ messages in thread

* Re: [Qemu-devel] [PATCH 3/6] qtest: add C version of test infrastructure
  2012-01-17 13:33     ` Paolo Bonzini
@ 2012-01-17 13:39       ` Stefan Hajnoczi
  0 siblings, 0 replies; 25+ messages in thread
From: Stefan Hajnoczi @ 2012-01-17 13:39 UTC (permalink / raw)
  To: Paolo Bonzini; +Cc: Kevin Wolf, Anthony Liguori, qemu-devel, Stefan Hajnoczi

On Tue, Jan 17, 2012 at 1:33 PM, Paolo Bonzini <pbonzini@redhat.com> wrote:
> On 01/17/2012 12:33 PM, Stefan Hajnoczi wrote:
>>
>> I was wondering about a qemu-side solution where a closed qtest socket
>> means we need to shut down, but am not sure if the chardev code lets
>> us do that.  (Really we want POLLHUP but we only seem to have
>> POLLIN/POLLOUT handlers.)
>
>
> For poll, both POLLIN and POLLOUT are always reported together with POLLHUP.
>  I think the same happens with select().  If you get a zero-read in the
> qtest chardev handler you can shut down.

There is already open/closed logic in qemu-char.c that acts on
select(2) becoming readable.  However, it isn't kicking in - we're
still ending up in send_all(), which should only be called when we
thing the socket is connected.  I'll investigate some more.

Stefan

^ permalink raw reply	[flat|nested] 25+ messages in thread

* Re: [Qemu-devel] [PATCH 4/6] make: add check targets based on gtester
  2012-01-13 18:32 ` [Qemu-devel] [PATCH 4/6] make: add check targets based on gtester Anthony Liguori
  2012-01-16 17:16   ` Paolo Bonzini
@ 2012-01-17 14:04   ` Paolo Bonzini
  2012-01-17 14:22     ` Anthony Liguori
  1 sibling, 1 reply; 25+ messages in thread
From: Paolo Bonzini @ 2012-01-17 14:04 UTC (permalink / raw)
  To: qemu-devel, Anthony Liguori

On 01/13/2012 07:32 PM, Anthony Liguori wrote:
> This will run all tests through gtester.  The main targets are:
>
> $ make check
>
> Which will run each unit test and:
>
> $ make check-report.html
>
> Which will generate a nice HTML report of the test status.

gtester-report here (Fedora 16) has a bug where it needs something like 
this:

  <info>
   <package>qemu</package>
   <version>0.0</version>
   <revision>rev</revision>
  </info>

inside the <gtester> tag.  Any chance you could generate these tags from 
gtester-cat so that gtester-report actually works?

Paolo

^ permalink raw reply	[flat|nested] 25+ messages in thread

* Re: [Qemu-devel] [PATCH 4/6] make: add check targets based on gtester
  2012-01-17 14:04   ` Paolo Bonzini
@ 2012-01-17 14:22     ` Anthony Liguori
  0 siblings, 0 replies; 25+ messages in thread
From: Anthony Liguori @ 2012-01-17 14:22 UTC (permalink / raw)
  To: Paolo Bonzini; +Cc: qemu-devel

On 01/17/2012 08:04 AM, Paolo Bonzini wrote:
> On 01/13/2012 07:32 PM, Anthony Liguori wrote:
>> This will run all tests through gtester. The main targets are:
>>
>> $ make check
>>
>> Which will run each unit test and:
>>
>> $ make check-report.html
>>
>> Which will generate a nice HTML report of the test status.
>
> gtester-report here (Fedora 16) has a bug where it needs something like this:
>
> <info>
> <package>qemu</package>
> <version>0.0</version>
> <revision>rev</revision>
> </info>
>
> inside the <gtester> tag. Any chance you could generate these tags from
> gtester-cat so that gtester-report actually works?

Sure.

Regards,

Anthony Liguori

> Paolo

^ permalink raw reply	[flat|nested] 25+ messages in thread

* Re: [Qemu-devel] [PATCH 3/6] qtest: add C version of test infrastructure
  2012-01-13 18:32 ` [Qemu-devel] [PATCH 3/6] qtest: add C version of test infrastructure Anthony Liguori
  2012-01-17 11:33   ` Stefan Hajnoczi
@ 2012-01-17 16:09   ` Paolo Bonzini
  2012-01-18 16:00   ` Kevin Wolf
  2 siblings, 0 replies; 25+ messages in thread
From: Paolo Bonzini @ 2012-01-17 16:09 UTC (permalink / raw)
  To: qemu-devel, Anthony Liguori

[-- Attachment #1: Type: text/plain, Size: 365 bytes --]

On 01/13/2012 07:32 PM, Anthony Liguori wrote:
> This also includes a qtest wrapper script to make it easier to launch qtest
> tests directly.
>
> Signed-off-by: Anthony Liguori<aliguori@us.ibm.com>

Here is a Python test harness for qtest.  I haven't tried merging them 
with the makefiles.

Feel free to add my s-o-b and include the files in your patches.

Paolo

[-- Attachment #2: gtest_main.py --]
[-- Type: text/x-python, Size: 19484 bytes --]

#! /usr/bin/env python
#
# GTester-compatible main program for pyunit
#
#    Copyright (C) 2011 Red Hat, Inc.
#    Author: Paolo Bonzini <pbonzini@redhat.com>
#
# Permission to use, copy, modify, and/or distribute this software for any
# purpose with or without fee is hereby granted, provided that the above
# copyright notice and this permission notice appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.


# This file defines replacements for unittest.main and unittest.TextTestRunner.
# They are command-line compatible with glib test suites, including gtester
# support, and even in standalone mode the output is similar to gtest-based
# C unit tests.  The only unsupported features are -m and --GTestSkipCount.
#
# Use this instead of unittest.main:
#
#   if __name__ == "__main__":
#       gtest_main.main() # run all tests

import sys
import os
import types

import fnmatch
import random
import re
import struct
import time
import traceback

import unittest
from unittest.signals import registerResult, installHandler

# Ugly implementation dependency... used by TestResult to skip internal
# frames in the traceback
__unittest = True


def test_id(test, module='__main__'):
    """Retrieve a GTest-like path from a TestCase."""
    id = test.id().replace('.', '/')
    if module is not None:
        id = id[len(module.__name__):]
    return id


class SelectTestsSuite(unittest.TestSuite):
    """A wrapper for other TestSuites that processes -p and -s options.
       Perhaps it could also use decorators to implement -m?"""

    re = None
    module = None

    def __init__(self, tests=(), module='__main__', path='*', skipPath=None):
        super(unittest.TestSuite, self).__init__(tests=tests)
        self.module = module

        regex = ''
        if path != '*':
            regex = fnmatch.translate(path)
        if skipPath is not None:
            regex = '(?!' + fnmatch.translate(skipPath) + ')' + regex
        if regex != '':
            self.re = re.compile(regex)

    def __iter__(self):
        iter = super(unittest.TestSuite, self).__iter__()
        if self.re is not None:
            iter = (x for x in iter if self.accept(x))
        return iter

    def addTest(self, test):
        if isinstance(test, unittest.TestSuite):
            self.addTests(test)
        else:
            unittest.TestSuite.addTest(self, test)

    def accept(self, test):
        id = test_id(test, self.module)
        return self.re is None or self.re.match(id)


class GTestResult(unittest.TestResult):
    """A test result class that can print formatted text results to a stream
       and can talk to gtester using the glib test protocol.  Roughly based on
       TextTestResult, used internally by instances of GAbstractTestRunner."""

    separator1 = '=' * 70 + "\n"
    separator2 = '-' * 70 + "\n"

    def defaultSeed(self):
        """Return a default random number seed.  GLib expects this to be
           the string 'R02S' followed by four zero-padded 32-bit integers.
           We need to return a properly formatted seed so that the value
           can be passed to gtester even when Python and C tests are mixed
           on the command line."""
        try:
            s = os.urandom(16)
            a, b, c, d = struct.unpack(">IIII", s)
        except NotImplementedError:
            t = time.time()
            a = int(t)
            b = int((t - a) * 1000000)
            c = os.getpid()
            d = os.name = 'posix' and os.getppid() or 0
        return "R02S{0:>08x}{1:>08x}{2:>08x}{3:>08x}".format(a,b,c,d)

    def __init__(self, log, stream, progName=None, module='__main__', verbosity=1,
                 seed=None):
        super(GTestResult, self).__init__()
        self.stream = stream
        self.log = log
        self.showAll = verbosity > 1
        self.quiet = verbosity == 0
        self.module = module
        self.seed = seed or self.defaultSeed()
        self.progName = progName or os.path.basename(sys.argv[0])

    # These methods implement the glib test protocol.
    G_TEST_LOG_NONE = 0
    G_TEST_LOG_ERROR = 1           # s:msg
    G_TEST_LOG_START_BINARY = 2    # s:binaryname s:seed
    G_TEST_LOG_LIST_CASE = 3       # s:testpath
    G_TEST_LOG_SKIP_CASE = 4       # s:testpath, TODO
    G_TEST_LOG_START_CASE = 5      # s:testpath
    G_TEST_LOG_STOP_CASE = 6       # d:status d:nforks d:elapsed
    G_TEST_LOG_MIN_RESULT = 7      # s:blurb d:result
    G_TEST_LOG_MAX_RESULT = 8      # s:blurb d:result
    G_TEST_LOG_MESSAGE = 9         # s:blurb

    def pack_log(self, typ, strings=(), nums=()):
        out = struct.pack(">iiii", typ, len(strings), len(nums), 0)
        for s in strings:
            out = out + struct.pack(">i", len(s)) + s
        for n in nums:
            out = out + struct.pack(">d", float(n))
        out = struct.pack(">i", len(out) + 4) + out
        self.log.write(out)

    def logStartBinary(self):
        self.pack_log(self.G_TEST_LOG_START_BINARY, (self.progName, self.seed))

    def logStartCase(self, test):
        id = test_id(test, self.module)
        self.pack_log(self.G_TEST_LOG_START_CASE, (id,))

    def logListCase(self, test):
        id = test_id(test, self.module)
        self.pack_log(self.G_TEST_LOG_LIST_CASE, (id,))

    def logError(self, msg):
        self.pack_log(self.G_TEST_LOG_ERROR, (msg,))

    def logMessage(self, msg):
        self.pack_log(self.G_TEST_LOG_MESSAGE, (msg,))

    def logStopCase(self, status):
        try:
            elapsed = time.clock() - self.startCaseTime
            self.pack_log(self.G_TEST_LOG_STOP_CASE, nums=(status,0,elapsed))
        except:
            # This happens when class setup fails.  startCaseTime has not
            # been set, do nothing
            assert status != 0
            pass

    def logException(self, test, kind, err, skip=1):
        """Return a representation for the exception passed to addError
           or addFailure."""
        type, inst, trace=err
        try:
            file, line, func, text = traceback.extract_tb(trace, skip+1)[skip]
            msg = "%s:%s:%s:%s: %s\n" % (kind, file, line, type.__name__, inst)
        except:
            msg = "%s:%s: %s\n" % (kind, type.__name__, inst)
        self.write("**\n" + msg)
        self.logError(msg + self.separator1 + self._exc_info_to_string(err, test))

    # These methods implement text output.
    def write(self, str):
        self.stream.write(str)

    def flush(self):
        self.stream.flush()

    # These methods implement the standard TestResult protocol.
    def startTestRun(self):
        self.logStartBinary()
        self.startTime = time.clock()

    def stopTestRun(self):
        self.stopTime = time.clock()

    def startTest(self, test):
        super(GTestResult, self).startTest(test)
        random.seed(self.seed)
        self.startCaseTime = time.clock()
        self.logStartCase(test)
        if not self.quiet:
            self.write(test_id(test, self.module))
            self.write(": ")
            self.flush()

    def addSuccess(self, test):
        super(GTestResult, self).addSuccess(test)
        self.logStopCase(0)
        if not self.quiet:
            self.write("OK\n")

    def addError(self, test, err):
        self.logException(test, "ERROR", err)
        self.logStopCase(1)
        super(GTestResult, self).addError(test, err)

    def addFailure(self, test, err):
        self.logException(test, "FAIL", err)
        self.logStopCase(1)
        super(GTestResult, self).addFailure(test, err)

    def addSkip(self, test, reason):
        self.logStopCase(0)
        super(GTestResult, self).addSkip(test, reason)
        if not self.quiet:
            self.write("SKIP {0!r}\n".format(reason))

    def addExpectedFailure(self, test, err):
        self.logException("XFAIL", err)
        self.logStopCase(0)
        super(GTestResult, self).addExpectedFailure(test, err)
        if not self.quiet:
            self.write("XFAIL\n")

    def addUnexpectedSuccess(self, test):
        self.logError("unexpected success")
        self.logStopCase(1)
        super(GTestResult, self).addUnexpectedSuccess(test)
        if not self.quiet:
            self.write("XPASS\n")

    # Additional methods used by GTestLister and GTestRunner.
    def listTest(self, test):
        super(GTestResult, self).startTest(test)
        self.logListCase(test)
        if not self.quiet:
            self.write(test_id(test, self.module))
            self.write("\n")
            self.flush()
        super(GTestResult, self).addSuccess(test)

    def printErrors(self):
        self.printErrorList('ERROR', self.errors)
        self.printErrorList('FAIL', self.failures)
        self.write(self.separator2)

    def printErrorList(self, kind, errors):
        for test, err in errors:
            self.write(self.separator1)
            self.write("%s: %s\n" % (kind,test_id(test, self.module)))
            self.write(self.separator2)
            self.write("%s\n" % err)

    def printSummary(self):
        run = self.testsRun
        timeTaken = self.stopTime - self.startTime
        if not self.quiet:
            self.write("Ran %d test%s in %.3fs\n\n" %
                       (run, run != 1 and "s" or "", timeTaken))

        infos = []
        failed = len(self.failures)
        if failed:
            infos.append("failures=%d" % failed)

        errored = len(self.errors)
        if errored:
            infos.append("errors=%d" % errored)

        skipped = len(self.skipped)
        if skipped:
            infos.append("skipped=%d" % skipped)

        expectedFails = len(self.expectedFailures)
        if expectedFails:
            infos.append("expected failures=%d" % expectedFails)

        unexpectedSuccesses = len(self.unexpectedSuccesses)
        if unexpectedSuccesses:
            infos.append("unexpected successes=%d" % unexpectedSuccesses)

        if not self.wasSuccessful():
            self.write("FAILED (%s)\n" % (", ".join(infos),))
        elif infos:
            self.write("OK (%s)\n" % (", ".join(infos),))
        elif not self.quiet:
            self.write("OK\n")

    def printResults(self):
        if self.quiet or self.showAll:
            if self.showAll:
                self.write("\n")
                self.printErrors()
            self.printSummary()

class GAbstractTestRunner(object):
    """A test runner class that interfaces to a GTestResult.  Compared
       to e.g. TextTestRunner, it can pass down some data that is required
       by the glib test protocol (program name, random number seed)."""

    def __init__(self, log=None, stream=sys.stdout, verbosity=1, progName=None,
                 module='__main__', failfast=True, seed=None, buffer=False):
        self.module = module
        self.verbosity = verbosity
        self.failfast = failfast
        self.buffer = buffer
        self.progName = progName
        self.seed = seed

        class _DummyStream(object):
            def write(self, s):
                pass

        self.log = log or _DummyStream()
        self.stream = log and _DummyStream() or stream

    def _makeResult(self):
        return GTestResult(stream=self.stream, log=self.log, module=self.module,
                           seed=self.seed, progName=self.progName,
                           verbosity=self.verbosity)

    def run(self, test):
        "Run the given test case or test suite."
        result = self._makeResult()
        registerResult(result)
        result.failfast = self.failfast
        result.buffer = self.buffer
        startTestRun = getattr(result, 'startTestRun', None)
        if startTestRun is not None:
            startTestRun()
        try:
            self.doRun(test, result)
        finally:
            stopTestRun = getattr(result, 'stopTestRun', None)
            if stopTestRun is not None:
                stopTestRun()
        return result

    def doRun(self, test, result):
        "Run the given test case or test suite."
        test(result)

class GTestLister(GAbstractTestRunner):
    """A test runner class that only prints the names of test cases.
       in the suite.

       When run in standalone mode it prints out the names of tests
       that have been selected.  When run in gtester mode, it logs
       the same information using the glib test protocol."""

    def doRun(self, test, result):
        """Run the given test case or test suite (actually just ask the
           GTestResult to list the test case)."""
        for t in test:
            result.listTest(t)

class GTestRunner(GAbstractTestRunner):
    """A test runner class that emits results in textual and GLib form.

       When run in standalone mode it prints out the names of tests as
       they are run, errors as they occur, and a summary of the results
       at the end of the test run, depending on the verbosity level.
       When run in gtester mode, it logs the entire run using the glib
       test protocol."""

    def run(self, test):
        "Run the given test case or test suite."
        result = super(GTestRunner, self).run(test)
        result.printResults()
        return result


USAGE = """\
Usage: %(progName)s [options]

Help Options:
  -?, --help                     Show help options
Test Options:
  -l                             List test cases available in a test executable
  -seed=RANDOMSEED               Provide a random seed to reproduce test
                                 runs using random numbers
  -v, --verbose                  Run tests verbosely
  -q, --quiet                    Run tests quietly
  -p TESTPATH                    execute all tests matching TESTPATH
  -s TESTPATH                    skip all tests matching TESTPATH
  -m {perf|slow|thorough|quick}  (unsupported) Execute tests according to mode
  -k, --keep-going               Keep running after the first failure
  --runner=CLASS                 Use an alternative test runner
                                 (e.g. unittest.TextTestRunner)
  --GTestLogFD=N                 file descriptor for communication with GTester
  --GTestSkipCount=N             (unsupported) gtester-specific argument

-p and -s accept slash-separated paths.  They work even in combination with
--runner.  -l and --GTestLogFD are only supported without --runner.
"""

class GTestProgram(object):
    """A command-line program that runs a set of tests, used to make test
       modules conveniently executable and to interface them with gtester."""

    failfast = True
    progName = None
    seed = None
    exit = True
    logFD = None
    verbosity = 1
    path = '*'
    skipPath = None

    def getModule(self, module):
        if isinstance(module, basestring):
            m = __import__(module)
            for part in module.split('.')[1:]:
                m = getattr(m, part)
            return m
        else:
            return module

    def getClass(self, name):
        if isinstance(name, basestring):
            parts = name.split('.')
            module = ".".join(parts[:-1])
            m = self.getModule(module)
            return getattr(m, parts[-1])
        else:
            return name

    def __init__(self, module='__main__', argv=None,
                    testRunner=None, testLoader=unittest.defaultTestLoader,
                    exit=True, seed=None):
        argv = argv or sys.argv
        self.module = self.getModule(module)
        self.exit = exit
        self.seed = seed
        self.testRunner = testRunner
        self.testLoader = testLoader
        self.progName = os.path.basename(argv[0])
        self.parseArgs(argv)
        self.suite = self.createTests()
        self.runTests()

    def usageExit(self, msg=None):
        if msg:
            print msg
        print USAGE % {'progName': self.progName}
        if self.exit:
            sys.exit(2)

    def parseArgs(self, argv):
        import getopt
        long_opts = ['seed=', 'keep-going', 'verbose', 'quiet', 'GTestLogFD=',
                     'runner=', 'GTestSkipCount=']
        list = False
        try:
            options, args = getopt.getopt(argv[1:], '?hHlvqs:p:m:k', long_opts)
            for opt, value in options:
                # quirk in the gtest option parser...
                if opt[1] != '-' and value != '' and value[0] == '=':
                    value = value[1:]

                if opt in ('-h','-H','-?','--help'):
                    self.usageExit()
                if opt in ('-q','--quiet'):
                    self.verbosity = 0
                if opt in ('-v','--verbose'):
                    self.verbosity = 2
                if opt in ('-k','--keep-going'):
                    self.failfast = False
                if opt in ('-l'):
                    list = True
                if opt in ('-p'):
                    self.path = value
                if opt in ('-s'):
                    self.skipPath = value
                if opt in ('--seed'):
                    self.seed = value
                if opt in ('--runner'):
                    self.testRunner = self.getClass(value)
                if opt in ('--GTestLogFD'):
                    self.logFD = int(value)
        except getopt.error, msg:
            self.usageExit(msg)

        if self.testRunner is None:
            self.testRunner = list and GTestLister or GTestRunner
        else:
            # Set the seed now for user-specified runners.  GTestRunners
            # resets it for each testcase, so that results are reproducible
            # even if other tests change.
            random.seed(self.seed)

    def createTests(self):
        allTests = self.testLoader.loadTestsFromModule(self.module)
        return SelectTestsSuite(tests=allTests, path=self.path, skipPath=self.skipPath,
                                module=self.module)

    def runTests(self):
        installHandler()
        if isinstance(self.testRunner, type) and \
               issubclass(self.testRunner, GAbstractTestRunner):
            # pass GTest-specific options to GAbstractTestRunner subclasses
            testRunner = self.testRunner(verbosity=self.verbosity,
                                         failfast=self.failfast,
                                         module=self.module,
                                         progName=self.progName,
                                         seed=self.seed,
                                         log=self.logFD and os.fdopen(self.logFD, 'w'))
        elif isinstance(self.testRunner, (type, types.ClassType)):
            try:
                testRunner = self.testRunner(verbosity=self.verbosity,
                                             failfast=self.failfast)
            except TypeError:
                testRunner = self.testRunner()
        else:
            # it is assumed to be a TestRunner instance
            testRunner = self.testRunner

        self.result = testRunner.run(self.suite)
        if self.exit:
            sys.exit(not self.result.wasSuccessful())

main = GTestProgram

[-- Attachment #3: qtest.py --]
[-- Type: text/x-python, Size: 5474 bytes --]

#! /usr/bin/env python
#
# qtest harness for PyUnit
#
#    Copyright (C) 2011 Red Hat, Inc.
#    Author: Paolo Bonzini <pbonzini@redhat.com>
#
# This file is licensed under the terms of the GNU GPL, version 2 or later.
# See the COPYING file in the top-level directory.

import os
import struct
import socket
import unittest

class ClientDisconnected(Exception):
    pass

class QTestCase(unittest.TestCase):
    @classmethod
    def supportedArches():
        return ['all']

    @classmethod
    def extraArgs():
        return ()

    @classmethod
    def isSupportedArch(self, arch):
        return any(x == 'all' or x == arch for x in self.supportedArches())

    @classmethod
    def _raiseIRQ(self, irq):
        self._irqs.add(irq)

    @classmethod
    def _lowerIRQ(self, irq):
        self._irqs.discard(irq)

    @classmethod
    def setUpClass(self):
        qemu_binary = os.getenv('QTEST_QEMU_BINARY')
        arch = os.path.basename(qemu_binary).replace('qemu-system-','')
        if not self.isSupportedArch(arch):
            self._classSetupFailed = True
            return

        socket_path = '/tmp/qtest-%d.sock' % os.getpid()
        server_socket = socket.socket(socket.AF_UNIX)
        server_socket.bind(socket_path)
        server_socket.listen(1)

        self.pid = os.spawnl(os.P_NOWAIT, qemu_binary,
                             qemu_binary,
                             '-qtest', 'unix:%s' % socket_path,
                             '-qtest-log', '/dev/null',
                             '-machine', 'accel=qtest',
                             *self.extraArgs())

        self._socket, addr = server_socket.accept()
        self._irqs = set()
        self._buffer = ''
        self._reply = None
        server_socket.close()

    @classmethod
    def tearDownClass(self):
        self._socket.close()
        self._socket = None
        os.kill(self.pid, 15)
        os.waitpid(self.pid, 0)

    @classmethod
    def _qtestResponse(self, buf):
        tokens = buf.split()
        if tokens[0] == 'OK':
            self._reply = tokens
            return

        if tokens[0] == 'IRQ':
            if tokens[1] == 'raise':
                self._raiseIRQ(int(tokens[2]))
            else:
                self._lowerIRQ(int(tokens[2]))
            return

    @classmethod
    def waitForEvents(self, blocking=True):
        self._socket.setblocking(int(blocking))
        while True:
            i = self._buffer.find('\n')
            if i != -1:
                response, self._buffer = self._buffer[:i], self._buffer[i+1:]
                self._qtestResponse(response)
                self._socket.setblocking(0)
                continue

            try:
                data = self._socket.recv(4096)
                self._buffer = self._buffer + data
                if (len(data) == 0):
                    raise ClientDisconnected
            except:
                return

    @classmethod
    def _send(self, str):
        assert self._reply is None
        self._socket.sendall(str + '\n')
        while self._reply is None:
            self.waitForEvents()
        reply, self._reply = self._reply, None
        return reply

    # IRQ access
    def getIRQ(self, irq):
        self.waitForEvents(False)
        return irq in self._irqs

    def waitIRQ(self, irq, level=True):
        self.waitForEvents(False)
        while (irq in self._irqs) != level:
            self.waitForEvents(True)

    # PIO reads
    def inb(self, port):
        return int(self._send('inb 0x%x' % port)[1][2:], 16)

    def inw(self, port):
        return int(self._send('inw 0x%x' % port)[1][2:], 16)

    def inl(self, port):
        return int(self._send('inl 0x%x' % port)[1][2:], 16)

    # Memory reads
    def memread_unpack(self, addr, format):
        size = struct.calcsize(format)
        str = 'read 0x%x 0x%x' % (addr, size)
        reply = self._send(str)[1]
        bytes = reply[2:].decode('hex_codec')
        return struct.unpack(format, bytes)

    def memread(self, addr, size):
        return self.memread_unpack('%dB' % size)

    def ldb(self, addr):
        return self.memread_unpack(addr, 'B')[0]

    def ldw_le(self, addr):
        return self.memread_unpack(addr, '<H')[0]

    def ldl_le(self, addr):
        return self.memread_unpack(addr, '<I')[0]

    def ldw_be(self, addr):
        return self.memread_unpack(addr, '>H')[0]

    def ldl_be(self, addr):
        return self.memread_unpack(addr, '>I')[0]

    # PIO writes
    def outb(self, port, val):
        self._send('outb 0x%x 0x%x' % (port, val))

    def outw(self, port, val):
        self._send('outw 0x%x 0x%x' % (port, val))

    def outl(self, port, val):
        self._send('outl 0x%x 0x%x' % (port, val))

    # Memory writes
    def memwrite_pack(self, addr, format, *data):
        bytes = struct.pack(format, *data)
        str = 'write 0x%x 0x%x 0x%s' % (
                addr, len(bytes), bytes.encode('hex_codec'))
        self._send(str)

    def memwrite(self, addr, *data):
        self.memwrite_pack('%dB' % len(data), *data)

    def stb(self, addr, datum):
        self.memwrite_pack(addr, 'B', datum)

    def stw_le(self, addr, datum):
        self.memwrite_pack(addr, '<H', datum)

    def stl_le(self, addr, datum):
        self.memwrite_pack(addr, '<I', datum)

    def stw_be(self, addr, datum):
        self.memwrite_pack(addr, '>H', datum)

    def stl_be(self, addr, datum):
        self.memwrite_pack(addr, '>I', datum)

[-- Attachment #4: rtc-test.py --]
[-- Type: text/x-python, Size: 1380 bytes --]

#! /usr/bin/env python
#
# Sample qtest written in Python
#
#    Copyright (C) 2011 Red Hat, Inc.
#    Author: Paolo Bonzini <pbonzini@redhat.com>
#
# This file is licensed under the terms of the GNU GPL, version 2 or later.
# See the COPYING file in the top-level directory.

import time
import qtest
import gtest_main

class QTestCase(qtest.QTestCase):
    @classmethod
    def supportedArches(self):
        return ['i386', 'x86_64']

    @classmethod
    def extraArgs(self):
        return ('-vga', 'none')

    BASE = 0x70
    RTC_SECONDS = 0
    RTC_MINUTES = 2
    RTC_HOURS = 4

    def read(self, reg):
        self.outb(self.BASE, reg)
        return self.inb(self.BASE + 1)

    def write(self, reg, value):
        self.outb(self.BASE, reg)
        self.outb(self.BASE + 1, value)

    def testRead(self):
        self.write(self.RTC_HOURS, 0x01)
        self.write(self.RTC_MINUTES, 0x15)
        self.write(self.RTC_SECONDS, 0x29)
        assert self.read(self.RTC_MINUTES) == 0x15
        assert self.read(self.RTC_HOURS) == 0x01

    def testUpdate(self):
        self.write(self.RTC_HOURS, 0x01)
        self.write(self.RTC_MINUTES, 0x15)
        self.write(self.RTC_SECONDS, 0x29)
        while self.read(self.RTC_SECONDS) == 0x29:
            time.sleep(0.2)
        assert self.read(self.RTC_SECONDS) == 0x30

if __name__ == '__main__':
    gtest_main.main()

^ permalink raw reply	[flat|nested] 25+ messages in thread

* Re: [Qemu-devel] [PATCH 1/6] qtest: add test framework
@ 2012-01-17 17:22 Michael Walle
  0 siblings, 0 replies; 25+ messages in thread
From: Michael Walle @ 2012-01-17 17:22 UTC (permalink / raw)
  To: Anthony Liguori; +Cc: Kevin Wolf, Paolo Bonzini, qemu-devel, Stefan Hajnoczi

On Fri, January 13, 2012 19:32, Anthony Liguori wrote:
> diff --git a/qtest.c b/qtest.c
> new file mode 100644
> index 0000000..f41a9c3
> --- /dev/null
> +++ b/qtest.c
> @@ -0,0 +1,357 @@
> +/*
> + * Test Server
> + *
> + * Copyright IBM, Corp. 2011
> + *
> + * Authors:
> + *  Anthony Liguori   <aliguori@us.ibm.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 "qtest.h"
> +#include "qemu-char.h"
> +#include "ioport.h"
> +#include "memory.h"
> +#include "hw/irq.h"
> +#include "sysemu.h"
> +
> +#define MAX_IRQ 256
> +
> +const char *qtest_chrdev;
> +const char *qtest_log;
> +int qtest_allowed = 0;
> +
> +static FILE *qtest_log_fp;
> +static CharDriverState *qtest_chr;
> +static GString *inbuf;
> +static int irq_levels[MAX_IRQ];
> +static struct timeval start_time;
> +static bool qtest_opened;
> +
> +#define FMT_timeval "%" PRId64 ".%06" PRId64

This doesnt work for me. tv_sec and tv_usec are "long int" on my platform
(32bit, x86, debian stable).

> +
> +/**
> + * QTest Protocol
> + *
> + * Line based protocol, request/response based.  Server can send async
messages
> + * so clients should always handle many async messages before the response
> + * comes in.
> + *
> + * Valid requests
> + *
> + *  > outb ADDR VALUE
> + *  < OK
> + *
> + *  > outw ADDR VALUE
> + *  < OK
> + *
> + *  > outl ADDR VALUE
> + *  < OK
> + *
> + *  > inb ADDR
> + *  < OK VALUE
> + *
> + *  > inw ADDR
> + *  < OK VALUE
> + *
> + *  > inl ADDR
> + *  < OK VALUE
> + *
> + *  > read ADDR SIZE
> + *  < OK DATA
> + *
> + *  > write ADDR SIZE DATA
> + *  < OK
> + *
> + * Valid async messages:
> + *
> + *  IRQ raise NUM
> + *  IRQ lower NUM
> + *
> + * ADDR, SIZE, VALUE are all integers parsed with strtoul() with a base
of 0.
> + *
> + * DATA is an arbitrarily long hex number prefixed with '0x'.  If it's
smaller
> + * than the expected size, the value will be zero filled at the end of
the data
> + * sequence.
> + *
> + * NUM is an IRQ number.
> + */
> +
> +static int hex2nib(char ch)
> +{
> +    if (ch >= '0' && ch <= '9') {
> +        return ch - '0';
> +    } else if (ch >= 'a' && ch <= 'f') {
> +        return 10 + (ch - 'a');
> +    } else if (ch >= 'A' && ch <= 'F') {
> +        return 10 + (ch - 'a');
> +    } else {
> +        return -1;
> +    }
> +}
> +
> +static void qtest_get_time(struct timeval *tv)
> +{
> +    gettimeofday(tv, NULL);
> +    tv->tv_sec -= start_time.tv_sec;
> +    tv->tv_usec -= start_time.tv_usec;
> +    if (tv->tv_usec < 0) {
> +        tv->tv_usec += 1000000;
> +        tv->tv_sec -= 1;
> +    }
> +}
> +
> +static void qtest_send_prefix(CharDriverState *chr)
> +{
> +    struct timeval tv;
> +
> +    if (!qtest_log_fp || !qtest_opened) {
> +        return;
> +    }
> +
> +    qtest_get_time(&tv);
> +    fprintf(qtest_log_fp, "[S +" FMT_timeval "] ",
> +            tv.tv_sec, tv.tv_usec);
> +}
> +
> +static void qtest_send(CharDriverState *chr, const char *fmt, ...) +{
> +    va_list ap;
> +    char buffer[1024];
> +    size_t len;
> +
> +    va_start(ap, fmt);
> +    len = vsnprintf(buffer, sizeof(buffer), fmt, ap);
> +    va_end(ap);
> +
> +    qemu_chr_fe_write(chr, (uint8_t *)buffer, len);
> +    if (qtest_log_fp && qtest_opened) {
> +        fprintf(qtest_log_fp, "%s", buffer);
> +    }
> +}
> +
> +static void qtest_process_command(CharDriverState *chr, gchar **words) +{
> +    const gchar *command;
> +
> +    g_assert(words);
> +
> +    command = words[0];
> +
> +    if (qtest_log_fp) {
> +        struct timeval tv;
> +        int i;
> +
> +        qtest_get_time(&tv);
> +        fprintf(qtest_log_fp, "[R +" FMT_timeval "]",
> +                tv.tv_sec, tv.tv_usec);
> +        for (i = 0; words[i]; i++) {
> +            fprintf(qtest_log_fp, " %s", words[i]);
> +        }
> +        fprintf(qtest_log_fp, "\n");
> +    }
> +
> +    g_assert(command);
i don't know if these asserts will be in the final version of your patch
or wether one say that any protocol violations will terminate qemu. maybe
we should just ignore unknown/invalid commands for now? Eg. i found it
very useful to use the protocol directly with telnet but it was annoying
that any invalid command aborted qemu.

OTOH you may dictate to use a proper client library. i'm fine with this
too ;)

> +    if (strcmp(words[0], "outb") == 0 ||
> +        strcmp(words[0], "outw") == 0 ||
> +        strcmp(words[0], "outl") == 0) {
> +        uint16_t addr;
> +        uint32_t value;
> +
> +        g_assert(words[1] && words[2]);
> +        addr = strtol(words[1], NULL, 0);
> +        value = strtol(words[2], NULL, 0);
> +
> +        if (words[0][3] == 'b') {
> +            cpu_outb(addr, value);
> +        } else if (words[0][3] == 'w') {
> +            cpu_outw(addr, value);
> +        } else if (words[0][3] == 'l') {
> +            cpu_outl(addr, value);
> +        }
> +        qtest_send_prefix(chr);
> +        qtest_send(chr, "OK\n");
> +    } else if (strcmp(words[0], "inb") == 0 ||
> +        strcmp(words[0], "inw") == 0 ||
> +        strcmp(words[0], "inl") == 0) {
> +        uint16_t addr;
> +        uint32_t value = -1U;
> +
> +        g_assert(words[1]);
> +        addr = strtol(words[1], NULL, 0);
> +
> +        if (words[0][2] == 'b') {
> +            value = cpu_inb(addr);
> +        } else if (words[0][2] == 'w') {
> +            value = cpu_inw(addr);
> +        } else if (words[0][2] == 'l') {
> +            value = cpu_inl(addr);
> +        }
> +        qtest_send_prefix(chr);
> +        qtest_send(chr, "OK 0x%04x\n", value);
> +    } else if (strcmp(words[0], "read") == 0) {
> +        uint64_t addr, len, i;
> +        uint8_t *data;
> +
> +        g_assert(words[1] && words[2]);
> +        addr = strtoul(words[1], NULL, 0);
> +        len = strtoul(words[2], NULL, 0);
> +
> +        data = g_malloc(len);
> +        cpu_physical_memory_read(addr, data, len);
> +
> +        qtest_send_prefix(chr);
> +        qtest_send(chr, "OK 0x");
> +        for (i = 0; i < len; i++) {
> +            qtest_send(chr, "%02x", data[i]);
> +        }
> +        qtest_send(chr, "\n");
> +
> +        g_free(data);
> +    } else if (strcmp(words[0], "write") == 0) {
> +        uint64_t addr, len, i;
> +        uint8_t *data;
> +        size_t data_len;
> +
> +        g_assert(words[1] && words[2] && words[3]);
> +        addr = strtoul(words[1], NULL, 0);
> +        len = strtoul(words[2], NULL, 0);
> +
> +        data_len = strlen(words[3]);
> +        if (data_len < 3) {
> +            qtest_send(chr, "ERR invalid argument size\n");
> +            return;
> +        }
> +
> +        data = g_malloc(len);
> +        for (i = 0; i < len; i++) {
> +            if ((i * 2 + 4) <= data_len) {
> +                data[i] = hex2nib(words[3][i * 2 + 2]) << 4;
> +                data[i] |= hex2nib(words[3][i * 2 + 3]);
> +            } else {
> +                data[i] = 0;
> +            }
> +        }
> +        cpu_physical_memory_write(addr, data, len);
> +        g_free(data);
> +
> +        qtest_send_prefix(chr);
> +        qtest_send(chr, "OK\n");
> +    } else {
> +        fprintf(stderr, "Unknown command `%s'\n", words[0]);
> +    }
> +}
> +
> +static void qtest_process_inbuf(CharDriverState *chr, GString *inbuf) +{
> +    char *end;
> +
> +    while ((end = strchr(inbuf->str, '\n')) != NULL) {
> +        size_t offset;
> +        GString *cmd;
> +        gchar **words;
> +
> +        offset = end - inbuf->str;
> +
> +        cmd = g_string_new_len(inbuf->str, offset);
> +        g_string_erase(inbuf, 0, offset + 1);
> +
> +        words = g_strsplit(cmd->str, " ", 0);
> +        qtest_process_command(chr, words);
> +        g_strfreev(words);
> +
> +        g_string_free(cmd, TRUE);
> +    }
> +}
> +
> +static void qtest_read(void *opaque, const uint8_t *buf, int size) +{
> +    CharDriverState *chr = opaque;
> +
> +    g_string_append_len(inbuf, (const gchar *)buf, size);
> +    qtest_process_inbuf(chr, inbuf);
> +}
> +
> +static int qtest_can_read(void *opaque)
> +{
> +    return 1024;
> +}
> +
> +static void qtest_event(void *opaque, int event)
> +{
> +    int i;
> +
> +    switch (event) {
> +    case CHR_EVENT_OPENED:
> +        qemu_system_reset(false);
> +        for (i = 0; i < ARRAY_SIZE(irq_levels); i++) {
> +            irq_levels[i] = 0;
> +        }
> +        gettimeofday(&start_time, NULL);
> +        qtest_opened = true;
> +        if (qtest_log_fp) {
> +            fprintf(qtest_log_fp, "[I " FMT_timeval "] OPENED\n", +    
               start_time.tv_sec, start_time.tv_usec);
> +        }
> +        break;
> +    case CHR_EVENT_CLOSED:
> +        qtest_opened = false;
> +        if (qtest_log_fp) {
> +            struct timeval tv;
> +            qtest_get_time(&tv);
> +            fprintf(qtest_log_fp, "[I +" FMT_timeval "] CLOSED\n", +   
                tv.tv_sec, tv.tv_usec);
> +        }
> +        break;
> +    default:
> +        break;
> +    }
> +}
> +
> +static void qtest_set_irq(void *opaque, int irq, int level)
> +{
> +    CharDriverState *chr = qtest_chr;
> +    bool changed;
> +
> +    changed = (irq_levels[irq] != level);
> +    irq_levels[irq] = level;
> +
> +    if (changed) {
> +        qtest_send_prefix(chr);
> +        qtest_send(chr, "IRQ %s %d\n",
> +                   level ? "raise" : "lower", irq);
> +    }
> +}
> +
> +qemu_irq *qtest_interrupt_controller_init(void)
> +{
> +    return qemu_allocate_irqs(qtest_set_irq, NULL, MAX_IRQ);
> +}
> +
> +int qtest_init(void)
> +{
> +    CharDriverState *chr;
> +
> +    g_assert(qtest_chrdev != NULL);
> +
> +    chr = qemu_chr_new("qtest", qtest_chrdev, NULL);
chr may be NULL


> +
> +    qemu_chr_add_handlers(chr, qtest_can_read, qtest_read, qtest_event,
chr);
> +
> +    inbuf = g_string_new("");
> +
> +    if (qtest_log) {
> +        if (strcmp(qtest_log, "none") != 0) {
> +            qtest_log_fp = fopen(qtest_log, "w+");
> +        }
> +    } else {
> +        qtest_log_fp = stderr;
> +    }
> +
> +    qtest_chr = chr;
> +
> +    return 0;
> +}
> diff --git a/qtest.h b/qtest.h
> new file mode 100644
> index 0000000..f0e1377
> --- /dev/null
> +++ b/qtest.h
> @@ -0,0 +1,37 @@
> +/*
> + * Test Server
> + *
> + * Copyright IBM, Corp. 2011
> + *
> + * Authors:
> + *  Anthony Liguori   <aliguori@us.ibm.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.
> + *
> + */
> +
> +#ifndef QTEST_H
> +#define QTEST_H
> +
> +#include "qemu-common.h"
> +
> +extern int qtest_allowed;
> +extern const char *qtest_chrdev;
> +extern const char *qtest_log;
> +
> +static inline bool qtest_enabled(void)
> +{
> +    return qtest_allowed;
> +}
> +
> +static inline int qtest_available(void)
> +{
> +    return 1;
> +}
> +
> +int qtest_init(void);
> +
> +qemu_irq *qtest_interrupt_controller_init(void);
> +
> +#endif
> diff --git a/vl.c b/vl.c
> index ba55b35..58fb5d9 100644
> --- a/vl.c
> +++ b/vl.c
> @@ -152,6 +152,7 @@ int main(int argc, char **argv)
>  #ifdef CONFIG_VIRTFS
>  #include "fsdev/qemu-fsdev.h"
>  #endif
> +#include "qtest.h"
>  #include "disas.h"
> @@ -1988,6 +1989,7 @@ static struct {
>      { "tcg", "tcg", tcg_available, tcg_init, &tcg_allowed },
>      { "xen", "Xen", xen_available, xen_init, &xen_allowed },
>      { "kvm", "KVM", kvm_available, kvm_init, &kvm_allowed },
> +    { "qtest", "QTest", qtest_available, qtest_init, &qtest_allowed },
>  };
>  static int configure_accelerator(void)
> @@ -3053,6 +3055,12 @@ int main(int argc, char **argv, char **envp)
>                      fclose(fp);
>                      break;
>                  }
> +            case QEMU_OPTION_qtest:
> +                qtest_chrdev = optarg;
> +                break;
> +            case QEMU_OPTION_qtest_log:
> +                qtest_log = optarg;
> +                break;
>              default:
>                  os_parse_cmd_args(popt->index, optarg);
>              }
> --
> 1.7.4.1

-- 
michael

^ permalink raw reply	[flat|nested] 25+ messages in thread

* Re: [Qemu-devel] [PATCH 1/6] qtest: add test framework
  2012-01-17 11:13     ` Stefan Hajnoczi
@ 2012-01-18  9:05       ` Stefan Hajnoczi
  0 siblings, 0 replies; 25+ messages in thread
From: Stefan Hajnoczi @ 2012-01-18  9:05 UTC (permalink / raw)
  To: Anthony Liguori
  Cc: Kevin Wolf, Anthony Liguori, Stefan Hajnoczi, qemu-devel,
	Avi Kivity, Paolo Bonzini

On Tue, Jan 17, 2012 at 11:13 AM, Stefan Hajnoczi <stefanha@gmail.com> wrote:
> On Mon, Jan 16, 2012 at 5:08 PM, Anthony Liguori <anthony@codemonkey.ws> wrote:
>> On 01/16/2012 10:59 AM, Stefan Hajnoczi wrote:
>>>
>>> On Fri, Jan 13, 2012 at 6:32 PM, Anthony Liguori<aliguori@us.ibm.com>
>>>  wrote:
>>>>
>>>> +    if (strcmp(words[0], "outb") == 0 ||
>>>> +        strcmp(words[0], "outw") == 0 ||
>>>> +        strcmp(words[0], "outl") == 0) {
>>>> +        uint16_t addr;
>>>> +        uint32_t value;
>>>> +
>>>> +        g_assert(words[1]&&  words[2]);
>>>> +        addr = strtol(words[1], NULL, 0);
>>>> +        value = strtol(words[2], NULL, 0);
>>>> +
>>>> +        if (words[0][3] == 'b') {
>>>> +            cpu_outb(addr, value);
>>>> +        } else if (words[0][3] == 'w') {
>>>> +            cpu_outw(addr, value);
>>>> +        } else if (words[0][3] == 'l') {
>>>> +            cpu_outl(addr, value);
>>>> +        }
>>>> +        qtest_send_prefix(chr);
>>>> +        qtest_send(chr, "OK\n");
>>>> +    } else if (strcmp(words[0], "inb") == 0 ||
>>>> +        strcmp(words[0], "inw") == 0 ||
>>>> +        strcmp(words[0], "inl") == 0) {
>>>> +        uint16_t addr;
>>>> +        uint32_t value = -1U;
>>>> +
>>>> +        g_assert(words[1]);
>>>> +        addr = strtol(words[1], NULL, 0);
>>>> +
>>>> +        if (words[0][2] == 'b') {
>>>> +            value = cpu_inb(addr);
>>>> +        } else if (words[0][2] == 'w') {
>>>> +            value = cpu_inw(addr);
>>>> +        } else if (words[0][2] == 'l') {
>>>> +            value = cpu_inl(addr);
>>>> +        }
>>>> +        qtest_send_prefix(chr);
>>>> +        qtest_send(chr, "OK 0x%04x\n", value);
>>>
>>>
>>> Endianness is a little weird here.  memory.c will byteswap if target
>>> and device endianness differ.
>>>
>>> Imagine the case where we're on an x86 host, running a ppc guest,
>>> reading from PCI configuration space (little-endian).
>>
>>
>> These functions expect to get host native endian.  The qtest wire protocol
>> is a string (which has no endianness) and converts it to host native endian.
>
> The cpu_inl() return value is not host native endian.  We perform a
> byteswap if the memory region endianness is different from the target
> endianness.

After our chat yesterday I inspected cpu_inl() under gdb.  You are
right that cpu_inl() returns a value in host endian.  The memory
region endianness is ignored and no byteswapping is performed.

This means qtests do not need to byteswap - it's already in the
correct endianness, no matter which device, target, or host.

Stefan

^ permalink raw reply	[flat|nested] 25+ messages in thread

* Re: [Qemu-devel] [PATCH 3/6] qtest: add C version of test infrastructure
  2012-01-13 18:32 ` [Qemu-devel] [PATCH 3/6] qtest: add C version of test infrastructure Anthony Liguori
  2012-01-17 11:33   ` Stefan Hajnoczi
  2012-01-17 16:09   ` Paolo Bonzini
@ 2012-01-18 16:00   ` Kevin Wolf
  2012-01-18 16:02     ` Paolo Bonzini
  2012-01-18 16:08     ` Anthony Liguori
  2 siblings, 2 replies; 25+ messages in thread
From: Kevin Wolf @ 2012-01-18 16:00 UTC (permalink / raw)
  To: Anthony Liguori; +Cc: Paolo Bonzini, qemu-devel, Stefan Hajnoczi

Am 13.01.2012 19:32, schrieb Anthony Liguori:
> This also includes a qtest wrapper script to make it easier to launch qtest
> tests directly.
> 
> Signed-off-by: Anthony Liguori <aliguori@us.ibm.com>

> +QTestState *qtest_init(const char *extra_args)
> +{
> +    QTestState *s;
> +    struct sockaddr_un addr;
> +    int sock, ret, i;
> +    gchar *socket_path;
> +    gchar *pid_file;
> +    gchar *command;
> +    const char *qemu_binary;
> +    pid_t pid;
> +
> +    qemu_binary = getenv("QTEST_QEMU_BINARY");
> +    g_assert(qemu_binary != NULL);
> +
> +    socket_path = g_strdup_printf("/tmp/qtest-%d.sock", getpid());
> +    pid_file = g_strdup_printf("/tmp/qtest-%d.pid", getpid());
> +
> +    s = g_malloc(sizeof(*s));
> +
> +    sock = socket(PF_UNIX, SOCK_STREAM, 0);
> +    g_assert_no_errno(sock);
> +
> +    addr.sun_family = AF_UNIX;
> +    snprintf(addr.sun_path, sizeof(addr.sun_path), "%s", socket_path);
> +
> +    pid = fork();
> +    if (pid == 0) {
> +        command = g_strdup_printf("%s "
> +                                  "-qtest unix:%s,server,nowait "
> +                                  "-qtest-log /dev/null "
> +                                  "-pidfile %s "
> +                                  "-machine accel=qtest "
> +                                  "%s", qemu_binary, socket_path,
> +                                  pid_file,
> +                                  extra_args ?: "");
> +
> +        ret = system(command);
> +        exit(ret);
> +        g_free(command);
> +    }
> +
> +    do {
> +        sleep(1);

This is the line that takes the greatest part of the time for make
check-qtest. Can we use some shorter delay if it's required at all?

> +        ret = connect(sock, (struct sockaddr *)&addr, sizeof(addr));
> +    } while (ret == -1);
> +    g_assert_no_errno(ret);
> +
> +    s->fd = sock;
> +    s->rx = g_string_new("");
> +    s->pid_file = pid_file;
> +    for (i = 0; i < MAX_IRQ; i++) {
> +        s->irq_level[i] = false;
> +    }
> +
> +    g_free(socket_path);
> +
> +    return s;
> +}

Kevin

^ permalink raw reply	[flat|nested] 25+ messages in thread

* Re: [Qemu-devel] [PATCH 3/6] qtest: add C version of test infrastructure
  2012-01-18 16:00   ` Kevin Wolf
@ 2012-01-18 16:02     ` Paolo Bonzini
  2012-01-18 16:08     ` Anthony Liguori
  1 sibling, 0 replies; 25+ messages in thread
From: Paolo Bonzini @ 2012-01-18 16:02 UTC (permalink / raw)
  To: Kevin Wolf; +Cc: Anthony Liguori, qemu-devel, Stefan Hajnoczi

On 01/18/2012 05:00 PM, Kevin Wolf wrote:
>> >  +    do {
>> >  +        sleep(1);
> This is the line that takes the greatest part of the time for make
> check-qtest. Can we use some shorter delay if it's required at all?

You can use a client socket, listen before spawning QEMU and accept 
afterwards.  It's what I did in the Python version.

Paolo

^ permalink raw reply	[flat|nested] 25+ messages in thread

* Re: [Qemu-devel] [PATCH 3/6] qtest: add C version of test infrastructure
  2012-01-18 16:00   ` Kevin Wolf
  2012-01-18 16:02     ` Paolo Bonzini
@ 2012-01-18 16:08     ` Anthony Liguori
  1 sibling, 0 replies; 25+ messages in thread
From: Anthony Liguori @ 2012-01-18 16:08 UTC (permalink / raw)
  To: Kevin Wolf; +Cc: Paolo Bonzini, qemu-devel, Stefan Hajnoczi

On 01/18/2012 10:00 AM, Kevin Wolf wrote:
> Am 13.01.2012 19:32, schrieb Anthony Liguori:
>> This also includes a qtest wrapper script to make it easier to launch qtest
>> tests directly.
>>
>> Signed-off-by: Anthony Liguori<aliguori@us.ibm.com>
>
>> +QTestState *qtest_init(const char *extra_args)
>> +{
>> +    QTestState *s;
>> +    struct sockaddr_un addr;
>> +    int sock, ret, i;
>> +    gchar *socket_path;
>> +    gchar *pid_file;
>> +    gchar *command;
>> +    const char *qemu_binary;
>> +    pid_t pid;
>> +
>> +    qemu_binary = getenv("QTEST_QEMU_BINARY");
>> +    g_assert(qemu_binary != NULL);
>> +
>> +    socket_path = g_strdup_printf("/tmp/qtest-%d.sock", getpid());
>> +    pid_file = g_strdup_printf("/tmp/qtest-%d.pid", getpid());
>> +
>> +    s = g_malloc(sizeof(*s));
>> +
>> +    sock = socket(PF_UNIX, SOCK_STREAM, 0);
>> +    g_assert_no_errno(sock);
>> +
>> +    addr.sun_family = AF_UNIX;
>> +    snprintf(addr.sun_path, sizeof(addr.sun_path), "%s", socket_path);
>> +
>> +    pid = fork();
>> +    if (pid == 0) {
>> +        command = g_strdup_printf("%s "
>> +                                  "-qtest unix:%s,server,nowait "
>> +                                  "-qtest-log /dev/null "
>> +                                  "-pidfile %s "
>> +                                  "-machine accel=qtest "
>> +                                  "%s", qemu_binary, socket_path,
>> +                                  pid_file,
>> +                                  extra_args ?: "");
>> +
>> +        ret = system(command);
>> +        exit(ret);
>> +        g_free(command);
>> +    }
>> +
>> +    do {
>> +        sleep(1);
>
> This is the line that takes the greatest part of the time for make
> check-qtest. Can we use some shorter delay if it's required at all?

Ah, good catch.  It should change to a usleep().

It is needed, you can't guarantee that qemu is listening yet on the sockets when 
you try to connect.

Regards,

Anthony Liguori

>
>> +        ret = connect(sock, (struct sockaddr *)&addr, sizeof(addr));
>> +    } while (ret == -1);
>> +    g_assert_no_errno(ret);
>> +
>> +    s->fd = sock;
>> +    s->rx = g_string_new("");
>> +    s->pid_file = pid_file;
>> +    for (i = 0; i<  MAX_IRQ; i++) {
>> +        s->irq_level[i] = false;
>> +    }
>> +
>> +    g_free(socket_path);
>> +
>> +    return s;
>> +}
>
> Kevin
>

^ permalink raw reply	[flat|nested] 25+ messages in thread

end of thread, other threads:[~2012-01-18 16:10 UTC | newest]

Thread overview: 25+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2012-01-13 18:32 [Qemu-devel] [PATCH 1/6] qtest: add test framework Anthony Liguori
2012-01-13 18:32 ` [Qemu-devel] [PATCH 2/6] qtest: add support for -M pc Anthony Liguori
2012-01-13 18:32 ` [Qemu-devel] [PATCH 3/6] qtest: add C version of test infrastructure Anthony Liguori
2012-01-17 11:33   ` Stefan Hajnoczi
2012-01-17 13:33     ` Paolo Bonzini
2012-01-17 13:39       ` Stefan Hajnoczi
2012-01-17 16:09   ` Paolo Bonzini
2012-01-18 16:00   ` Kevin Wolf
2012-01-18 16:02     ` Paolo Bonzini
2012-01-18 16:08     ` Anthony Liguori
2012-01-13 18:32 ` [Qemu-devel] [PATCH 4/6] make: add check targets based on gtester Anthony Liguori
2012-01-16 17:16   ` Paolo Bonzini
2012-01-16 18:14     ` Anthony Liguori
2012-01-17 10:42       ` Paolo Bonzini
2012-01-17 14:04   ` Paolo Bonzini
2012-01-17 14:22     ` Anthony Liguori
2012-01-13 18:32 ` [Qemu-devel] [PATCH 5/6] rtc: split out macros into a header file and use in test case Anthony Liguori
2012-01-13 18:32 ` [Qemu-devel] [PATCH 6/6] qtest: add rtc-test test-case Anthony Liguori
2012-01-16 16:59 ` [Qemu-devel] [PATCH 1/6] qtest: add test framework Stefan Hajnoczi
2012-01-16 17:08   ` Avi Kivity
2012-01-16 17:20     ` Anthony Liguori
2012-01-16 17:08   ` Anthony Liguori
2012-01-17 11:13     ` Stefan Hajnoczi
2012-01-18  9:05       ` Stefan Hajnoczi
  -- strict thread matches above, loose matches on Subject: below --
2012-01-17 17:22 Michael Walle

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).