diff options
author | Richard W.M. Jones <rjones@redhat.com> | 2011-12-22 08:40:30 +0000 |
---|---|---|
committer | Richard W.M. Jones <rjones@redhat.com> | 2011-12-22 13:04:38 +0000 |
commit | 5d93d70b4d36b2337104b3dbca07722fa4d47ff5 (patch) | |
tree | 792a9b58304dbe311d71a34c048cd941c64c2127 /tests/c-api/test-user-cancel.c | |
parent | 72f5487be2df82277021d61f01884381ed9efbc5 (diff) | |
download | libguestfs-5d93d70b4d36b2337104b3dbca07722fa4d47ff5.tar.gz libguestfs-5d93d70b4d36b2337104b3dbca07722fa4d47ff5.tar.xz libguestfs-5d93d70b4d36b2337104b3dbca07722fa4d47ff5.zip |
tests: Rename capitests -> tests/c-api.
Diffstat (limited to 'tests/c-api/test-user-cancel.c')
-rw-r--r-- | tests/c-api/test-user-cancel.c | 371 |
1 files changed, 371 insertions, 0 deletions
diff --git a/tests/c-api/test-user-cancel.c b/tests/c-api/test-user-cancel.c new file mode 100644 index 00000000..0cc049a0 --- /dev/null +++ b/tests/c-api/test-user-cancel.c @@ -0,0 +1,371 @@ +/* libguestfs + * Copyright (C) 2011 Red Hat Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/* Test user cancellation. + * + * We perform the test using two threads. The main thread issues + * guestfs commands to download and upload large files. Uploads and + * downloads are done to/from a pipe which is connected back to the + * current process. The second test thread sits on the other end of + * the pipe, feeding or consuming data slowly, and injecting the user + * cancel events at a particular place in the transfer. + * + * It is important to test both download and upload separately, since + * these exercise different code paths in the library. However this + * adds complexity here because these tests are symmetric-but-opposite + * cases. + */ + +#include <config.h> + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <fcntl.h> +#include <errno.h> +#include <sys/time.h> +#include <math.h> + +#include <pthread.h> + +#include "guestfs.h" + +static const char *filename = "test.img"; +static const off_t filesize = 1024*1024*1024; + +static void remove_test_img (void); +static void *start_test_thread (void *); +static off_t random_cancel_posn (void); + +struct test_thread_data { + guestfs_h *g; /* handle */ + int direction; /* direction of transfer */ +#define DIRECTION_UP 1 /* upload (test thread is writing) */ +#define DIRECTION_DOWN 2 /* download (test thread is reading) */ + int fd; /* pipe to read/write */ + off_t cancel_posn; /* position at which to cancel */ + off_t transfer_size; /* how much data thread wrote/read */ +}; + +int +main (int argc, char *argv[]) +{ + guestfs_h *g; + int fd; + char c = 0; + pthread_t test_thread; + struct test_thread_data data; + int fds[2], r, op_error, op_errno, errors = 0; + char dev_fd[64]; + + srand48 (time (NULL)); + + g = guestfs_create (); + if (g == NULL) { + fprintf (stderr, "failed to create handle\n"); + exit (EXIT_FAILURE); + } + + /* Create a test image and test data. */ + fd = open (filename, O_WRONLY|O_CREAT|O_TRUNC|O_NOCTTY, 0666); + if (fd == -1) { + perror (filename); + exit (EXIT_FAILURE); + } + + atexit (remove_test_img); + + if (lseek (fd, filesize - 1, SEEK_SET) == (off_t) -1) { + perror ("lseek"); + close (fd); + exit (EXIT_FAILURE); + } + + if (write (fd, &c, 1) != 1) { + perror ("write"); + close (fd); + exit (EXIT_FAILURE); + } + + if (close (fd) == -1) { + perror ("test.img"); + exit (EXIT_FAILURE); + } + + if (guestfs_add_drive_opts (g, filename, + GUESTFS_ADD_DRIVE_OPTS_FORMAT, "raw", + -1) == -1) + exit (EXIT_FAILURE); + + if (guestfs_launch (g) == -1) + exit (EXIT_FAILURE); + + if (guestfs_part_disk (g, "/dev/sda", "mbr") == -1) + exit (EXIT_FAILURE); + + if (guestfs_mkfs (g, "ext2", "/dev/sda1") == -1) + exit (EXIT_FAILURE); + + if (guestfs_mount_options (g, "", "/dev/sda1", "/") == -1) + exit (EXIT_FAILURE); + + /*----- Upload cancellation test -----*/ + + data.g = g; + data.direction = DIRECTION_UP; + + if (pipe (fds) == -1) { + perror ("pipe"); + exit (EXIT_FAILURE); + } + + data.fd = fds[1]; + snprintf (dev_fd, sizeof dev_fd, "/dev/fd/%d", fds[0]); + + data.cancel_posn = random_cancel_posn (); + + /* Create the test thread. */ + r = pthread_create (&test_thread, NULL, start_test_thread, &data); + if (r != 0) { + fprintf (stderr, "pthread_create: %s", strerror (r)); + exit (EXIT_FAILURE); + } + + /* Do the upload. */ + op_error = guestfs_upload (g, dev_fd, "/upload"); + op_errno = guestfs_last_errno (g); + + /* Kill the test thread and clean up. */ + r = pthread_cancel (test_thread); + if (r != 0) { + fprintf (stderr, "pthread_cancel: %s", strerror (r)); + exit (EXIT_FAILURE); + } + r = pthread_join (test_thread, NULL); + if (r != 0) { + fprintf (stderr, "pthread_join: %s", strerror (r)); + exit (EXIT_FAILURE); + } + + close (fds[0]); + close (fds[1]); + + /* We expect to get an error, with errno == EINTR. */ + if (op_error == -1 && op_errno == EINTR) + printf ("test-user-cancel: upload cancellation test passed (%ld/%ld)\n", + (long) data.cancel_posn, (long) data.transfer_size); + else { + fprintf (stderr, "test-user-cancel: upload cancellation test FAILED\n"); + fprintf (stderr, "cancel_posn %ld, upload returned %d, errno = %d (%s)\n", + (long) data.cancel_posn, op_error, op_errno, strerror (op_errno)); + errors++; + } + + if (guestfs_rm (g, "/upload") == -1) + exit (EXIT_FAILURE); + + /*----- Download cancellation test -----*/ + + if (guestfs_touch (g, "/download") == -1) + exit (EXIT_FAILURE); + + if (guestfs_truncate_size (g, "/download", filesize/4) == -1) + exit (EXIT_FAILURE); + + data.g = g; + data.direction = DIRECTION_DOWN; + + if (pipe (fds) == -1) { + perror ("pipe"); + exit (EXIT_FAILURE); + } + + data.fd = fds[0]; + snprintf (dev_fd, sizeof dev_fd, "/dev/fd/%d", fds[1]); + + data.cancel_posn = random_cancel_posn (); + + /* Create the test thread. */ + r = pthread_create (&test_thread, NULL, start_test_thread, &data); + if (r != 0) { + fprintf (stderr, "pthread_create: %s", strerror (r)); + exit (EXIT_FAILURE); + } + + /* Do the download. */ + op_error = guestfs_download (g, "/download", dev_fd); + op_errno = guestfs_last_errno (g); + + /* Kill the test thread and clean up. */ + r = pthread_cancel (test_thread); + if (r != 0) { + fprintf (stderr, "pthread_cancel: %s", strerror (r)); + exit (EXIT_FAILURE); + } + r = pthread_join (test_thread, NULL); + if (r != 0) { + fprintf (stderr, "pthread_join: %s", strerror (r)); + exit (EXIT_FAILURE); + } + + close (fds[0]); + close (fds[1]); + + /* We expect to get an error, with errno == EINTR. */ + if (op_error == -1 && op_errno == EINTR) + printf ("test-user-cancel: download cancellation test passed (%ld/%ld)\n", + (long) data.cancel_posn, (long) data.transfer_size); + else { + fprintf (stderr, "test-user-cancel: download cancellation test FAILED\n"); + fprintf (stderr, "cancel_posn %ld, upload returned %d, errno = %d (%s)\n", + (long) data.cancel_posn, op_error, op_errno, strerror (op_errno)); + errors++; + } + + exit (errors == 0 ? EXIT_SUCCESS : EXIT_FAILURE); +} + +static void +remove_test_img (void) +{ + unlink (filename); +} + +static char buffer[BUFSIZ]; + +#ifndef MIN +#define MIN(a,b) ((a)<(b)?(a):(b)) +#endif + +static void * +start_test_thread (void *datav) +{ + struct test_thread_data *data = datav; + ssize_t r; + size_t n; + + data->transfer_size = 0; + + if (data->direction == DIRECTION_UP) { /* thread is writing */ + /* Feed data in, up to the cancellation point. */ + while (data->transfer_size < data->cancel_posn) { + n = MIN (sizeof buffer, + (size_t) (data->cancel_posn - data->transfer_size)); + r = write (data->fd, buffer, n); + if (r == -1) { + perror ("test thread: write to pipe before user cancel"); + exit (EXIT_FAILURE); + } + data->transfer_size += r; + } + + /* Keep feeding data after the cancellation point for as long as + * the main thread wants it. + */ + while (1) { + /* Repeatedly assert the cancel flag. We have to do this because + * the guestfs_upload command in the main thread may not have + * started yet. + */ + guestfs_user_cancel (data->g); + + r = write (data->fd, buffer, sizeof buffer); + if (r == -1) { + perror ("test thread: write to pipe after user cancel"); + exit (EXIT_FAILURE); + } + data->transfer_size += r; + } + } else { /* thread is reading */ + /* Sink data, up to the cancellation point. */ + while (data->transfer_size < data->cancel_posn) { + n = MIN (sizeof buffer, + (size_t) (data->cancel_posn - data->transfer_size)); + r = read (data->fd, buffer, n); + if (r == -1) { + perror ("test thread: read from pipe before user cancel"); + exit (EXIT_FAILURE); + } + if (r == 0) { + perror ("test thread: unexpected end of file before user cancel"); + exit (EXIT_FAILURE); + } + data->transfer_size += r; + } + + /* Do user cancellation. */ + guestfs_user_cancel (data->g); + + /* Keep sinking data as long as the main thread is writing. */ + while (1) { + r = read (data->fd, buffer, sizeof buffer); + if (r == -1) { + perror ("test thread: read from pipe after user cancel"); + exit (EXIT_FAILURE); + } + if (r == 0) + break; + data->transfer_size += r; + } + + while (1) + pause (); + } + + return NULL; +} + +static double random_gauss (double mu, double sd); + +/* Generate a random cancellation position, but skew it towards + * smaller numbers. + */ +static off_t +random_cancel_posn (void) +{ + const double mu = 65536; + const double sd = 65536 * 4; + double r; + + do { + r = random_gauss (mu, sd); + } while (r <= 0); + + return (off_t) r; +} + +/* Generate a random Gaussian distributed number using the Box-Muller + * transformation. (http://www.taygeta.com/random/gaussian.html) + */ +static double +random_gauss (double mu, double sd) +{ + double x1, x2, w, y1; + + do { + x1 = 2. * drand48 () - 1.; + x2 = 2. * drand48 () - 1.; + w = x1 * x1 + x2 * x2; + } while (w >= 1.); + + w = sqrt ((-2. * log (w)) / w); + y1 = x1 * w; + //y2 = x2 * w; + return mu + y1 * sd; +} |