From: Bruno Haible Date: Sun, 17 Apr 2011 17:27:28 +0000 (+0200) Subject: nonblocking: Add tests for pipes. X-Git-Tag: v0.1~2936 X-Git-Url: http://erislabs.net/gitweb/?a=commitdiff_plain;h=8a0661a8db248b45ced989b23e165fda6d69b3b4;p=gnulib.git nonblocking: Add tests for pipes. * tests/test-nonblocking-pipe.sh: New file. * tests/test-nonblocking-pipe-main.c: New file. * tests/test-nonblocking-pipe-child.c: New file. * tests/test-nonblocking-pipe.h: New file. * tests/test-nonblocking-writer.h: New file. * tests/test-nonblocking-reader.h: New file. * tests/test-nonblocking-misc.h: New file. * modules/nonblocking-pipe-tests: New file. * modules/nonblocking-tests (Depends-on): Add nonblocking-pipe-tests. --- diff --git a/ChangeLog b/ChangeLog index c81be0cff..d86f4c9d2 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,16 @@ +2011-04-17 Bruno Haible + + nonblocking: Add tests for pipes. + * tests/test-nonblocking-pipe.sh: New file. + * tests/test-nonblocking-pipe-main.c: New file. + * tests/test-nonblocking-pipe-child.c: New file. + * tests/test-nonblocking-pipe.h: New file. + * tests/test-nonblocking-writer.h: New file. + * tests/test-nonblocking-reader.h: New file. + * tests/test-nonblocking-misc.h: New file. + * modules/nonblocking-pipe-tests: New file. + * modules/nonblocking-tests (Depends-on): Add nonblocking-pipe-tests. + 2011-04-16 Bruno Haible gettext: Clarify the needed programmer actions. diff --git a/modules/nonblocking-pipe-tests b/modules/nonblocking-pipe-tests new file mode 100644 index 000000000..428452f78 --- /dev/null +++ b/modules/nonblocking-pipe-tests @@ -0,0 +1,34 @@ +Files: +tests/test-nonblocking-pipe.sh +tests/test-nonblocking-pipe-main.c +tests/test-nonblocking-pipe-child.c +tests/test-nonblocking-pipe.h +tests/test-nonblocking-writer.h +tests/test-nonblocking-reader.h +tests/test-nonblocking-misc.h +tests/macros.h + +Depends-on: +stdbool +unistd +nonblocking +wait-process +pipe-posix +dup2 +environ +posix_spawnp +binary-io +gettimeofday +snprintf +vsnprintf +strerror +ssize_t +usleep +read +write + +configure.ac: + +Makefile.am: +TESTS += test-nonblocking-pipe.sh +check_PROGRAMS += test-nonblocking-pipe-main test-nonblocking-pipe-child diff --git a/modules/nonblocking-tests b/modules/nonblocking-tests index a1e5e7c03..bd6cc134b 100644 --- a/modules/nonblocking-tests +++ b/modules/nonblocking-tests @@ -5,6 +5,7 @@ tests/macros.h Depends-on: close pipe-posix +nonblocking-pipe-tests configure.ac: diff --git a/tests/test-nonblocking-misc.h b/tests/test-nonblocking-misc.h new file mode 100644 index 000000000..66a13e47e --- /dev/null +++ b/tests/test-nonblocking-misc.h @@ -0,0 +1,108 @@ +/* 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 . */ + +/* Whether to print debugging messages. */ +#define ENABLE_DEBUGGING 0 + +/* Delay (in microseconds) to sleep when write() or read() returned -1 with + errno = EAGAIN. */ +#define SMALL_DELAY 10000 + +/* Return a memory area, filled with the data to be transferred. */ +static unsigned char * +init_data (size_t data_block_size) +{ + unsigned char *data; + unsigned int i; + + data = (unsigned char *) malloc (2 * data_block_size); + ASSERT (data != NULL); + + for (i = 0; i < 2 * data_block_size; i++) + data[i] = (unsigned char) (i * i + (7 * i) % 61 + 4); + + return data; +} + +#if ENABLE_DEBUGGING +# include +static int dbgfprintf (FILE *fp, const char *format, ...) + _GL_ATTRIBUTE_FORMAT_PRINTF (2, 3); +static int +dbgfprintf (FILE *fp, const char *format, ...) +{ + /* Accumulate the entire line in a buffer, so that the output on fp + is done atomically. */ + char line[1024]; + size_t line_len; + struct timeval current_time; + va_list args; + int ret; + + line_len = 0; + gettimeofday (¤t_time, NULL); + ret = snprintf (line, sizeof (line), "%.6f ", + current_time.tv_sec + (double) current_time.tv_usec * 1e-6); + if (ret < 0) + return -1; + line_len = strlen (line); + + va_start (args, format); + ret = vsnprintf (line + line_len, sizeof (line) - line_len, format, args); + va_end (args); + if (ret < 0) + return -1; + line_len += strlen (line + line_len); + + ret = fwrite (line, 1, line_len, fp); + + /* Make sure the debugging information is output, so that the order of the + messages reflects the timeline of events, and so that the output is not + lost if the program crashes afterwards (relevant on mingw). */ + fflush (fp); + return ret; +} +#else +# define dbgfprintf if (1) ; else fprintf +#endif + +/* Return a textual description of the error code ERR, if FAILED is true. + Return an empty string if FAILED is false. */ +static const char * +dbgstrerror (bool failed, int err) +{ + static char buf[256]; + if (failed) + { + sprintf (buf, " %d %s", err, strerror (err)); + return buf; + } + else + return ""; +} + +#define TIMING_DECLS \ + 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; diff --git a/tests/test-nonblocking-pipe-child.c b/tests/test-nonblocking-pipe-child.c new file mode 100644 index 000000000..d12a6e0ce --- /dev/null +++ b/tests/test-nonblocking-pipe-child.c @@ -0,0 +1,50 @@ +/* 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 + +#include "binary-io.h" + +#include "macros.h" +#include "test-nonblocking-pipe.h" +#define PROG_ROLE "child" +#include "test-nonblocking-reader.h" + +int +main (int argc, char *argv[]) +{ + int test = atoi (argv[1]); + + /* Close unused file descriptors. */ + close (STDOUT_FILENO); + + /* STDIN_FILENO was created as binary in the parent process. But since an + fd's mode is stored in the process, not in the kernel, on native Windows + we need to set it as binary in the child process again. */ + SET_BINARY (STDIN_FILENO); + + main_reader_loop (test, PIPE_DATA_BLOCK_SIZE, STDIN_FILENO); + + return 0; +} diff --git a/tests/test-nonblocking-pipe-main.c b/tests/test-nonblocking-pipe-main.c new file mode 100644 index 000000000..e61269ce5 --- /dev/null +++ b/tests/test-nonblocking-pipe-main.c @@ -0,0 +1,110 @@ +/* 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 +#include +#include + +#if (defined _WIN32 || defined __WIN32__) && ! defined __CYGWIN__ +# include +#else +# include +#endif + +#include "nonblocking.h" +#include "wait-process.h" + +#include "macros.h" +#include "test-nonblocking-pipe.h" +#define PROG_ROLE "main" +#include "test-nonblocking-writer.h" + +int +main (int argc, char *argv[]) +{ + const char *child_path = argv[1]; + int test = atoi (argv[2]); + int fd[2]; + int child; + int exitcode; + + /* 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); + + exitcode = + main_writer_loop (test, PIPE_DATA_BLOCK_SIZE, STDOUT_FILENO, false); + + { + int err = + wait_subprocess (child, child_path, false, false, false, false, NULL); + ASSERT (err == 0); + } + + return exitcode; +} diff --git a/tests/test-nonblocking-pipe.h b/tests/test-nonblocking-pipe.h new file mode 100644 index 000000000..c4e65616a --- /dev/null +++ b/tests/test-nonblocking-pipe.h @@ -0,0 +1,38 @@ +/* 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. + Working values of PIPE_DATA_BLOCK_SIZE, depending on kernel: + + Platform PIPE_DATA_BLOCK_SIZE + + Linux >= 63489 + FreeBSD, OpenBSD, MacOS X >= 65537 + AIX >= 32769 + HP-UX >= 8193 + IRIX >= 10241 + OSF/1 >= 262145 + Solaris <= 7 >= 10241 + Solaris >= 8 >= 20481 + Cygwin >= 65537 + native Win32 >= 4097 (depends on the _pipe argument) + */ +#if defined __osf__ +# define PIPE_DATA_BLOCK_SIZE 270000 +#else +# define PIPE_DATA_BLOCK_SIZE 70000 +#endif diff --git a/tests/test-nonblocking-pipe.sh b/tests/test-nonblocking-pipe.sh new file mode 100755 index 000000000..dd692be7e --- /dev/null +++ b/tests/test-nonblocking-pipe.sh @@ -0,0 +1,17 @@ +#!/bin/sh + +# Note: This test fails on Cygwin 1.5.x, because the non-blocking flag has +# apparently no effect on STDOUT_FILENO. It is fixed in Cygwin 1.7. + +# Test blocking write() with blocking read(). +# Commented out because this test succeeds on all platforms anyway. +#./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 diff --git a/tests/test-nonblocking-reader.h b/tests/test-nonblocking-reader.h new file mode 100644 index 000000000..2099b69b2 --- /dev/null +++ b/tests/test-nonblocking-reader.h @@ -0,0 +1,200 @@ +/* The reader part of a test program for non-blocking communication. + + 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 . */ + +/* 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(). + */ + +#include "test-nonblocking-misc.h" + +static ssize_t +full_read (size_t fd, void *buf, size_t count) +{ + size_t bytes_read; + + bytes_read = 0; + while (bytes_read < count) + { + TIMING_DECLS + ssize_t ret; + int saved_errno; + + dbgfprintf (stderr, "%s: >> read (%lu)\n", PROG_ROLE, + (unsigned long) (count - bytes_read)); + START_TIMING + ret = read (fd, (char *) buf + bytes_read, count - bytes_read); + saved_errno = errno; + END_TIMING + dbgfprintf (stderr, "%s: << read -> %ld%s\n", PROG_ROLE, + (long) ret, dbgstrerror (ret < 0, saved_errno)); + if (ret < 0) + return -1; + else + { + ASSERT (ret > 0); + bytes_read += ret; + } + } + return bytes_read; +} + +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) + { + TIMING_DECLS + ssize_t ret; + int saved_errno; + + dbgfprintf (stderr, "%s: >> read (%lu)\n", PROG_ROLE, + (unsigned long) (count - bytes_read)); + START_TIMING + ret = read (fd, (char *) buf + bytes_read, count - bytes_read); + saved_errno = errno; + END_TIMING + dbgfprintf (stderr, "%s: << read -> %ld%s\n", PROG_ROLE, + (long) ret, dbgstrerror (ret < 0, saved_errno)); + /* This assertion fails if the non-blocking flag is effectively not set + on fd = STDIN_FILENO. */ + ASSERT (spent_time < 0.5); + if (ret < 0) + { + ASSERT (saved_errno == EAGAIN); + usleep (SMALL_DELAY); + } + else + { + ASSERT (ret > 0); + bytes_read += ret; + } + } + return bytes_read; +} + +/* Execute the reader loop. */ +static void +main_reader_loop (int test, size_t data_block_size, int fd) +{ + unsigned char *expected; + unsigned char *data; + + /* Set up the expected data. */ + expected = init_data (data_block_size); + + data = (unsigned char *) malloc (2 * data_block_size); + ASSERT (data != NULL); + + switch (test) + { + TIMING_DECLS + 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 (fd, data, data_block_size); + END_TIMING + ASSERT (ret == data_block_size); + ASSERT (memcmp (data, expected, data_block_size) == 0); + ASSERT (spent_time > 0.5); + /* This assertion fails if data_block_size is very large and + ENABLE_DEBUGGING is 1. */ + ASSERT (spent_time < 1.5); + + usleep (1000000); + + START_TIMING + ret = full_read (fd, data, data_block_size); + END_TIMING + ASSERT (ret == data_block_size); + ASSERT (memcmp (data, expected + data_block_size, data_block_size) == 0); + /* This assertion fails if data_block_size is much larger than needed + and SMALL_DELAY is too large. */ + 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 (fd, data, data_block_size); + END_TIMING + ASSERT (ret == data_block_size); + ASSERT (memcmp (data, expected, data_block_size) == 0); + ASSERT (spent_time > 0.5); + /* This assertion fails if data_block_size is much larger than needed + and SMALL_DELAY is too large, or if data_block_size is very large and + ENABLE_DEBUGGING is 1. */ + ASSERT (spent_time < 1.5); + + usleep (1000000); + + START_TIMING + ret = full_read_from_nonblocking_fd (fd, data, data_block_size); + END_TIMING + ASSERT (ret == data_block_size); + ASSERT (memcmp (data, expected + data_block_size, data_block_size) == 0); + /* This assertion fails if data_block_size is much larger than needed + and SMALL_DELAY is too large. */ + ASSERT (spent_time < 0.5); + + break; + + default: + abort (); + } + + free (data); + free (expected); +} diff --git a/tests/test-nonblocking-writer.h b/tests/test-nonblocking-writer.h new file mode 100644 index 000000000..6db53f6da --- /dev/null +++ b/tests/test-nonblocking-writer.h @@ -0,0 +1,186 @@ +/* The writer part of a test program for non-blocking communication. + + 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 . */ + +/* 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(). + */ + +#include "test-nonblocking-misc.h" + +/* Execute the writer loop. + Returns 0 if successful, 1 if data_block_size is too small. */ +static int +main_writer_loop (int test, size_t data_block_size, int fd, + bool has_large_buffer) +{ + int too_small = 0; + unsigned char *data; + + /* Set up the data to transfer. */ + data = init_data (data_block_size); + + switch (test) + { + TIMING_DECLS + ssize_t ret; + + case 0: /* Test blocking write() with blocking read(). */ + case 2: /* Test blocking write() with non-blocking read(). */ + { + int saved_errno; + + usleep (1000000); + + dbgfprintf (stderr, "%s:1: >> write (%lu)\n", PROG_ROLE, + (unsigned long) 2 * data_block_size); + START_TIMING + ret = write (fd, data, 2 * data_block_size); + saved_errno = errno; + END_TIMING + dbgfprintf (stderr, "%s:1: << write -> %ld%s\n", PROG_ROLE, + (long) ret, dbgstrerror (ret < 0, saved_errno)); + ASSERT (ret == 2 * data_block_size); + if (!has_large_buffer) + { + /* This assertion fails if data_block_size is too small. */ + if (!(spent_time > 0.5)) + { + fprintf (stderr, + "%s:1: spent_time = %g, data_block_size too small\n", + PROG_ROLE, spent_time); + too_small = 1; + } + } + ASSERT (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; + int saved_errno; + + usleep (1000000); + + bytes_written = 0; + while (bytes_written < 2 * data_block_size) + { + dbgfprintf (stderr, "%s:2: >> write (%lu)\n", PROG_ROLE, + (unsigned long) (2 * data_block_size - bytes_written)); + START_TIMING + ret = write (fd, data + bytes_written, + 2 * data_block_size - bytes_written); + saved_errno = errno; + END_TIMING + dbgfprintf (stderr, "%s:2: << write -> %ld%s\n", PROG_ROLE, + (long) ret, dbgstrerror (ret < 0, saved_errno)); + if (ret < 0 && bytes_written >= data_block_size) + { + ASSERT (saved_errno == EAGAIN); + ASSERT (spent_time < 0.5); + break; + } + /* This assertion fails if the non-blocking flag is effectively not + set on fd. */ + ASSERT (spent_time < 0.5); + if (ret < 0) + { + ASSERT (saved_errno == EAGAIN); + usleep (SMALL_DELAY); + } + else + { + /* This assertion fails if data_block_size is too small. */ + if (!(ret > 0)) + { + fprintf (stderr, + "%s:1: spent_time = %g, data_block_size too small\n", + PROG_ROLE, spent_time); + too_small = 1; + } + bytes_written += ret; + } + } + ASSERT (bytes_written >= data_block_size); + + while (bytes_written < 2 * data_block_size) + { + dbgfprintf (stderr, "%s:3: >> write (%lu)\n", PROG_ROLE, + (unsigned long) (2 * data_block_size - bytes_written)); + START_TIMING + ret = write (fd, data + bytes_written, + 2 * data_block_size - bytes_written); + saved_errno = errno; + END_TIMING + dbgfprintf (stderr, "%s:3: << write -> %ld%s\n", PROG_ROLE, + (long) ret, dbgstrerror (ret < 0, saved_errno)); + ASSERT (spent_time < 0.5); + if (ret < 0) + { + ASSERT (saved_errno == EAGAIN); + usleep (SMALL_DELAY); + } + else + { + ASSERT (ret > 0); + bytes_written += ret; + } + } + } + break; + + default: + abort (); + } + + free (data); + return too_small; +}