* [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
* 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 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 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
* [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
* 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 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 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
* [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 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 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 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 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 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
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).