bug-gnulib
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[PATCH] My version of pipe-filter


From: Paolo Bonzini
Subject: [PATCH] My version of pipe-filter
Date: Sat, 25 Jul 2009 12:49:40 +0200

Hi all, here is a version of pipe-filter that works under Win32
too (tested with Wine), has tests, and so on.

Testing under native Windows would be appreciated.

Bruno, what do you think?

Paolo

2009-07-25  Paolo Bonzini  <address@hidden>

        * lib/pipe-filter.c: New.
        * lib/pipe-filter.h: New.
        * modules/pipe-filter: New.
        * modules/pipe-filter-tests: New.
        * tests/test-pipe-filter.sh: New.
        * tests/test-pipe-filter-helper.c: New.
        * tests/test-pipe-filter-main.c: New.

Cc: Bruno Haible <address@hidden>
---
 lib/pipe-filter.c               |  522 +++++++++++++++++++++++++++++++++++++++
 lib/pipe-filter.h               |  122 +++++++++
 modules/pipe-filter             |   33 +++
 modules/pipe-filter-tests       |   15 ++
 tests/test-pipe-filter-helper.c |   38 +++
 tests/test-pipe-filter-main.c   |  101 ++++++++
 tests/test-pipe-filter.sh       |   32 +++
 8 files changed, 875 insertions(+), 0 deletions(-)
 create mode 100644 lib/pipe-filter.c
 create mode 100644 lib/pipe-filter.h
 create mode 100644 modules/pipe-filter
 create mode 100644 modules/pipe-filter-tests
 create mode 100644 tests/test-pipe-filter-helper.c
 create mode 100644 tests/test-pipe-filter-main.c
 create mode 100755 tests/test-pipe-filter.sh

