summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAmit Shah <amit.shah@redhat.com>2009-10-20 11:11:35 +0530
committerAmit Shah <amit.shah@redhat.com>2009-10-20 11:11:35 +0530
commit5ed4f8465c53191cbb3e1e70690d895f42af2045 (patch)
tree8ff5dfac1727126160ecc62801cbfab185e75eea
parent5e920b11d41e6967ab804ee67b454b79335a8647 (diff)
downloadtest-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--Makefile6
-rw-r--r--README36
-rw-r--r--auto-virtserial-guest.c231
-rw-r--r--auto-virtserial.c731
-rwxr-xr-xrun-test.sh30
-rw-r--r--virtserial.h15
6 files changed, 1047 insertions, 2 deletions
diff --git a/Makefile b/Makefile
index 740c855..cceaecc 100644
--- a/Makefile
+++ b/Makefile
@@ -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
diff --git a/README b/README
new file mode 100644
index 0000000..45e2d11
--- /dev/null
+++ b/README
@@ -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;
+};