changeset 14589:e34e9b75f470

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.
author Bruno Haible <bruno@clisp.org>
date Sun, 17 Apr 2011 19:27:28 +0200
parents 9735b8fe9038
children e0d0a4052520
files ChangeLog modules/nonblocking-pipe-tests modules/nonblocking-tests tests/test-nonblocking-misc.h tests/test-nonblocking-pipe-child.c tests/test-nonblocking-pipe-main.c tests/test-nonblocking-pipe.h tests/test-nonblocking-pipe.sh tests/test-nonblocking-reader.h tests/test-nonblocking-writer.h
diffstat 10 files changed, 757 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,16 @@
+2011-04-17  Bruno Haible  <bruno@clisp.org>
+
+	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  <bruno@clisp.org>
 
 	gettext: Clarify the needed programmer actions.
new file mode 100644
--- /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
--- a/modules/nonblocking-tests
+++ b/modules/nonblocking-tests
@@ -5,6 +5,7 @@
 Depends-on:
 close
 pipe-posix
+nonblocking-pipe-tests
 
 configure.ac:
 
new file mode 100644
--- /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 <http://www.gnu.org/licenses/>.  */
+
+/* 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 <stdarg.h>
+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 (&current_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;
new file mode 100644
--- /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 <http://www.gnu.org/licenses/>.  */
+
+#include <config.h>
+
+#include <errno.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/time.h>
+
+#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;
+}
new file mode 100644
--- /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 <http://www.gnu.org/licenses/>.  */
+
+#include <config.h>
+
+#include <errno.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/time.h>
+
+#if (defined _WIN32 || defined __WIN32__) && ! defined __CYGWIN__
+# include <process.h>
+#else
+# include <spawn.h>
+#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;
+}
new file mode 100644
--- /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 <http://www.gnu.org/licenses/>.  */
+
+/* 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
new file mode 100755
--- /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
new file mode 100644
--- /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 <http://www.gnu.org/licenses/>.  */
+
+/* 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);
+}
new file mode 100644
--- /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 <http://www.gnu.org/licenses/>.  */
+
+/* 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;
+}