diff --git a/lib/pipe-filter.c b/lib/pipe-filter.c
new file mode 100644
index 0000000..7949761
--- /dev/null
+++ b/lib/pipe-filter.c
@@ -0,0 +1,522 @@
+/* Synchronous writing, asynchronous reading of pipes connected to a
+   subprocess.
+
+   Copyright (C) 2009 Free Software Foundation, Inc.
+   Written by Paolo Bonzini <address@hidden> and Bruno Haible
+   <address@hidden>, 2009.
+
+   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 "pipe-filter.h"
+
+#include <assert.h>
+#include <errno.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#if (defined _WIN32 || defined __WIN32__) && ! defined __CYGWIN__
+#include <windows.h>
+#else
+#include <sys/select.h>
+#endif
+
+#include "error.h"
+#include "gettext.h"
+#include "pipe.h"
+#include "wait-process.h"
+#include "xalloc.h"
+
+#define _(str) gettext (str)
+
+#ifndef SSIZE_MAX
+# define SSIZE_MAX ((ssize_t) (SIZE_MAX / 2))
+#endif
+
+
+/* We use a child process, and communicate through a bidirectional pipe.
+   To avoid deadlocks, let the child process decide when it wants to write,
+   and let the parent read accordingly.  The parent uses select() to
+   know whether it must write or read.  On platforms without select(),
+   we use non-blocking I/O.  (This means the parent is busy looping
+   while waiting for the child.  Not good.  But hardly any platform
+   lacks select() nowadays.)  */
+
+/* On BeOS select() works only on sockets, not on normal file descriptors.  */
+#ifdef __BEOS__
+# undef HAVE_SELECT
+#endif
+
+#ifdef EINTR
+
+/* EINTR handling for close(), read(), write(), select().
+   These functions can return -1/EINTR even though we don't have any
+   signal handlers set up, namely when we get interrupted via SIGSTOP.  */
+
+static inline int
+nonintr_close (int fd)
+{
+  int retval;
+
+  do
+    retval = close (fd);
+  while (retval < 0 && errno == EINTR);
+
+  return retval;
+}
+#undef close /* avoid warning related to gnulib module unistd */
+#define close nonintr_close
+
+static inline ssize_t
+nonintr_read (int fd, void *buf, size_t bufsize)
+{
+  ssize_t retval;
+
+  do
+    retval = read (fd, buf, bufsize);
+  while (retval < 0 && errno == EINTR);
+
+  return retval;
+}
+#define read nonintr_read
+
+static inline ssize_t
+nonintr_write (int fd, const void *buf, size_t count)
+{
+  ssize_t retval;
+
+  do
+    retval = write (fd, buf, count);
+  while (retval < 0 && errno == EINTR);
+
+  return retval;
+}
+#undef write /* avoid warning on VMS */
+#define write nonintr_write
+
+# if HAVE_SELECT
+
+static inline int
+nonintr_select (int n, fd_set *readfds, fd_set *writefds, fd_set *exceptfds,
+               struct timeval *timeout)
+{
+  int retval;
+
+  do
+    retval = select (n, readfds, writefds, exceptfds, timeout);
+  while (retval < 0 && errno == EINTR);
+
+  return retval;
+}
+#  undef select /* avoid warning on VMS */
+#  define select nonintr_select
+
+# endif
+
+#endif
+
+/* Non-blocking I/O.  */
+#ifndef O_NONBLOCK
+# define O_NONBLOCK O_NDELAY
+#endif
+#if HAVE_SELECT
+# define IS_EAGAIN(errcode) 0
+#else
+# ifdef EWOULDBLOCK
+#  define IS_EAGAIN(errcode) ((errcode) == EAGAIN || (errcode) == EWOULDBLOCK)
+# else
+#  define IS_EAGAIN(errcode) ((errcode) == EAGAIN)
+# endif
+#endif
+
+struct filter {
+  pid_t child;
+  const char *progname;
+  bool null_stderr;
+  bool exit_on_error;
+  char *read_buf;
+  size_t read_bufsize;
+  done_read_fn done_read;
+  void *private_data;
+  int fd[2];
+  int exit_code;
+
+  volatile int reader_errno;
+  volatile int writer_errno;
+
+#if (defined _WIN32 || defined __WIN32__) && ! defined __CYGWIN__
+  HANDLE hCallbackMutex;
+  HANDLE hReader;
+#else
+  fd_set readfds, writefds;
+#endif
+};
+
+#if (defined _WIN32 || defined __WIN32__) && ! defined __CYGWIN__
+
+static unsigned int WINAPI
+reader_thread_func (void *thread_arg)
+{
+  struct filter *f = (struct filter *) thread_arg;
+
+  for (;;)
+    {
+      ssize_t nread = read (f->fd[0], f->read_buf, f->read_bufsize);
+      WaitForSingleObject (f->hCallbackMutex, INFINITE);
+      if (f->writer_errno)
+       break;
+
+      if (nread == -1)
+       {
+         f->reader_errno = errno;
+         break;
+       }
+      if (nread > 0)
+       f->done_read (f->read_buf, nread, f->private_data);
+      else
+       break;
+      ReleaseMutex (f->hCallbackMutex);
+    }
+
+  f->reader_errno = -1;
+  ReleaseMutex (f->hCallbackMutex);
+  _endthreadex (0); /* calls ExitThread (0) */
+  abort ();
+}
+
+static int
+filter_init (struct filter *f)
+{
+  f->hCallbackMutex = CreateMutex (NULL, TRUE, NULL);
+
+  f->hReader =
+    (HANDLE) _beginthreadex (NULL, 100000, reader_thread_func, f, 0, NULL);
+
+  if (f->hReader == NULL)
+    {
+      if (f->exit_on_error)
+       error (EXIT_FAILURE, 0, _("creation of reading thread failed"));
+      return -1;
+    }
+  else
+    return 0;
+}
+
+static void
+filter_loop (struct filter *f, const char *buf, size_t count)
+{
+  /* While within filter_write, the reader thread can do callbacks.  */
+  ReleaseMutex (f->hCallbackMutex);
+
+  if (f->writer_errno == 0)
+    {
+      ssize_t nwritten = write (f->fd[1], buf, count);
+      if (nwritten == -1)
+       f->writer_errno = errno;
+    }
+
+  /* Wait for the current callback to return.  */
+  WaitForSingleObject (f->hCallbackMutex, INFINITE);
+
+  if (!f->writer_errno && f->reader_errno && f->reader_errno != -1)
+    errno = f->reader_errno;
+}
+
+static void
+filter_cleanup (struct filter *f, bool finish_reading)
+{
+  if (finish_reading)
+    {
+      ReleaseMutex (f->hCallbackMutex);
+      WaitForSingleObject (f->hReader, INFINITE);
+    }
+  else
+    TerminateThread (f->hReader, 1);
+
+  CloseHandle (f->hReader);
+  CloseHandle (f->hCallbackMutex);
+}
+
+#else
+static int
+filter_init (struct filter *f)
+{
+  /* Enable non-blocking I/O.  This permits the read() and write() calls
+     to return -1/EAGAIN without blocking; this is important for polling
+     if HAVE_SELECT is not defined.  It also permits the read() and write()
+     calls to return after partial reads/writes; this is important if
+     HAVE_SELECT is defined, because select() only says that some data
+     can be read or written, not how many.  Without non-blocking I/O,
+     Linux 2.2.17 and BSD systems prefer to block instead of returning
+     with partial results.  */
+  int fcntl_flags;
+
+  if ((fcntl_flags = fcntl (f->fd[1], F_GETFL, 0)) < 0
+      || fcntl (f->fd[1], F_SETFL, fcntl_flags | O_NONBLOCK) < 0
+      || (fcntl_flags = fcntl (f->fd[0], F_GETFL, 0)) < 0
+      || fcntl (f->fd[0], F_SETFL, fcntl_flags | O_NONBLOCK) < 0)
+    {
+      if (f->exit_on_error)
+       error (EXIT_FAILURE, errno,
+              _("cannot set up nonblocking I/O to %s subprocess"),
+              f->progname);
+      return -1;
+    }
+
+  FD_ZERO (&f->readfds);
+  FD_ZERO (&f->writefds);
+  return 0;
+}
+
+static void
+filter_loop (struct filter *f, const char *buf, size_t count)
+{
+  static struct timeval tv0;
+
+  assert (!f->reader_errno && !f->writer_errno);
+  for (;;)
+    {
+#if HAVE_SELECT
+      int n = f->fd[0] > f->fd[1] ? f->fd[0] + 1 : f->fd[1] + 1;
+      if (!f->reader_errno)
+       FD_SET (f->fd[0], &f->readfds);
+      if (!f->writer_errno)
+       FD_SET (f->fd[1], &f->writefds);
+      n = select (n, &f->readfds, (count ? &f->writefds : NULL), NULL,
+                 (buf && !count ? &tv0 : NULL));
+      if (n == 0)
+       break;
+
+      if (n == -1)
+         {
+           f->writer_errno = errno;
+           if (f->exit_on_error)
+             error (EXIT_FAILURE, errno,
+                    _("communication with %s subprocess failed"),
+                    f->progname);
+           return;
+         }
+
+      if (count && FD_ISSET (f->fd[1], &f->writefds))
+       goto try_write;
+      if (FD_ISSET (f->fd[0], &f->readfds))
+       goto try_read;
+      break;
+#endif
+
+      /* Attempt to write.  */
+#if HAVE_SELECT
+    try_write:
+#endif
+      if (count)
+       {
+         ssize_t nwritten = write (f->fd[1], buf,
+                                   count > SSIZE_MAX ? SSIZE_MAX : count);
+         if (nwritten == -1)
+           {
+             if (IS_EAGAIN (errno))
+               continue;
+             f->writer_errno = errno;
+             if (f->exit_on_error)
+               error (EXIT_FAILURE, errno,
+                      _("write to %s subprocess failed"), f->progname);
+             return;
+           }
+         else
+           {
+             count -= nwritten;
+             buf += nwritten;
+           }
+       }
+#if HAVE_SELECT
+      continue;
+#endif
+
+      /* Attempt to read.  */
+#if HAVE_SELECT
+    try_read:
+#endif
+      {        
+       ssize_t nread = read (f->fd[0], f->read_buf, f->read_bufsize);
+       if (nread == -1)
+         {
+           if (IS_EAGAIN (errno))
+             continue;
+           f->reader_errno = errno;
+           if (f->exit_on_error)
+             error (EXIT_FAILURE, errno,
+                    _("read from %s subprocess failed"), f->progname);
+           return;
+         }
+       else if (nread == 0)
+         {
+           f->reader_errno = -1;
+           return;
+         }
+       else
+         f->done_read (f->read_buf, nread, f->private_data);
+      }
+#if HAVE_SELECT
+      continue;
+#endif
+    }
+}
+
+static void
+filter_cleanup (struct filter *f, bool finish_reading)
+{
+  if (finish_reading)
+    filter_loop (f, NULL, 0);
+}
+#endif
+
+
+static void
+filter_terminate (struct filter *f)
+{
+  if (f->exit_code == -1)
+    {
+      close (f->fd[1]);
+      filter_cleanup (f, !f->reader_errno && !f->writer_errno);
+      close (f->fd[0]);
+      f->exit_code = wait_subprocess (f->child, f->progname, true, 
f->null_stderr,
+                                     true, f->exit_on_error, NULL);
+      if (f->exit_on_error && f->exit_code)
+       error (EXIT_FAILURE, 0, _("subprocess %s failed (exit status %d)"),
+              f->progname, f->exit_code);
+
+    }
+}
+
+/* Create a subprocess and pipe some data through it.
+   progname is the program name used in error messages.
+   prog_path is the file name of the program to invoke.
+   prog_argv is a NULL terminated argument list, starting with
+   prog_path as first element.
+   If null_stderr is true, the subprocess' stderr will be redirected
+   to /dev/null, and the usual error message to stderr will be
+   omitted.  This is suitable when the subprocess does not fulfill an
+   important task.
+   If exit_on_error is true, any error will cause the main process to
+   exit with an error status.
+   If the subprocess does not start correctly, exit if exit_on_error is
+   true, otherwise return NULL and set errno.
+
+   The caller will write to the subprocess through filter_write; during
+   calls to filter_write, the done_read function may be called to
+   process any data that the subprocess has written.  done_read will
+   receive at most read_bufsize bytes stored into buf, as well as a
+   copy of private_data.  */
+struct filter *
+filter_create (const char *progname,
+              const char *prog_path, const char **prog_argv,
+              bool null_stderr, bool exit_on_error,
+              char *read_buf, size_t read_bufsize,
+              done_read_fn done_read,
+              void *private_data)
+{
+  struct filter *f = xmalloc (sizeof (struct filter));
+  pid_t child;
+  int fd[2];
+
+  /* Open a bidirectional pipe to a subprocess.  */
+  f->child = create_pipe_bidi (progname, prog_path, (char **) prog_argv,
+                              null_stderr, true, exit_on_error,
+                              fd);
+
+  f->progname = progname;
+  f->null_stderr = null_stderr;
+  f->exit_on_error = exit_on_error;
+  f->exit_code = -1;
+  f->read_buf = read_buf;
+  f->read_bufsize = read_bufsize;
+  f->done_read = done_read;
+  f->private_data = private_data;
+  f->reader_errno = 0;
+  f->writer_errno = 0;
+  f->fd[0] = fd[0];
+  f->fd[1] = fd[1];
+
+  if (filter_init (f) < 0)
+    filter_terminate (f);
+
+  return f;
+}
+
+
+/* Write size bytes starting at buf into the pipe and in the meanwhile
+   possibly call the done_read function specified in create_filter.
+   The done_read function may be called in a different thread than
+   the current thread, depending on the platform.  However, it will
+   always be called before filter_write has returned (or else will be
+   delayed to the next call to filter_write or filter_close).  Return
+   only after all the entire buffer has been written to the pipe.
+
+   If the subprocess exits early with zero status, subsequent writes
+   will becomes no-ops and zero is returned.
+
+   If there is a problem reading or writing, return -1 and set errno.
+
+   If the subprocess exits early with nonzero status, return the status.
+   (In either case, filter_write will instead exit if exit_on_error was
+   passed as true).
+
+   Otherwise return 0.  */
+int
+filter_write (struct filter *f, const char *buf, size_t count)
+{
+  int rc, save_errno;
+  assert (buf);
+  if (f->exit_code != -1)
+    return f->exit_code;
+  if (!count)
+    return 0;
+
+  filter_loop (f, buf, count);
+  if (!f->reader_errno && !f->writer_errno)
+    return 0;
+
+  save_errno = errno;
+  filter_terminate (f);
+  errno = save_errno;
+  return (f->writer_errno ? -1 : f->exit_code);
+}
+
+/* Finish reading the output via the done_read function specified in
+   create_filter.  The done_read function may be called in a different
+   thread than.  However, it will always be called before filter_close
+   has returned.  The write side of the pipe is closed as soon as
+   filter_close starts, while the read side will be closed just before
+   it finishes.  If there is a problem reading or closing the pipe,
+   return -1 and set errno.  If the subprocess exits early with nonzero
+   status, return the status.  (In either case, filter_close will
+   instead exit if exit_on_error was passed as true).
+
+   Otherwise return 0.  */
+int
+filter_close (struct filter *f)
+{
+  int rc, save_errno;
+  filter_terminate (f);
+  rc = f->exit_code;
+  save_errno = errno;
+  free (f);
+  errno = save_errno;
+  return rc;
+}
diff --git a/lib/pipe-filter.h b/lib/pipe-filter.h
new file mode 100644
index 0000000..0290c11
--- /dev/null
+++ b/lib/pipe-filter.h
@@ -0,0 +1,122 @@
+/* Synchronous writing, asynchronous reading of pipes connected to a
+   subprocess.
+
+   Copyright (C) 2009 Free Software Foundation, Inc.
+   Written by Bruno Haible <address@hidden>, 2009.
+
+   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/>.  */
+
+#ifndef _PIPE_FILTER_H
+#define _PIPE_FILTER_H
+
+#include <stdbool.h>
+#include <stddef.h>
+
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+
+/* Piping data through a subprocess in the naïve way - write data to the
+   subprocess and read from the subprocess when you expect it to have
+   produced results - is subject to two kinds of deadlocks:
+   1) If you write more than PIPE_MAX bytes or, more generally, if you write
+      more bytes than the subprocess can handle at once, the subprocess
+      may write its data and wait on you to read it, but you are currently
+      busy writing.
+   2) When you don't know ahead of time how many bytes the subprocess
+      will produce, the usual technique of calling read (fd, buf, BUFSIZ)
+      with a fixed BUFSIZ will, on Linux 2.2.17 and on BSD systems, cause
+      the read() call to block until *all* of the buffer has been filled.
+      But the subprocess cannot produce more data until you gave it more
+      input.  But you are currently busy reading from it.
+   This module provides a function that pipes data to a subprocess and
+   gets its output back via a callback, without risking these deadlocks.  */
+
+
+typedef void (*done_read_fn) (char *read_buf, size_t num_bytes_read,
+                             void *private_data);
+
+struct filter;
+
+/* Create a subprocess and pipe some data through it.
+   progname is the program name used in error messages.
+   prog_path is the file name of the program to invoke.
+   prog_argv is a NULL terminated argument list, starting with
+   prog_path as first element.
+   If null_stderr is true, the subprocess' stderr will be redirected
+   to /dev/null, and the usual error message to stderr will be
+   omitted.  This is suitable when the subprocess does not fulfill an
+   important task.
+   If exit_on_error is true, any error will cause the main process to
+   exit with an error status.
+   If the subprocess does not start correctly, exit if exit_on_error is
+   true, otherwise return NULL and set errno.
+
+   The caller will write to the subprocess through filter_write; during
+   calls to filter_write, the done_read function may be called to
+   process any data that the subprocess has written.  done_read will
+   receive at most read_bufsize bytes stored into buf, as well as a
+   copy of private_data.  */
+extern struct filter * filter_create (const char *progname,
+                                     const char *prog_path,
+                                     const char **prog_argv,
+                                     bool null_stderr, bool exit_on_error,
+                                     char *read_buf, size_t read_bufsize,
+                                     done_read_fn done_read,
+                                     void *private_data);
+
+
+/* Write size bytes starting at buf into the pipe and in the meanwhile
+   possibly call the done_read function specified in create_filter.
+   The done_read function may be called in a different thread than
+   the current thread, depending on the platform.  However, it will
+   always be called before filter_write has returned (or else will be
+   delayed to the next call to filter_write or filter_close).  Return
+   only after all the entire buffer has been written to the pipe or
+   the subprocess has exited.
+
+   If there is a problem reading or writing, return -1 and set errno.
+   If the subprocess exits early with nonzero status, return the status.
+   (In either case, filter_write will instead exit if exit_on_error was
+   passed as true).
+
+   If the subprocess exits early with zero status, subsequent writes
+   will becomes no-ops and zero is returned.
+
+   Otherwise return 0.  */
+extern int filter_write (struct filter *f,
+                        const char *buf, size_t size);
+
+/* Finish reading the output via the done_read function specified in
+   create_filter.  The done_read function may be called in a different
+   thread than.  However, it will always be called before filter_close
+   has returned.  The write side of the pipe is closed as soon as
+   filter_close starts, while the read side will be closed just before
+   it finishes.  If there is a problem reading or closing the pipe,
+   return -1 and set errno.  If the subprocess exits early with nonzero
+   status, return the status.  (In either case, filter_close will
+   instead exit if exit_on_error was passed as true).
+
+   Otherwise return 0.  */
+extern int filter_close (struct filter *f);
+
+#ifdef __cplusplus
+}
+#endif
+
+
+#endif /* _PIPE_FILTER_H */
+
diff --git a/modules/pipe-filter b/modules/pipe-filter
new file mode 100644
index 0000000..9f04daf
--- /dev/null
+++ b/modules/pipe-filter
@@ -0,0 +1,33 @@
+Description:
+Filtering of data through a subprocess.
+
+Files:
+lib/pipe-filter.h
+lib/pipe-filter.c
+
+Depends-on:
+pipe
+wait-process
+error
+exit
+gettext-h
+stdbool
+stdint
+sys_select
+unistd
+
+configure.ac:
+AC_REQUIRE([AC_C_INLINE])
+AC_CHECK_FUNCS([select])
+
+Makefile.am:
+lib_SOURCES += pipe-filter.c
+
+Include:
+"pipe-filter.h"
+
+License:
+GPL
+
+Maintainer:
+Paolo Bonzini
diff --git a/modules/pipe-filter-tests b/modules/pipe-filter-tests
new file mode 100644
index 0000000..acdcc55
--- /dev/null
+++ b/modules/pipe-filter-tests
@@ -0,0 +1,15 @@
+Files:
+tests/test-pipe-filter
+tests/test-pipe-filter-main.c
+tests/test-pipe-filter-helper.c
+
+Depends-on:
+sleep
+progname
+
+configure.ac:
+AC_CHECK_HEADERS_ONCE([unistd.h sys/wait.h])
+
+Makefile.am:
+TESTS += test-pipe-filter.sh
+check_PROGRAMS += test-pipe-filter-main test-pipe-filter-helper
diff --git a/tests/test-pipe-filter-helper.c b/tests/test-pipe-filter-helper.c
new file mode 100644
index 0000000..ed82632
--- /dev/null
+++ b/tests/test-pipe-filter-helper.c
@@ -0,0 +1,38 @@
+/* Helper program invoked by test-pipe-filter-main.
+
+   Copyright (C) 2009 Free Software Foundation, Inc.
+   Written by Paolo Bonzini <address@hidden>, 2009.
+
+   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 <stdio.h>
+#include <stdlib.h>
+
+int main()
+{
+  int i, j;
+  for (;;)
+    {
+      if (scanf (" %d", &i) != 1)
+       break;
+      if (scanf (" %d", &j) != 1)
+       break;
+      if (j == -1)
+       exit (i);
+      while (i <= j)
+       printf ("%d\n", i++);
+    }
+  exit (0);
+}
diff --git a/tests/test-pipe-filter-main.c b/tests/test-pipe-filter-main.c
new file mode 100644
index 0000000..742724c
--- /dev/null
+++ b/tests/test-pipe-filter-main.c
@@ -0,0 +1,101 @@
+/* Test harness for pipe-filter.
+
+   Copyright (C) 2009 Free Software Foundation, Inc.
+   Written by Paolo Bonzini <address@hidden>, 2009.
+
+   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 <stdio.h>
+#include <assert.h>
+#include <errno.h>
+#include <unistd.h>
+#include <string.h>
+#include <signal.h>
+#include "pipe-filter.h"
+#include "progname.h"
+
+void dummy_read_cb (char *buf, size_t nread, void *data)
+{
+}
+
+void read_cb (char *buf, size_t nread, void *data)
+{
+  write (STDOUT_FILENO, buf, nread);
+}
+
+int filter_writes (struct filter *f, const char *s)
+{
+  filter_write (f, s, strlen (s));
+}
+
+int main (int argc, char **argv)
+{
+  struct filter *f;
+  const char *path[] = { NULL, NULL };
+  char buf[512];
+  int rc;
+
+  set_program_name (argv[0]);
+
+#ifdef SIGPIPE
+  signal (SIGPIPE, SIG_IGN);
+#endif
+
+  /* Test writing to a nonexistent program traps sooner or later.  */
+  path[0] = "/nonexistent/blah";
+  f = filter_create ("pipe-filter-test", path[0], path, true, false,
+                    buf, sizeof(buf), dummy_read_cb, NULL);
+  sleep (1);
+  rc = filter_write (f, "", 1);
+  if (rc != 127 && rc != -1)
+    abort ();
+  if (filter_close (f) != 127)
+    abort ();
+  printf ("Test 1 passed.\n");
+  fflush (stdout);
+
+  /* Test returning the exit status.  */
+  path[0] = "./test-pipe-filter-helper";
+  f = filter_create ("pipe-filter-test", path[0], path, true, false,
+                    buf, sizeof(buf), dummy_read_cb, NULL);
+  filter_writes (f, "1 -1");
+  if (filter_close (f) != 1)
+    abort ();
+  printf ("Test 2 passed.\n");
+  fflush (stdout);
+
+  /* Same, but test returning the status in filter_write.  */
+  f = filter_create ("pipe-filter-test", path[0], path, true, false,
+                    buf, sizeof(buf), dummy_read_cb, NULL);
+  filter_writes (f, "1 -1\n");
+  sleep (1);
+  rc = filter_write (f, " ", 1); 
+  if (rc != 1 && rc != -1)
+    abort ();
+  if (filter_close (f) != 1)
+    abort ();
+  printf ("Test 3 passed.\n");
+  fflush (stdout);
+
+  /* Now test asynchronous I/O.  */
+  f = filter_create ("pipe-filter-test", path[0], path, true, true,
+                    buf, sizeof(buf), read_cb, NULL);
+  filter_writes (f, "1 500\n");
+  filter_writes (f, "501\n");
+  filter_writes (f, "1000");
+  filter_close (f);
+  printf ("Test 4 passed.\n");
+  fflush (stdout);
+}
diff --git a/tests/test-pipe-filter.sh b/tests/test-pipe-filter.sh
new file mode 100755
index 0000000..3e983be
--- /dev/null
+++ b/tests/test-pipe-filter.sh
@@ -0,0 +1,32 @@
+#! /bin/sh
+
+# pipe-filter test driver.
+#
+# Copyright (C) 2009 Free Software Foundation, Inc.
+# Written by Paolo Bonzini <address@hidden>, 2009.
+#
+# 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/>.  */
+
+./test-pipe-filter-main | {
+  set -e
+  read a && test "$a" = "Test 1 passed."
+  read a && test "$a" = "Test 2 passed."
+  read a && test "$a" = "Test 3 passed."
+  i=1
+  while [ $i -le 1000 ]; do
+    read a && test "$a" = "$i"
+    let i++ || i=`expr $i + 1`
+  done
+  read a && test "$a" = "Test 4 passed."
+}
-- 
1.6.2.5





reply via email to

[Prev in Thread] Current Thread [Next in Thread]