diff options
author | Amit Shah <amit.shah@redhat.com> | 2009-10-20 11:11:35 +0530 |
---|---|---|
committer | Amit Shah <amit.shah@redhat.com> | 2009-10-20 11:11:35 +0530 |
commit | 5ed4f8465c53191cbb3e1e70690d895f42af2045 (patch) | |
tree | 8ff5dfac1727126160ecc62801cbfab185e75eea | |
parent | 5e920b11d41e6967ab804ee67b454b79335a8647 (diff) | |
download | test-virtserial-5ed4f8465c53191cbb3e1e70690d895f42af2045.tar.gz test-virtserial-5ed4f8465c53191cbb3e1e70690d895f42af2045.tar.xz test-virtserial-5ed4f8465c53191cbb3e1e70690d895f42af2045.zip |
Automated test suite for new virtio-console functionality
This commit adds programs that run automated tests in the guest.
One program (auto-virtserial.c) is to be run on the host and
another (auto-virtserial-guest.c) is to be run in the guest.
A README file is added that explains how to run them using the
supplied script (run-test.sh).
Signed-off-by: Amit Shah <amit.shah@redhat.com>
-rw-r--r-- | Makefile | 6 | ||||
-rw-r--r-- | README | 36 | ||||
-rw-r--r-- | auto-virtserial-guest.c | 231 | ||||
-rw-r--r-- | auto-virtserial.c | 731 | ||||
-rwxr-xr-x | run-test.sh | 30 | ||||
-rw-r--r-- | virtserial.h | 15 |
6 files changed, 1047 insertions, 2 deletions
@@ -1,7 +1,9 @@ -all: test-virtserial +CFLAGS=-Wall + +all: test-virtserial auto-virtserial auto-virtserial-guest $(CC) $(CFLAGS) -o $< $<.c -debug: test-virtserial +debug: test-virtserial auto-virtserial auto-virtserial-guest $(CC) $(CFLAGS) -DDEBUG=1 -o $< $<.c .PHONY: all debug @@ -0,0 +1,36 @@ +Test cases for testing the new virtio-console functionality that +exposes multiple ports to guests for generic communication. + +Files: +test-virtserial.c: Standalone test program that can be run in the + guest. +auto-virtserial.c: Runs automated tests. To be run on the host. +auto-virtserial-guest.c: Runs automated tests, to be run on the guest. +run-test.sh: Starts qemu and runs the automated tests + +Setup: +In my guest (f11-auto.qcow2), I've put the auto-virtserial-guest +program in /etc/init.d/ and put a line in /etc/rc.local to invoke it +upon boot: + +<guest /etc/rc.local>: +/etc/init.d/auto-virtserial-guest & + +When run-test.sh is invoked with such a guest, when the guest comes +up, the tests start running automatically. + +Some settings in the run-test.sh script should be tweaked to match +your environment (especially the guest image file, qemu location, +etc.). + +These programs are sensitive to some details, like the host program +(auto-virtserial) has to be started before the guest invokes +auto-virtserial-guest program. Also the parameters passed to the qemu +invocation in run-test.sh are assumed to be in that order in +auto-virtserial.c, like the ports which have caching enabled, +throttling enabled, the byte limits and so on. + +More work is needed to make these programs fully ready; if you wish +you contribute, send me an email at <amit.shah@redhat.com> + +Amit diff --git a/auto-virtserial-guest.c b/auto-virtserial-guest.c new file mode 100644 index 0000000..a5c0f57 --- /dev/null +++ b/auto-virtserial-guest.c @@ -0,0 +1,231 @@ +/* + * auto-virtserial-guest: Exercise various options for virtio-serial ports + * + * This program accepts commands from the controlling program on the + * host. It then spawns tests on the other virtio-serial ports as per + * the host's orders. + * + * Most of these tests have been described in the test-virtserial.c file + * + * Some information on the virtio serial ports is available at + * http://www.linux-kvm.org/page/VMchannel_Requirements + * https://fedoraproject.org/wiki/Features/VirtioSerial + * + * Copyright (C) 2009, Red Hat, Inc. + * + * Author(s): + * Amit Shah <amit.shah@redhat.com> + * + * Licensed under the GNU General Public License v2. See the file COPYING + * for more details. + */ + +#include <errno.h> +#include <error.h> +#include <fcntl.h> +#include <poll.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <sys/stat.h> +#include <sys/types.h> +#include "virtserial.h" + +#define CONTROL_PORT "/dev/vcon1" + +/* The fd to work with for read / write requests. Set by the open message */ +static int fd; +/* The length to read / write. Set by the length message. Unset at close */ +static int length; + +static char *get_port_dev(unsigned int nr) +{ + char *buf; + buf = malloc(strlen("/dev/vcon12") + 1); + if (!buf) + return NULL; + sprintf(buf, "/dev/vcon%u", nr); + return buf; +} + +static int open_port(int nr) +{ + char *buf; + + buf = get_port_dev(nr); + if (!buf) + return -ENOMEM; + fd = open(get_port_dev(nr), O_RDWR); + if (fd == -1) + return -errno; + return 0; +} + +static int poll_port(int timeout) +{ + struct pollfd pollfds[1]; + int ret; + + pollfds[0].fd = fd; + pollfds[0].events = POLLIN | POLLOUT; + ret = poll(pollfds, 1, timeout); + if (ret == -1) + return -errno; + if (ret == 0) + return ret; + return pollfds[0].revents; +} + +static int read_port(int nr) +{ + char *buf; + int ret; + + if (!length) { + /* + * Host just wants to read something. In this case, read + * a default length of 1024 bytes. + */ + length = 1024; + } + buf = malloc(length); + if (!buf) + return -ENOMEM; + ret = read(fd, buf, length); + free(buf); + if (ret == -1) + ret = -errno; + + return ret; +} + +static int write_port(int nr) +{ + char *buf; + int ret; + + if (!length) + return 0; + + buf = malloc(length); + if (!buf) + return -ENOMEM; + ret = write(fd, buf, length); + free(buf); + if (ret == -1) + ret = -errno; + + return ret; +} + +static int close_port(int nr) +{ + close(fd); + length = 0; + return 0; +} + +static int set_port_nonblocking(int val) +{ + int ret, flags; + + flags = 0; + if (val) + flags = O_NONBLOCK; + ret = fcntl(fd, F_SETFL, flags); + if (ret == -1) + return -errno; + return 0; +} + +static void send_report(int cfd, int ret) +{ + struct guest_packet gpkt; + + gpkt.key = KEY_RESULT; + gpkt.value = ret; + write(cfd, &gpkt, sizeof(gpkt)); +} + +int main(int argc, char *argv[]) +{ + struct guest_packet gpkt; + struct pollfd pollfd[1]; + int ret, cfd; + + ret = access(CONTROL_PORT, R_OK|W_OK); + if (ret == -1) + error(errno, errno, "No control port found %s", CONTROL_PORT); + +back_to_open: + cfd = open(CONTROL_PORT, O_RDWR); + if (cfd == -1) + error(errno, errno, "open control port %s", CONTROL_PORT); + + gpkt.key = KEY_STATUS_OK; + gpkt.value = 1; + ret = write(cfd, &gpkt, sizeof(gpkt)); + if (ret == -1) + error(errno, errno, "write control port"); + + pollfd[0].fd = cfd; + pollfd[0].events = POLLIN; + + while (1) { + ret = poll(pollfd, 1, -1); + if (ret == -1) + error(errno, errno, "poll"); + + if (!(pollfd[0].revents & POLLIN)) + continue; + + ret = read(cfd, &gpkt, sizeof(gpkt)); + if (ret < sizeof(gpkt)) { + /* + * Out of sync with host. Close port and start over. + * For us to get back in sync with host, this port + * has to have buffer cachin disabled + */ + close(cfd); + goto back_to_open; + } + switch(gpkt.key) { + /* case KEY_STATUS_OK: */ + /* gpkt.key = KEY_STATUS_OK; */ + /* gpkt.value = 1; */ + /* write(cfd, &gpkt, sizeof(gpkt)); */ + /* break; */ + case KEY_OPEN: + ret = open_port(gpkt.value); + send_report(cfd, ret); + break; + case KEY_CLOSE: + ret = close_port(gpkt.value); + send_report(cfd, ret); + break; + case KEY_READ: + ret = read_port(gpkt.value); + send_report(cfd, ret); + break; + case KEY_NONBLOCK: + ret = set_port_nonblocking(gpkt.value); + send_report(cfd, ret); + break; + case KEY_LENGTH: + length = gpkt.value; + send_report(cfd, 0); + break; + case KEY_WRITE: + ret = write_port(gpkt.value); + send_report(cfd, ret); + break; + case KEY_POLL: + ret = poll_port(gpkt.value); + send_report(cfd, ret); + break; + } + } + return 0; +} diff --git a/auto-virtserial.c b/auto-virtserial.c new file mode 100644 index 0000000..dd7fb00 --- /dev/null +++ b/auto-virtserial.c @@ -0,0 +1,731 @@ +/* + * auto-virtserial: Exercise various options for virtio-serial ports + * + * This program is the controlling program to be run on the host. + * + * Assumptions: + * - The first port, that'll get seen as /dev/vcon1, will be the port + * for control information + * - The second port (vcon2) is started with no extra params (cache_buffers=1) + * - The third port (vcon3) is started with cache_buffers=0 + * - The fourth port (vcon4) is started with host and guest limits to 1MB. + * + * Most of these tests have been described in the test-virtserial.c file + * + * Some information on the virtio serial ports is available at + * http://www.linux-kvm.org/page/VMchannel_Requirements + * https://fedoraproject.org/wiki/Features/VirtioSerial + * + * Copyright (C) 2009, Red Hat, Inc. + * + * Author(s): + * Amit Shah <amit.shah@redhat.com> + * + * Licensed under the GNU General Public License v2. See the file COPYING + * for more details. + */ + +#include <errno.h> +#include <error.h> +#include <fcntl.h> +#include <poll.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <sys/socket.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <sys/un.h> +#include "virtserial.h" + +#define DEBUG 1 + +#ifdef DEBUG +#define debug(fmt, ...) \ + do { \ + printf(fmt, __VA_ARGS__); \ + } while(0) +#else +#define debug(fmt, ...) do { } while (0) +#endif + +/* + * We test virtio-serial with 4 ports running at the same time. + * Port #0, as per tradition, is reserved for virtio-console + */ +#define NR_PORTS 5 + +#define BUF_LENGTH 4080 +#define UNIX_PATH_MAX 108 + +static int socks[NR_PORTS]; +static unsigned int nr_passed, nr_failed; + +static void handle_guest_error(struct guest_packet *gpkt) +{ + char *buf; + + buf = malloc(gpkt->value); + if (!buf) + error(ENOMEM, ENOMEM, "Guest err"); + read(socks[1], buf, gpkt->value); + fprintf(stderr, "guest error: %s\n", buf); + free(buf); +} + +static int host_connect_chardev(const char *path, int nr) +{ + struct sockaddr_un sock; + int ret; + + socks[nr] = socket(AF_UNIX, SOCK_STREAM, 0); + if (socks[nr] == -1) + error(errno, errno, "socket %d", nr); + + sock.sun_family = AF_UNIX; + memcpy(&sock.sun_path, path, sizeof(sock.sun_path)); + ret = connect(socks[nr], (struct sockaddr *)&sock, sizeof(sock)); + if (ret == -1) + error(errno, errno, "connect: %s", path); + return 0; +} + +static int host_close_chardev(int nr) +{ + return close(socks[nr]); +} + +static int get_guest_response(struct guest_packet *gpkt) +{ + return read(socks[1], gpkt, sizeof(*gpkt)); +} + +static int guest_cmd_only(struct guest_packet *gpkt) +{ + return write(socks[1], gpkt, sizeof(*gpkt)); +} + +static int guest_cmd(struct guest_packet *gpkt) +{ + write(socks[1], gpkt, sizeof(*gpkt)); + get_guest_response(gpkt); + if (gpkt->key == KEY_RESULT) + return gpkt->value; + return -1; +} + +static int guest_open_port(int nr) +{ + struct guest_packet gpkt; + + gpkt.key = KEY_OPEN; + gpkt.value = nr; + return guest_cmd(&gpkt); +} + +static int guest_close_port(int nr) +{ + struct guest_packet gpkt; + + gpkt.key = KEY_CLOSE; + gpkt.value = nr; + return guest_cmd(&gpkt); +} + +static int guest_set_length(int nr, int len) +{ + struct guest_packet gpkt; + + gpkt.key = KEY_LENGTH; + gpkt.value = len; + return guest_cmd(&gpkt); +} + +static int guest_read(int nr, int len) +{ + struct guest_packet gpkt; + int ret; + + ret = guest_set_length(nr, len); + if (ret) + return ret; + + gpkt.key = KEY_READ; + gpkt.value = nr; + + return guest_cmd(&gpkt); +} + +static int guest_write(int nr, int len) +{ + struct guest_packet gpkt; + int ret; + + ret = guest_set_length(nr, len); + if (ret) + return ret; + + gpkt.key = KEY_WRITE; + gpkt.value = nr; + + return guest_cmd(&gpkt); +} + +static int guest_poll(int nr, int timeout) +{ + struct guest_packet gpkt; + + gpkt.key = KEY_POLL; + gpkt.value = timeout; + return guest_cmd(&gpkt); +} + +static int guest_set_port_nonblocking(int nr, int value) +{ + struct guest_packet gpkt; + + gpkt.key = KEY_NONBLOCK; + gpkt.value = value; + return guest_cmd(&gpkt); +} + +static void show_stats(void) +{ + fprintf(stderr, "-----\n"); + fprintf(stderr, "Total tests passed: %u\n", nr_passed); + fprintf(stderr, "Total tests failed: %u\n", nr_failed); + fprintf(stderr, "-----\n"); +} + +static void pass(const char *msg, const char *subtype) +{ + fprintf(stderr, "%s(%s):\t\tPASS\n", msg, subtype); + nr_passed++; +} + +static void fail(const char *msg, const char *subtype) +{ + fprintf(stderr, "%s(%s):\t\tFAIL\n", msg, subtype); + nr_failed++; +} + +static int test_open(int nr) +{ + int ret; + + ret = guest_open_port(nr); + if (ret) { + fail(__func__, "open"); + debug("%s: return: %d\n", __func__, ret); + } else { + pass(__func__, "open"); + } + return ret; +} + +static int test_close(int nr) +{ + int ret; + + ret = guest_close_port(nr); + if (ret) { + fail(__func__, "close"); + debug("%s: return: %d\n", __func__, ret); + } else { + pass(__func__, "close"); + } + return ret; +} + +/* Reads should return 0 when host chardev isn't connected */ +static int test_read_without_host(int nr) +{ + int ret; + + ret = guest_open_port(nr); + if (ret) { + fail(__func__, "open"); + debug("%s: open: %d\n", __func__, ret); + return ret; + } + ret = guest_read(nr, 0); + if (ret) + fail(__func__, "read"); + else + pass(__func__, "read"); + guest_close_port(nr); + return ret; +} + +static int test_blocking_read(const char *path, int nr) +{ + struct guest_packet gpkt; + struct pollfd pollfd[1]; + int ret; + + ret = guest_open_port(nr); + if (ret) { + fail(__func__, "open"); + debug("%s: open: %d\n", __func__, ret); + return ret; + } + host_connect_chardev(path, nr); + + guest_set_length(nr, sizeof(gpkt)); + /* Reads should block now that the host is connected */ + gpkt.key = KEY_READ; + gpkt.value = nr; + guest_cmd_only(&gpkt); + + pollfd[0].fd = socks[1]; + pollfd[0].events = POLLIN; + /* See if we get something in 5s -- we shouldn't. */ + ret = poll(pollfd, 1, 5000); + if (ret == -1) + error(errno, errno, "%s: poll", __func__); + if (ret) { + fail(__func__, "poll"); + debug("%s: poll: %d\n", __func__, ret); + ret = -1; + goto out; + } + /* Write out anything -- doesn't matter what it is */ + write(socks[nr], &gpkt, sizeof(gpkt)); + ret = poll(pollfd, 1, 5000); + if (ret != 1) { + fail(__func__, "poll"); + debug("%s: no response from guest\n", __func__); + ret = -1; + goto out; + } + get_guest_response(&gpkt); + if (gpkt.key != KEY_RESULT) + error(EINVAL, EINVAL, "%s: guest response\n", __func__); + if (gpkt.value == sizeof(gpkt)) { + pass(__func__, "read"); + ret = 0; + } else { + fail(__func__, "read"); + debug("%s: expected return %zu, got %d\n", + __func__, sizeof(gpkt), gpkt.value); + ret = -EINVAL; + } +out: + guest_close_port(nr); + host_close_chardev(nr); + return ret; +} + +static int test_nonblocking_read(const char *path, int nr) +{ + struct guest_packet gpkt; + struct pollfd pollfd[1]; + int ret; + + ret = guest_open_port(nr); + if (ret) { + fail(__func__, "open"); + debug("%s: open: %d\n", __func__, ret); + return ret; + } + host_connect_chardev(path, nr); + + ret = guest_set_port_nonblocking(nr, true); + if (ret) { + fail(__func__, "blocking"); + debug("%s: blocking: %d\n", __func__, -ret); + goto out; + } + guest_set_length(nr, sizeof(gpkt)); + /* Reads should block now that the host is connected */ + gpkt.key = KEY_READ; + gpkt.value = nr; + guest_cmd_only(&gpkt); + + pollfd[0].fd = socks[1]; + pollfd[0].events = POLLIN; + /* See if we get something in 5s -- we should. */ + ret = poll(pollfd, 1, 5000); + if (ret == -1) + error(errno, errno, "%s: poll", __func__); + if (!ret) { + fail(__func__, "poll"); + debug("%s: poll: %d\n", __func__, ret); + ret = -1; + goto out; + } + get_guest_response(&gpkt); + if (gpkt.value != -EAGAIN) { + fail(__func__, "guest response"); + debug("%s: Expected %d got %d\n", + __func__, -EAGAIN, gpkt.value); + } + /* Write out anything -- doesn't matter what it is */ + write(socks[nr], &gpkt, sizeof(gpkt)); + + ret = guest_read(nr, sizeof(gpkt)); + if (ret == sizeof(gpkt)) { + pass(__func__, "read"); + ret = 0; + } else { + fail(__func__, "read"); + debug("%s: expected return %zu, got %d\n", + __func__, sizeof(gpkt), gpkt.value); + ret = -EINVAL; + } +out: + guest_close_port(nr); + host_close_chardev(nr); + return ret; +} + +static int test_poll(const char *path, int nr) +{ + int ret; + + guest_open_port(nr); + ret = guest_poll(nr, 0); + if (ret < 0) { + fail(__func__, "open"); + debug("%s: poll error: %d\n", __func__, ret); + goto out; + } + if (ret != POLLHUP) { + fail(__func__, "POLLHUP"); + debug("%s: poll ret: 0x%x, expected 0x%x\n", + __func__, ret, POLLHUP); + goto out; + } + pass(__func__, "POLLHUP"); + + host_connect_chardev(path, nr); + ret = guest_poll(nr, 0); + if (ret < 0) { + fail(__func__, "poll"); + debug("%s: poll error: %d\n", __func__, ret); + goto out_close; + } else if (ret == POLLOUT) { + pass(__func__, "POLLOUT"); + } else { + fail(__func__, "POLLOUT"); + debug("%s: poll ret: 0x%x, expected 0x%x\n", + __func__, ret, POLLOUT); + goto out_close; + } + write(socks[nr], &ret, sizeof(ret)); + ret = guest_poll(nr, 0); + if (ret < 0) { + fail(__func__, "POLL"); + debug("%s: poll error: %d\n", __func__, ret); + goto out_flush; + } else if (ret == (POLLIN|POLLOUT)) { + pass(__func__, "POLLIN"); + } else { + fail(__func__, "POLLIN"); + debug("%s: poll ret: 0x%d, expected 0x%d\n", + __func__, ret, POLLIN|POLLOUT); + goto out_flush; + } + ret = 0; +out_flush: + guest_read(nr, sizeof(ret)); +out_close: + host_close_chardev(nr); +out: + guest_close_port(nr); + return ret; +} + +/* + * Tests if writes to guest get throttled after sending 1M data + * The invert parameter specifies the test should pass for ports + * that don't have throttling enabled + */ +static int test_guest_throttle(const char *path, int nr, bool enabled) +{ + char *buf; + struct pollfd pollfd[1]; + size_t size, copied; + int ret; + + /* Open guest */ + ret = guest_open_port(nr); + if (ret) { + fail(__func__, "open"); + debug("%s: open: %d\n", __func__, ret); + return ret; + } + host_connect_chardev(path, nr); + + buf = malloc(BUF_LENGTH); + if (!buf) + error(ENOMEM, ENOMEM, "%s\n", __func__); + + pollfd[0].fd = socks[nr]; + pollfd[0].events = POLLOUT; + ret = poll(pollfd, 1, 0); + if (ret == -1) + error(errno, errno, "%s: poll\n", __func__); + if (!(pollfd[0].revents & POLLOUT)) + error(ENOSPC, ENOSPC, "%s: can't write\n", __func__); + + copied = 0; + do { + size = write(socks[nr], buf, BUF_LENGTH); + if (size == -1) + error(errno, errno, "%s: write", __func__); + copied += size; + + ret = poll(pollfd, 1, 2000); + if (ret == -1) + error(errno, errno, "%s: poll\n", __func__); + } while ((pollfd[0].revents & POLLOUT) && (copied < 1048576)); + + if ((pollfd[0].revents & POLLOUT) && !enabled) { + pass(__func__, "disabled"); + } else if (!(pollfd[0].revents & POLLOUT) && enabled) { + pass(__func__, "enabled"); + } else { + fail(__func__, "throttle"); + debug("%s: ret = %d, copied = %zu\n", __func__, ret, copied); + } + /* Cleanup - free all the buffers that were sent */ + while (guest_poll(nr, 0) & POLLIN) { + guest_read(nr, BUF_LENGTH); + } + guest_close_port(nr); + host_close_chardev(nr); + free(buf); + return 0; +} + +static int test_host_throttle(const char *path, int nr, bool enabled) +{ + char *buf; + struct pollfd pollfds[1]; + size_t copied; + int ret; + + ret = guest_open_port(nr); + + copied = 0; + do { + ret = guest_write(nr, BUF_LENGTH); + if (ret > 0) + copied += ret; + } while ((copied < 1048576) && (ret > 0)); + + if (ret && !enabled) { + pass(__func__, "disabled"); + } else if (!(ret == -ENOSPC) && enabled) { + pass(__func__, "enabled"); + } else { + fail(__func__, "throttle"); + debug("%s: ret = %d, copied = %zu\n", __func__, ret, copied); + } + + host_connect_chardev(path, nr); + buf = malloc(BUF_LENGTH); + if (!buf) + error(ENOMEM, ENOMEM, "%s", __func__); + pollfds[0].fd = socks[nr]; + pollfds[0].events = POLLIN; + while ((poll(pollfds, 1, 0) == 1) && (pollfds[0].revents & POLLIN)) + read(socks[nr], buf, BUF_LENGTH); + free(buf); + + host_close_chardev(nr); + guest_close_port(nr); + return ret; +} + +static int test_guest_caching(const char *path, int nr, bool enabled) +{ + char *buf; + int ret; + + ret = guest_open_port(nr); + if (ret) { + fail(__func__, "open"); + debug("%s: open: %d\n", __func__, ret); + return ret; + } + host_connect_chardev(path, nr); + + buf = malloc(BUF_LENGTH); + if (!buf) + error(ENOMEM, ENOMEM, "%s\n", __func__); + + ret = write(socks[nr], buf, BUF_LENGTH); + if (ret == -1) + error(errno, errno, "%s: write", __func__); + + ret = guest_close_port(nr); + host_close_chardev(nr); + free(buf); + + ret = guest_open_port(nr); + ret = guest_read(nr, BUF_LENGTH); + if (enabled && (ret == BUF_LENGTH)) { + pass(__func__, "disabled"); + } else if (!enabled && !ret) { + pass(__func__, "enabled"); + } else { + fail(__func__, "caching"); + debug("%s: ret = %d\n", __func__, ret); + } + guest_close_port(nr); + return 0; +} + +static int test_host_caching(const char *path, int nr, bool enabled) +{ + int ret; + char *buf; + struct pollfd pollfds[1]; + + guest_open_port(nr); + ret = guest_write(nr, BUF_LENGTH); + if (ret != BUF_LENGTH) { + fail(__func__, "write"); + debug("%s: guest_write return: %d\n", __func__, ret); + goto out; + } + host_connect_chardev(path, nr); + + pollfds[0].fd = socks[nr]; + pollfds[0].events = POLLIN; + ret = poll(pollfds, 1, 2000); + if (ret < 0) { + fail(__func__, "poll"); + debug("%s: poll returned %d, revents %d\n", + __func__, ret, pollfds[0].revents); + goto out; + } + if (!enabled && ret > 0) { + fail(__func__, "disabled"); + debug("%s: ret = %d, revents = %d\n", + __func__, ret, pollfds[0].revents); + goto out; + } + if (enabled && ret != 1) { + fail(__func__, "enabled"); + debug("%s: ret = %d, revents = %d\n", + __func__, ret, pollfds[0].revents); + goto out; + } + if (!enabled) { + pass(__func__, "enabled"); + goto out; + } + buf = malloc(BUF_LENGTH); + ret = read(socks[nr], buf, BUF_LENGTH); + free(buf); + if (ret == BUF_LENGTH) { + pass(__func__, "enabled"); + } else { + fail(__func__, "caching"); + if (ret == -1) + ret = -errno; + debug("%s: read %d\n", __func__, ret); + } +out: + host_close_chardev(nr); + guest_close_port(nr); + return ret; +} + +static int start_tests(const char *char1, const char *char2, + const char *char3, const char *char4) +{ + test_open(2); + test_close(2); + test_read_without_host(2); + + test_blocking_read(char2, 2); + test_nonblocking_read(char2, 2); + + test_poll(char2, 2); + + /* Throttling is not enabled on this port */ + test_guest_throttle(char2, 2, false); + /* Throttling is enabled on this port */ + test_guest_throttle(char4, 4, true); + + /* Throttling is not enabled on this port */ + test_host_throttle(char2, 2, false); + /* Throttling is enabled on this port */ + test_host_throttle(char4, 4, true); + + /* Caching is enabled on this port */ + test_guest_caching(char2, 2, true); + /* Caching is not enabled on this port */ + test_guest_caching(char3, 3, false); + + /* Caching is enabled on this port */ + test_host_caching(char2, 2, true); + /* Caching is not enabled on this port */ + test_host_caching(char3, 3, false); + + return 0; +} + +int main(int argc, const char *argv[]) +{ + struct guest_packet gpkt; + struct pollfd pollfd[1]; + int i, ret; + + if (argc < NR_PORTS) { + errno = EINVAL; + error(errno, errno, "Usage: %s <unix-1> <unix-2> <unix-3>\n", + argv[0]); + } + /* Check if host char drvs are ok */ + for (i = 1; i < NR_PORTS; i++) { + ret = access(argv[i], R_OK|W_OK); + if (ret) + error(errno, errno, "access %s", argv[i]); + if (strlen(argv[i]) > UNIX_PATH_MAX) + error(E2BIG, E2BIG, "%s", argv[i]); + } + host_connect_chardev(argv[1], 1); + /* + * Send a message to the guest indicating we're ready. If the + * guest isn't ready yet, it'll connect and let us know. + */ + gpkt.key = KEY_STATUS_OK; + gpkt.value = 1; + ret = write(socks[1], &gpkt, sizeof(gpkt)); + if (ret == -1) + error(errno, errno, "write %s", argv[1]); + /* Now wait till we receive guest's response */ + pollfd[0].fd = socks[1]; + pollfd[0].events = POLLIN; + while (1) { + ret = poll(pollfd, 1, -1); + if (ret == -1) + error(errno, errno, "poll %s", argv[1]); + debug("poll revents = %u\n", pollfd[0].revents); + if (pollfd[0].revents & POLLIN) { + ret = read(socks[1], &gpkt, sizeof(gpkt)); + if (ret < sizeof(gpkt)) + error(EINVAL, EINVAL, "Read error"); + if (gpkt.key == KEY_STATUS_OK && gpkt.value) { + debug("Guest is up %d\n", gpkt.key); + break; + } + if (gpkt.key == KEY_GUEST_ERR) { + handle_guest_error(&gpkt); + error(EINVAL, EINVAL, "Guest error"); + } + } + } + /* Now we're all set to start our tests. */ + start_tests(argv[1], argv[2], argv[3], argv[4]); + show_stats(); + host_close_chardev(1); + + return nr_failed; +} diff --git a/run-test.sh b/run-test.sh new file mode 100755 index 0000000..083eeb9 --- /dev/null +++ b/run-test.sh @@ -0,0 +1,30 @@ +#!/bin/bash + +QEMU=/home/amit/src/qemu-kvm/x86_64-softmmu/qemu-system-x86_64 +GUEST=/guests/f11-auto.qcow2 + +KERNEL="-kernel /home/amit/tmp/linux-2.6/arch/x86/boot/bzImage" +KERNELARG='-append "root=/dev/sda2"' + +CHARDEVS="-chardev socket,path=/tmp/amit/test1,server,nowait,id=test1 \ + -chardev socket,path=/tmp/amit/test2,server,nowait,id=test2 \ + -chardev socket,path=/tmp/amit/test3,server,nowait,id=test3 \ + -chardev socket,path=/tmp/amit/test4,server,nowait,id=test4" +VIRTSER="-device virtio-serial-pci \ + -device virtserialport,chardev=test1,cache_buffers=0,name=test1 \ + -device virtserialport,chardev=test2,name=test2 \ + -device virtserialport,cache_buffers=0,chardev=test3,name=test3 \ + -device virtserialport,byte_limit=1048576,guest_byte_limit=1048576,chardev=test4,name=test4" +VNC="-vnc :1" +MISCOPT="-net none -enable-kvm -m 1G -smp 2" +SNAPSHOT="-snapshot" + +QEMU_OPTS="$KERNEL $KERNELARG $CHARDEVS $VIRTSER $VNC $MISCOPT $GUEST $SNAPSHOT" + +echo $QEMU $QEMU_OPTS + +$QEMU $QEMU_OPTS & + +sleep 5 + +./auto-virtserial /tmp/amit/test1 /tmp/amit/test2 /tmp/amit/test3 /tmp/amit/test4 diff --git a/virtserial.h b/virtserial.h new file mode 100644 index 0000000..120df38 --- /dev/null +++ b/virtserial.h @@ -0,0 +1,15 @@ +#define KEY_STATUS_OK 1 +#define KEY_GUEST_ERR 2 +#define KEY_OPEN 3 +#define KEY_CLOSE 4 +#define KEY_RESULT 5 +#define KEY_READ 6 +#define KEY_NONBLOCK 7 +#define KEY_LENGTH 8 +#define KEY_WRITE 9 +#define KEY_POLL 10 + +struct guest_packet { + unsigned int key; + int value; +}; |