Hi Eric,

I was a bit suspicious whether non-blocking I/O on Woe32 sockets really work.
So I set out to write a unit test for it. Sockets are a bit more complicated
than pipes, so I started with the pipes.

Find attached this unit test - for testing, not yet ready to be committed -.

Can you please review it? The test passes on Linux, FreeBSD, OpenBSD, MacOS X,
but fails on Solaris 2.6, OSF/1 5.1, IRIX 6.5, Cygwin 1.5 and 1.7, and mingw.

If already non-blocking pipes are buggy on so many platforms, what do we have
to expect from non-blocking sockets...?

Or maybe you can find a problem with my test, that I didn't see?

Bruno
-- 
In memoriam Hendrik Nicolaas Werkman 
<http://en.wikipedia.org/wiki/Hendrik_Nicolaas_Werkman>
From f3e0d4df60935583d7d3f8ec31c5b0bdeaf351e4 Mon Sep 17 00:00:00 2001
From: Bruno Haible <br...@clisp.org>
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 <http://www.gnu.org/licenses/>.  */
+
+/* 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 <http://www.gnu.org/licenses/>.  */
+
+#include <config.h>
+
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/time.h>
+
+#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 <http://www.gnu.org/licenses/>.  */
+
+#include <config.h>
+
+#include <errno.h>
+#include <stdlib.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 "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

Reply via email to