From f3e0d4df60935583d7d3f8ec31c5b0bdeaf351e4 Mon Sep 17 00:00:00 2001 From: Bruno Haible Date: Mon, 11 Apr 2011 03:57:36 +0200 Subject: [PATCH] Add more tests for module 'nonblocking'. --- modules/nonblocking-tests | 19 +++- tests/test-nonblocking-data.h | 29 +++++ tests/test-nonblocking-pipe-child.c | 143 ++++++++++++++++++++++ tests/test-nonblocking-pipe-main.c | 224 +++++++++++++++++++++++++++++++++++ tests/test-nonblocking-pipe.sh | 13 ++ 5 files changed, 426 insertions(+), 2 deletions(-) create mode 100644 tests/test-nonblocking-data.h create mode 100644 tests/test-nonblocking-pipe-child.c create mode 100644 tests/test-nonblocking-pipe-main.c create mode 100755 tests/test-nonblocking-pipe.sh diff --git a/modules/nonblocking-tests b/modules/nonblocking-tests index a1e5e7c..cd4e2d0 100644 --- a/modules/nonblocking-tests +++ b/modules/nonblocking-tests @@ -1,14 +1,29 @@ Files: tests/test-nonblocking.c +tests/test-nonblocking-pipe.sh +tests/test-nonblocking-pipe-main.c +tests/test-nonblocking-pipe-child.c +tests/test-nonblocking-data.h tests/macros.h Depends-on: close +dup2 +environ +full-read +gettimeofday pipe-posix +posix_spawnp +ssize_t +unistd +usleep +wait-process configure.ac: Makefile.am: -TESTS += test-nonblocking -check_PROGRAMS += test-nonblocking +TESTS += test-nonblocking test-nonblocking-pipe.sh +check_PROGRAMS += \ + test-nonblocking \ + test-nonblocking-pipe-main test-nonblocking-pipe-child test_nonblocking_LDADD = $(LDADD) $(LIBSOCKET) diff --git a/tests/test-nonblocking-data.h b/tests/test-nonblocking-data.h new file mode 100644 index 0000000..8928985 --- /dev/null +++ b/tests/test-nonblocking-data.h @@ -0,0 +1,29 @@ +/* Test for nonblocking read and write. + + Copyright (C) 2011 Free Software Foundation, 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 3 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, see . */ + +/* A data block ought to be larger than the size of the in-kernel buffer. + On Linux, it needs to be >= 63489. */ +#define DATA_BLOCK_SIZE 100000 + +static void +init_data (unsigned char data[2 * DATA_BLOCK_SIZE]) +{ + unsigned int i; + + for (i = 0; i < 2 * DATA_BLOCK_SIZE; i++) + data[i] = (unsigned char) (i * i + (7 * i) % 61 + 4); +} diff --git a/tests/test-nonblocking-pipe-child.c b/tests/test-nonblocking-pipe-child.c new file mode 100644 index 0000000..da1d8ee --- /dev/null +++ b/tests/test-nonblocking-pipe-child.c @@ -0,0 +1,143 @@ +/* Child program invoked by test-nonblocking-pipe-main. + + Copyright (C) 2011 Free Software Foundation, 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 3 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, see . */ + +#include + +#include +#include +#include +#include +#include + +#include "full-read.h" + +#include "test-nonblocking-data.h" +#include "macros.h" + +static ssize_t +full_read_from_nonblocking_fd (size_t fd, void *buf, size_t count) +{ + size_t bytes_read; + + bytes_read = 0; + while (bytes_read < count) + { + struct timeval before_time; + struct timeval after_time; + double spent_time; + #define START_TIMING \ + gettimeofday (&before_time, NULL); + #define END_TIMING \ + gettimeofday (&after_time, NULL); \ + spent_time = \ + (after_time.tv_sec - before_time.tv_sec) \ + + ((double) after_time.tv_usec - (double) before_time.tv_usec) * 1e-6; + ssize_t ret; + int saved_errno; + + START_TIMING + ret = read (fd, (char *) buf + bytes_read, count - bytes_read); + saved_errno = errno; + END_TIMING + ASSERT (spent_time < 0.5); + if (ret < 0) + { + ASSERT (saved_errno == EAGAIN); + usleep (100000); + } + else + { + ASSERT (ret > 0); + bytes_read += ret; + } + } + return bytes_read; +} + +int +main (int argc, char *argv[]) +{ + int test = atoi (argv[1]); + unsigned char expected[2 * DATA_BLOCK_SIZE]; + unsigned char data[2 * DATA_BLOCK_SIZE]; + + /* Close unused file descriptors. */ + close (STDOUT_FILENO); + + /* Set up the expected data. */ + init_data (expected); + + switch (test) + { + struct timeval before_time; + struct timeval after_time; + double spent_time; + #define START_TIMING \ + gettimeofday (&before_time, NULL); + #define END_TIMING \ + gettimeofday (&after_time, NULL); \ + spent_time = \ + (after_time.tv_sec - before_time.tv_sec) \ + + ((double) after_time.tv_usec - (double) before_time.tv_usec) * 1e-6; + ssize_t ret; + + case 0: /* Test blocking write() with blocking read(). */ + case 1: /* Test non-blocking write() with blocking read(). */ + START_TIMING + ret = full_read (STDIN_FILENO, data, DATA_BLOCK_SIZE); + END_TIMING + ASSERT (ret == DATA_BLOCK_SIZE); + ASSERT (memcmp (data, expected, DATA_BLOCK_SIZE) == 0); + ASSERT (spent_time > 0.5 && spent_time < 1.5); + + usleep (1000000); + + START_TIMING + ret = full_read (STDIN_FILENO, data, DATA_BLOCK_SIZE); + END_TIMING + ASSERT (ret == DATA_BLOCK_SIZE); + ASSERT (memcmp (data, expected + DATA_BLOCK_SIZE, DATA_BLOCK_SIZE) == 0); + ASSERT (spent_time < 0.5); + + break; + + case 2: /* Test blocking write() with non-blocking read(). */ + case 3: /* Test non-blocking write() with non-blocking read(). */ + START_TIMING + ret = full_read_from_nonblocking_fd (STDIN_FILENO, data, DATA_BLOCK_SIZE); + END_TIMING + ASSERT (ret == DATA_BLOCK_SIZE); + ASSERT (memcmp (data, expected, DATA_BLOCK_SIZE) == 0); + ASSERT (spent_time > 0.5 && spent_time < 1.5); + + usleep (1000000); + + START_TIMING + ret = full_read_from_nonblocking_fd (STDIN_FILENO, data, DATA_BLOCK_SIZE); + END_TIMING + ASSERT (ret == DATA_BLOCK_SIZE); + ASSERT (memcmp (data, expected + DATA_BLOCK_SIZE, DATA_BLOCK_SIZE) == 0); + ASSERT (spent_time < 0.5); + + break; + + default: + abort (); + } + + return 0; +} diff --git a/tests/test-nonblocking-pipe-main.c b/tests/test-nonblocking-pipe-main.c new file mode 100644 index 0000000..5fd335d --- /dev/null +++ b/tests/test-nonblocking-pipe-main.c @@ -0,0 +1,224 @@ +/* Test for nonblocking read and write on pipes. + + Copyright (C) 2011 Free Software Foundation, 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 3 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, see . */ + +#include + +#include +#include +#include +#include + +#if (defined _WIN32 || defined __WIN32__) && ! defined __CYGWIN__ +# include +#else +# include +#endif + +#include "nonblocking.h" +#include "wait-process.h" + +#include "test-nonblocking-data.h" +#include "macros.h" + +/* This program implements 4 tests: + + test == 0: + Test blocking write() with blocking read(). + + Timeline Main process Child process + 0 s Start Start, read(10000) + 1 s write(20000) Return from read(10000) + 2 s Next read(10000) + 2 s Return from write(20000) Return from read(10000) + + test == 1: + Test non-blocking write() with blocking read(). + + Timeline Main process Child process + 0 s Start Start, read(10000) + 1 s write(20000) Return from read(10000) + Return with at least 10000, + Repeatedly continue + write() of the rest + 2 s Next read(10000) + 2 s Return from write(10000) Return from read(10000) + + test == 2: + Test blocking write() with non-blocking read(). + + Timeline Main process Child process + 0 s Start Start, read(10000) + repeatedly polling + 1 s write(20000) Return from read(10000) + 2 s Next read(10000) + 2 s Return from write(20000) Return from read(10000) + + test == 3: + Test non-blocking write() with non-blocking read(). + */ + +int +main (int argc, char *argv[]) +{ + const char *child_path = argv[1]; + int test = atoi (argv[2]); + int fd[2]; + int child; + unsigned char data[2 * DATA_BLOCK_SIZE]; + + /* Create a pipe. */ + ASSERT (pipe (fd) >= 0); + + /* Map fd[0] to STDIN_FILENO and fd[1] to STDOUT_FILENO, because on Windows, + the only three file descriptors that are inherited by child processes are + STDIN_FILENO, STDOUT_FILENO, STDERR_FILENO. */ + if (fd[0] != STDIN_FILENO) + { + ASSERT (dup2 (fd[0], STDIN_FILENO) >= 0); + close (fd[0]); + } + if (fd[1] != STDOUT_FILENO) + { + ASSERT (dup2 (fd[1], STDOUT_FILENO) >= 0); + close (fd[1]); + } + + /* Prepare the file descriptors. */ + if (test & 1) + ASSERT (set_nonblocking_flag (STDOUT_FILENO, true) >= 0); + if (test & 2) + ASSERT (set_nonblocking_flag (STDIN_FILENO, true) >= 0); + + /* Spawn the child process. */ + { + const char *child_argv[3]; + + child_argv[0] = child_path; + child_argv[1] = argv[2]; + child_argv[2] = NULL; + +#if (defined _WIN32 || defined __WIN32__) && ! defined __CYGWIN__ + child = spawnvpe (P_NOWAIT, child_path, child_argv, + (const char **) environ); + ASSERT (child >= 0); +#else + { + pid_t child_pid; + int err = + posix_spawnp (&child_pid, child_path, NULL, NULL, (char **) child_argv, + environ); + ASSERT (err == 0); + child = child_pid; + } +#endif + } + + /* Close unused file descriptors. */ + close (STDIN_FILENO); + + /* Set up the data to transfer. */ + init_data (data); + + switch (test) + { + struct timeval before_time; + struct timeval after_time; + double spent_time; + #define START_TIMING \ + gettimeofday (&before_time, NULL); + #define END_TIMING \ + gettimeofday (&after_time, NULL); \ + spent_time = \ + (after_time.tv_sec - before_time.tv_sec) \ + + ((double) after_time.tv_usec - (double) before_time.tv_usec) * 1e-6; + ssize_t ret; + + case 0: /* Test blocking write() with blocking read(). */ + case 2: /* Test blocking write() with non-blocking read(). */ + usleep (1000000); + + START_TIMING + ret = write (STDOUT_FILENO, data, 2 * DATA_BLOCK_SIZE); + END_TIMING + ASSERT (ret == 2 * DATA_BLOCK_SIZE); + ASSERT (spent_time > 0.5 && spent_time < 1.5); + + break; + + case 1: /* Test non-blocking write() with blocking read(). */ + case 3: /* Test non-blocking write() with non-blocking read(). */ + { + size_t bytes_written; + + usleep (1000000); + + bytes_written = 0; + for (;;) + { + START_TIMING + ret = write (STDOUT_FILENO, data + bytes_written, + 2 * DATA_BLOCK_SIZE - bytes_written); + if (ret < 0 && bytes_written >= DATA_BLOCK_SIZE) + break; + END_TIMING + ASSERT (spent_time < 0.5); + if (ret >= 0) + { + ASSERT (ret > 0); + bytes_written += ret; + } + } + ASSERT (errno == EAGAIN); + END_TIMING + ASSERT (spent_time < 0.5); + ASSERT (bytes_written >= DATA_BLOCK_SIZE); + + while (bytes_written < 2 * DATA_BLOCK_SIZE) + { + START_TIMING + ret = write (STDOUT_FILENO, data + bytes_written, + 2 * DATA_BLOCK_SIZE - bytes_written); + if (ret < 0) + { + ASSERT (errno == EAGAIN); + END_TIMING + ASSERT (spent_time < 0.5); + usleep (100000); + } + else + { + END_TIMING + ASSERT (spent_time < 0.5); + ASSERT (ret > 0); + bytes_written += ret; + } + } + } + break; + + default: + abort (); + } + + { + int err = + wait_subprocess (child, child_path, false, false, false, false, NULL); + ASSERT (err == 0); + } + + return 0; +} diff --git a/tests/test-nonblocking-pipe.sh b/tests/test-nonblocking-pipe.sh new file mode 100755 index 0000000..c6e1e0b --- /dev/null +++ b/tests/test-nonblocking-pipe.sh @@ -0,0 +1,13 @@ +#!/bin/sh + +# Test blocking write() with blocking read(). +./test-nonblocking-pipe-main${EXEEXT} ./test-nonblocking-pipe-child${EXEEXT} 0 || exit 1 + +# Test non-blocking write() with blocking read(). +./test-nonblocking-pipe-main${EXEEXT} ./test-nonblocking-pipe-child${EXEEXT} 1 || exit 1 + +# Test blocking write() with non-blocking read(). +./test-nonblocking-pipe-main${EXEEXT} ./test-nonblocking-pipe-child${EXEEXT} 2 || exit 1 + +# Test non-blocking write() with non-blocking read(). +./test-nonblocking-pipe-main${EXEEXT} ./test-nonblocking-pipe-child${EXEEXT} 3 || exit 1 -- 1.6.3.2