[v2,2/2] stdio: Add more setvbuf tests

Message ID xna596e0up.fsf@greed.delorie.com (mailing list archive)
State Under Review
Delegated to: Joseph Myers
Headers
Series ptmx & tst-setvbuf2 |

Checks

Context Check Description
redhat-pt-bot/TryBot-apply_patch success Patch applied to master at the time it was sent
redhat-pt-bot/TryBot-32bit success Build for i686
linaro-tcwg-bot/tcwg_glibc_build--master-arm success Build passed
linaro-tcwg-bot/tcwg_glibc_check--master-arm success Test passed
linaro-tcwg-bot/tcwg_glibc_build--master-aarch64 success Build passed
linaro-tcwg-bot/tcwg_glibc_check--master-aarch64 success Test passed

Commit Message

DJ Delorie March 27, 2025, 7:21 p.m. UTC
  -- >8 --
changes since v2:
use xfopen
use xmalloc
use xmkfifo
style and formatting
  

Comments

Joseph Myers March 31, 2025, 9:35 p.m. UTC | #1
On Thu, 27 Mar 2025, DJ Delorie wrote:

> +      while (i < BUFSIZ)
> +	{
> +	  wn = write (fd, test_data+i, BUFSIZ-i);

We'd normally use spaces around binary operators '+' and '-'.

> +  writer_thread_tid = xpthread_create (NULL, writer_thread_proc,
> +				       (void *)& thread_data);
> +}
> +
> +static void
> +start_writer_thread_n (const char *fname)
> +{
> +  debug;
> +  thread_data.fd = 0;
> +  thread_data.fname = fname;
> +  writer_thread_tid = xpthread_create (NULL, writer_thread_proc,
> +				       (void *)& thread_data);

We wouldn't normally have a space after unary '&'.

> +static void
> +start_reader_thread (int fd)
> +{
> +  debug;
> +  thread_data.fd = fd;
> +  thread_data.fname = NULL;
> +  reader_thread_tid = xpthread_create (NULL, reader_thread_proc,
> +				       (void *)& thread_data);
> +}
> +
> +static void
> +start_reader_thread_n (const char *fname)
> +{
> +  debug;
> +  thread_data.fd = 0;
> +  thread_data.fname = fname;
> +  reader_thread_tid = xpthread_create (NULL, reader_thread_proc,
> +				       (void *)& thread_data);

Likewise.

> +    case test_source_pipe:
> +      {
> +	debug;
> +	TEST_COMPARE (pipe (test_pipe), 0);

It looks like this is a case where the subsequent code expects pipe to 
have succeeded, so xpipe would be better.

> +  if (test_stream_reads[s])
> +    {
> +      char buf[10];
> +      dumpfp (fp);
> +      size_t fc = fread (buf, 1, 10-1, fp);

Another case where spaces around a binary operator would seem appropriate 
(or sizeof buf - 1 if that's what's meant).

> +	  TEST_COMPARE_BLOB (buf, count-1, test_data+1, count-1);

More such cases for spaces.

> +	  TEST_COMPARE_BLOB (fp->_IO_read_base, count, test_data, count);
> +	}
> +    }
> +  else
> +    {
> +      dumpfp (fp);
> +      test_put_string (fp, test_data+1, 10-1);

Likewise.

> +  if (test_stream_reads[s])
> +    {
> +      char buf[10];
> +      dumpfp (fp);
> +      size_t fc = fread (buf, 1, 10-1, fp);

Likewise.

> +      /* We already checked for the first character being 'a'.  */
> +      if (count > 1)
> +	{
> +	  TEST_COMPARE_BLOB (buf, count-1, test_data+1, count-1);

Likewise.

> +  else
> +    {
> +      dumpfp (fp);
> +      test_put_string (fp, test_data+1, 10-1);

Likewise.

> +      for (i=0; i<=argc; i++)

Likewise.

> +  for (enum test_source_case f = 0; f < test_source_count; ++ f)
> +    for (enum test_stream_case s = 0; s < test_stream_count; ++ s)
> +      for (enum test_config_case c = 0; c < test_config_count; ++ c)

We wouldn't normally have spaces after unary '++'.

OK with the fixes indicated.
  
DJ Delorie April 1, 2025, 8:41 p.m. UTC | #2
Joseph Myers <josmyers@redhat.com> writes:
> OK with the fixes indicated.

Indicated fixes fixed, and pushed.  Thanks!
  
Joseph Myers April 2, 2025, 5:43 p.m. UTC | #3
The new test fails to link for Hurd (undefined references to pthread 
functions), it will need to link with $(shared-thread-library).
  
DJ Delorie April 2, 2025, 6:59 p.m. UTC | #4
Joseph Myers <josmyers@redhat.com> writes:
> The new test fails to link for Hurd (undefined references to pthread 
> functions), it will need to link with $(shared-thread-library).

Not having a working hurd install... like this?

diff --git a/stdio-common/Makefile b/stdio-common/Makefile
index 3fd33b836d..3709222266 100644
--- a/stdio-common/Makefile
+++ b/stdio-common/Makefile
@@ -767,8 +767,9 @@ $(objpfx)tst-setvbuf1-cmp.out: tst-setvbuf1.expect $(objpfx)tst-setvbuf1.out
 	$(evaluate-test)
 
 CFLAGS-tst-setvbuf2.c += -DIND_PROC=\"$(objpfx)tst-setvbuf2-ind\"
-$(objpfx)tst-setvbuf2-ind : $(objpfx)tst-setvbuf2-ind.o
+$(objpfx)tst-setvbuf2-ind : $(objpfx)tst-setvbuf2-ind.o $(shared-thread-library)
 $(objpfx)tst-setvbuf2.out: $(objpfx)tst-setvbuf2-ind
+$(objpfx)tst-setvbuf2 : $(shared-thread-library)
 
 $(objpfx)tst-printf-round: $(libm)
 $(objpfx)tst-scanf-round: $(libm)
  
Joseph Myers April 2, 2025, 10:16 p.m. UTC | #5
On Wed, 2 Apr 2025, DJ Delorie wrote:

> Joseph Myers <josmyers@redhat.com> writes:
> > The new test fails to link for Hurd (undefined references to pthread 
> > functions), it will need to link with $(shared-thread-library).
> 
> Not having a working hurd install... like this?

Yes, like that (the typical way to test this sort of thing is with 
build-many-glibcs.py - you can build compilers and glibcs just for 
i686-gnu or x86_64-gnu without needing to build all the other 
configurations).
  
DJ Delorie April 2, 2025, 11:42 p.m. UTC | #6
Joseph Myers <josmyers@redhat.com> writes:
> Yes, like that (the typical way to test this sort of thing is with 
> build-many-glibcs.py - you can build compilers and glibcs just for 
> i686-gnu or x86_64-gnu without needing to build all the other 
> configurations).

Right, this is just a build failure.  I used to have a Hurd VM for
testing but a recent qemu upgrade broke it.  Pushed, thanks.

Perhaps we[*] should add a Hurd CI/CD pretest trybot?


[*] and by "we" I mean "someone besides me" ;-)
  
Andreas Schwab April 3, 2025, 8:19 a.m. UTC | #7
On Mär 27 2025, DJ Delorie wrote:

> diff --git a/stdio-common/Makefile b/stdio-common/Makefile
> index d3733d0c3d..2dcd7a7020 100644
> --- a/stdio-common/Makefile
> +++ b/stdio-common/Makefile
> @@ -351,7 +351,9 @@ endif
>  endif
>  
>  tests-container += \
> -  tst-popen3
> +  tst-popen3 \
> +  tst-setvbuf2 \
> +  tst-setvbuf2-ind
>    # tests-container
>  
>  generated += \
> @@ -363,6 +365,8 @@ generated += \
>  
>  tests-internal = \
>    tst-grouping_iterator \
> +  tst-setvbuf2 \
> +  tst-setvbuf2-ind \
>    # tests-internal

There is a warning from make:

../Rules:244: target '.../stdio-common/tst-setvbuf2' given more than once in the same rule
../Rules:244: target '.../stdio-common/tst-setvbuf2-ind' given more than once in the same rule
  
DJ Delorie April 3, 2025, 5:42 p.m. UTC | #8
Andreas Schwab <schwab@suse.de> writes:
> There is a warning from make:
>
> ../Rules:244: target '.../stdio-common/tst-setvbuf2' given more than once in the same rule
> ../Rules:244: target '.../stdio-common/tst-setvbuf2-ind' given more than once in the same rule

I know.  I couldn't find any other way to tell the build that a given
test was both a container test and needed internal access.
  
Andreas Schwab April 7, 2025, 8:40 a.m. UTC | #9
On Apr 03 2025, DJ Delorie wrote:

> Andreas Schwab <schwab@suse.de> writes:
>> There is a warning from make:
>>
>> ../Rules:244: target '.../stdio-common/tst-setvbuf2' given more than once in the same rule
>> ../Rules:244: target '.../stdio-common/tst-setvbuf2-ind' given more than once in the same rule
>
> I know.  I couldn't find any other way to tell the build that a given
> test was both a container test and needed internal access.

The target list that contains both test-internal and test-container
could be sorted to remove the duplicates.

diff --git a/Rules b/Rules
index c8adc00008..ae23070845 100644
--- a/Rules
+++ b/Rules
@@ -241,7 +241,7 @@ $(addprefix $(objpfx),$(binaries-shared-notests)): %: %.o \
 endif
 
 ifneq "$(strip $(binaries-shared-tests))" ""
-$(addprefix $(objpfx),$(binaries-shared-tests)): %: %.o \
+$(addprefix $(objpfx),$(sort $(binaries-shared-tests))): %: %.o \
   $(link-extra-libs-tests) \
   $(sort $(filter $(common-objpfx)lib%,$(link-libc))) \
   $(addprefix $(csu-objpfx),start.o) $(+preinit) $(+postinit)
  
Florian Weimer April 7, 2025, 9:34 a.m. UTC | #10
* Andreas Schwab:

> On Apr 03 2025, DJ Delorie wrote:
>
>> Andreas Schwab <schwab@suse.de> writes:
>>> There is a warning from make:
>>>
>>> ../Rules:244: target '.../stdio-common/tst-setvbuf2' given more than once in the same rule
>>> ../Rules:244: target '.../stdio-common/tst-setvbuf2-ind' given more than once in the same rule
>>
>> I know.  I couldn't find any other way to tell the build that a given
>> test was both a container test and needed internal access.
>
> The target list that contains both test-internal and test-container
> could be sorted to remove the duplicates.
>
> diff --git a/Rules b/Rules
> index c8adc00008..ae23070845 100644
> --- a/Rules
> +++ b/Rules
> @@ -241,7 +241,7 @@ $(addprefix $(objpfx),$(binaries-shared-notests)): %: %.o \
>  endif
>  
>  ifneq "$(strip $(binaries-shared-tests))" ""
> -$(addprefix $(objpfx),$(binaries-shared-tests)): %: %.o \
> +$(addprefix $(objpfx),$(sort $(binaries-shared-tests))): %: %.o \
>    $(link-extra-libs-tests) \
>    $(sort $(filter $(common-objpfx)lib%,$(link-libc))) \
>    $(addprefix $(csu-objpfx),start.o) $(+preinit) $(+postinit)

Looks good to me.

Thanks,
Florian
  

Patch

diff --git a/stdio-common/Makefile b/stdio-common/Makefile
index d3733d0c3d..2dcd7a7020 100644
--- a/stdio-common/Makefile
+++ b/stdio-common/Makefile
@@ -351,7 +351,9 @@  endif
 endif
 
 tests-container += \
-  tst-popen3
+  tst-popen3 \
+  tst-setvbuf2 \
+  tst-setvbuf2-ind
   # tests-container
 
 generated += \
@@ -363,6 +365,8 @@  generated += \
 
 tests-internal = \
   tst-grouping_iterator \
+  tst-setvbuf2 \
+  tst-setvbuf2-ind \
   # tests-internal
 
 test-srcs = \
@@ -710,6 +714,10 @@  $(objpfx)tst-setvbuf1-cmp.out: tst-setvbuf1.expect $(objpfx)tst-setvbuf1.out
 	cmp $^ > $@; \
 	$(evaluate-test)
 
+CFLAGS-tst-setvbuf2.c += -DIND_PROC=\"$(objpfx)tst-setvbuf2-ind\"
+$(objpfx)tst-setvbuf2-ind : $(objpfx)tst-setvbuf2-ind.o
+$(objpfx)tst-setvbuf2.out: $(objpfx)tst-setvbuf2-ind
+
 $(objpfx)tst-printf-round: $(libm)
 $(objpfx)tst-scanf-round: $(libm)
 
diff --git a/stdio-common/tst-setvbuf2-ind.c b/stdio-common/tst-setvbuf2-ind.c
new file mode 100644
index 0000000000..fda2942c24
--- /dev/null
+++ b/stdio-common/tst-setvbuf2-ind.c
@@ -0,0 +1,2 @@ 
+#define INDEPENDENT_PART 1
+#include "tst-setvbuf2.c"
diff --git a/stdio-common/tst-setvbuf2.c b/stdio-common/tst-setvbuf2.c
new file mode 100644
index 0000000000..fb1a976ea8
--- /dev/null
+++ b/stdio-common/tst-setvbuf2.c
@@ -0,0 +1,1030 @@ 
+/* Test setvbuf under various conditions.
+   Copyright (C) 2025 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library 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
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <https://www.gnu.org/licenses/>.  */
+
+/* This file is used twice, once as the test itself (where do_test
+   is defined) and once as a subprocess we spawn to test stdin et all
+   (where main is defined).  INDEPENDENT_PART is defined for the
+   latter.
+
+   Note also that the purpose of this test is to test setvbuf, not the
+   underlying buffering code.  */
+
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdio_ext.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <string.h>
+#include <errno.h>
+#include <signal.h>
+#include <libio.h>
+#include <termios.h>
+
+#include <support/support.h>
+#include <support/check.h>
+#include <support/temp_file.h>
+#include <support/xstdio.h>
+#include <support/xunistd.h>
+#include <support/xthread.h>
+#include <support/tty.h>
+
+/* Dear future developer: If you are reading this, you are likely
+   trying to change or understand this test.  In that case, these
+   debug/dump macros will be helpful.  */
+#if 0
+# define debug printf ("\033[3%dm%s:%d\033[0m\n", \
+		       (__LINE__%6)+1,__FUNCTION__, __LINE__);
+
+static void
+dumpfp (FILE *fp)
+{
+  char f[10], *p=f;
+
+  if (fp->_flags & _IO_UNBUFFERED)
+    *p++ = 'N';
+  if (fp->_flags & _IO_LINE_BUF)
+    *p++ = 'L';
+  if (p == f)
+    *p++ = 'B';
+  *p = 0;
+
+  printf ("FILE %p flags %s"
+	  " read %p \033[%dm%+ld \033[%dm%+ld\033[0m"
+	  " write %p \033[%dm%+ld \033[%dm%+ld\033[0m %ld"
+	  " buf %p \033[%dm%+ld\033[0m  sz %ld pend %ld\n",
+	  fp, f,
+
+	  fp->_IO_read_base,
+	  fp->_IO_read_ptr == fp->_IO_read_base ? 33 : 32,
+	  fp->_IO_read_ptr - fp->_IO_read_base,
+	  fp->_IO_read_end == fp->_IO_read_base ? 33 : 36,
+	  fp->_IO_read_end - fp->_IO_read_base,
+
+	  fp->_IO_write_base,
+	  fp->_IO_write_ptr == fp->_IO_write_base ? 33 : 32,
+	  fp->_IO_write_ptr - fp->_IO_write_base,
+	  fp->_IO_write_end == fp->_IO_write_base ? 33 : 36,
+	  fp->_IO_write_end - fp->_IO_write_base,
+	  fp->_IO_write_end - fp->_IO_write_base,
+
+	  fp->_IO_buf_base,
+	  fp->_IO_buf_end == fp->_IO_buf_base ? 33 : 35,
+	  fp->_IO_buf_end - fp->_IO_buf_base,
+	  __fbufsize (fp), __fpending (fp)
+	  );
+}
+#else
+# define debug
+# define dumpfp(FP)
+#endif
+
+#ifndef INDEPENDENT_PART
+/* st_blksize value for that file, or BUFSIZ if out of range.  */
+static int blksize = BUFSIZ;
+#endif
+
+/* Our test buffer.  */
+#define TEST_BUFSIZE 42
+static int bufsize = TEST_BUFSIZE < BUFSIZ ? TEST_BUFSIZE : BUFSIZ;
+static char *buffer;
+
+/* Test data, both written to that file and used as an in-memory
+   stream.  */
+char test_data[2 * BUFSIZ];
+
+#define TEST_STRING "abcdef\n"
+
+enum test_source_case
+  {
+    test_source_file,
+    test_source_pipe,
+    test_source_fifo,
+    test_source_pseudo_terminal,
+    test_source_dev_null,
+    test_source_count,
+  };
+
+static const char *const test_source_name[test_source_count] =
+  {
+    "regular file",
+    "pipe",
+    "fifo",
+    "pseudo_terminal",
+    "dev_null"
+  };
+
+enum test_stream_case
+  {
+    test_stream_stdin,
+    test_stream_stdout,
+    test_stream_stderr,
+    test_stream_fopen_r,
+    test_stream_fdopen_r,
+    test_stream_fopen_w,
+    test_stream_fdopen_w,
+    test_stream_count
+  };
+
+static bool test_stream_reads[test_stream_count] =
+  {
+    true,
+    false,
+    false,
+    true,
+    true,
+    false,
+    false
+  };
+
+static const char *const test_stream_name[test_stream_count] =
+  {
+    "stdin",
+    "stdout",
+    "stderr",
+    "fopen (read)",
+    "fdopen (read)",
+    "fopen (write)",
+    "fdopen (write)"
+  };
+
+enum test_config_case
+  {
+    test_config_none,
+    test_config_unbuffered,
+    test_config_line,
+    test_config_fully,
+    test_config_count
+  };
+
+static const char *const test_config_name[test_config_count] =
+  {
+    "no change",
+    "unbuffered",
+    "line buffered",
+    "fully buffered"
+  };
+
+FILE *test_stream;
+
+char *test_file_name = NULL;
+int pty_fd;
+char *test_pipe_name = NULL;
+int test_pipe[2];
+
+/* This is either -1 or represents a pre-opened file descriptor for
+   the test as returned by prepare_test_file.  */
+int test_fd;
+
+/*------------------------------------------------------------*/
+
+/* Note that throughout this test we reopen, remove, and change
+   to/from a fifo, the test file.  This would normally cause a race
+   condition, except that we're in a test container.  No other process
+   can run in the test container simultaneously.  */
+
+void
+prepare_test_data (void)
+{
+  buffer = (char *) xmalloc (bufsize);
+
+#ifndef INDEPENDENT_PART
+  /* Both file and pipe need this.  */
+  if (test_file_name == NULL)
+    {
+      debug;
+      int fd = create_temp_file ("tst-setvbuf2", &test_file_name);
+      TEST_VERIFY_EXIT (fd != -1);
+      struct stat64 st;
+      xfstat64 (fd, &st);
+      if (st.st_blksize > 0 && st.st_blksize < BUFSIZ)
+	blksize = st.st_blksize;
+      xclose (fd);
+    }
+#endif
+
+  for (size_t i = 0; i < 2 * BUFSIZ; i++)
+    {
+      unsigned char c = TEST_STRING[i % strlen (TEST_STRING)];
+      test_data[i] = c;
+    }
+}
+
+#ifndef INDEPENDENT_PART
+
+/* These functions provide a source/sink for the "other" side of any
+   pipe-style descriptor we're using for test.  */
+
+static pthread_t writer_thread_tid = 0;
+static pthread_t reader_thread_tid = 0;
+
+typedef struct {
+  int fd;
+  const char *fname;
+} ThreadData;
+/* It's OK if this is static, we only run one at a time.  */
+ThreadData thread_data;
+
+static void *
+writer_thread_proc (void *closure)
+{
+  ThreadData *td = (ThreadData *) closure;
+  int fd;
+  int i;
+  ssize_t wn;
+  debug;
+
+  if (td->fname)
+    td->fd = xopen (td->fname, O_WRONLY, 0777);
+  fd = td->fd;
+
+  while (1)
+    {
+      i = 0;
+      while (i < BUFSIZ)
+	{
+	  wn = write (fd, test_data+i, BUFSIZ-i);
+	  if (wn <= 0)
+	    break;
+	  i += wn;
+	}
+    }
+  return NULL;
+}
+
+static void *
+reader_thread_proc (void *closure)
+{
+  ThreadData *td = (ThreadData *) closure;
+  int fd;
+  ssize_t rn;
+  int n = 0;
+  debug;
+
+  if (td->fname)
+    td->fd = xopen (td->fname, O_RDONLY, 0777);
+  fd = td->fd;
+
+  while (1)
+    {
+      char buf[BUFSIZ];
+      rn = read (fd, buf, BUFSIZ);
+      if (rn <= 0)
+	break;
+      TEST_COMPARE_BLOB (buf, rn, test_data+n, rn);
+      n += rn;
+    }
+  return NULL;
+}
+
+static void
+start_writer_thread (int fd)
+{
+  debug;
+  thread_data.fd = fd;
+  thread_data.fname = NULL;
+  writer_thread_tid = xpthread_create (NULL, writer_thread_proc,
+				       (void *)& thread_data);
+}
+
+static void
+start_writer_thread_n (const char *fname)
+{
+  debug;
+  thread_data.fd = 0;
+  thread_data.fname = fname;
+  writer_thread_tid = xpthread_create (NULL, writer_thread_proc,
+				       (void *)& thread_data);
+}
+
+static void
+end_writer_thread (void)
+{
+  debug;
+  if (writer_thread_tid)
+    {
+      pthread_cancel (writer_thread_tid);
+      xpthread_join (writer_thread_tid);
+      xclose (thread_data.fd);
+      writer_thread_tid = 0;
+    }
+}
+
+static void
+start_reader_thread (int fd)
+{
+  debug;
+  thread_data.fd = fd;
+  thread_data.fname = NULL;
+  reader_thread_tid = xpthread_create (NULL, reader_thread_proc,
+				       (void *)& thread_data);
+}
+
+static void
+start_reader_thread_n (const char *fname)
+{
+  debug;
+  thread_data.fd = 0;
+  thread_data.fname = fname;
+  reader_thread_tid = xpthread_create (NULL, reader_thread_proc,
+				       (void *)& thread_data);
+}
+
+static void
+end_reader_thread (void)
+{
+  debug;
+  if (reader_thread_tid)
+    {
+      pthread_cancel (reader_thread_tid);
+      xpthread_join (reader_thread_tid);
+      xclose (thread_data.fd);
+      reader_thread_tid = 0;
+    }
+}
+
+/*------------------------------------------------------------*/
+
+/* These two functions are reponsible for choosing a file to be tested
+   against, typically by returning a filename but in a few cases also
+   providing a file descriptor (i.e. for fdopen).  */
+
+static const char *
+prepare_test_file (enum test_source_case f, enum test_stream_case s)
+{
+  debug;
+
+  test_fd = -1;
+
+  switch (f)
+    {
+    case test_source_file:
+      {
+	if (test_stream_reads[f])
+	  {
+	    debug;
+	    FILE *fp = xfopen (test_file_name, "w");
+	    TEST_VERIFY_EXIT (fwrite (test_data, 1, 2 * BUFSIZ, fp)
+			      == 2 * BUFSIZ);
+	    xfclose (fp);
+	  }
+	debug;
+	return test_file_name;
+      }
+
+    case test_source_pipe:
+      {
+	debug;
+	TEST_COMPARE (pipe (test_pipe), 0);
+	if (test_stream_reads[s])
+	  {
+	    start_writer_thread (test_pipe[1]);
+	    test_fd = test_pipe[0];
+	  }
+	else
+	  {
+	    start_reader_thread (test_pipe[0]);
+	    test_fd = test_pipe[1];
+	  }
+	test_pipe_name = xasprintf ("/proc/self/fd/%d", test_fd);
+	debug;
+	return test_pipe_name;
+      }
+
+    case test_source_fifo:
+      {
+	/* We do not want to fail/exit if the file doesn't exist.  */
+	unlink (test_file_name);
+	xmkfifo (test_file_name, 0600);
+	debug;
+	if (test_stream_reads[s])
+	  start_writer_thread_n (test_file_name);
+	else
+	  start_reader_thread_n (test_file_name);
+	debug;
+	return test_file_name;
+      }
+
+    case test_source_pseudo_terminal:
+      {
+	support_openpty (&pty_fd, &test_fd, &test_pipe_name, NULL, NULL);
+
+	debug;
+	if (test_stream_reads[s])
+	  start_writer_thread (pty_fd);
+	else
+	  start_reader_thread (pty_fd);
+
+	debug;
+	return test_pipe_name;
+      }
+
+    case test_source_dev_null:
+	debug;
+      return "/dev/null";
+
+    default:
+      abort ();
+    }
+}
+
+static void
+unprepare_test_file (FILE *fp,
+		     enum test_source_case f,
+		     enum test_stream_case s)
+{
+  debug;
+  switch (f)
+    {
+    case test_source_file:
+      break;
+
+    case test_source_pipe:
+      free (test_pipe_name);
+      if (test_stream_reads[s])
+	end_writer_thread ();
+      else
+	end_reader_thread ();
+      break;
+
+    case test_source_fifo:
+      if (test_stream_reads[s])
+	end_writer_thread ();
+      else
+	end_reader_thread ();
+      unlink (test_file_name);
+      break;
+
+    case test_source_pseudo_terminal:
+      free (test_pipe_name);
+      if (test_stream_reads[s])
+	end_writer_thread ();
+      else
+	end_reader_thread ();
+      break;
+
+    case test_source_dev_null:
+      break;
+
+    default:
+      abort ();
+    }
+  debug;
+}
+
+/*------------------------------------------------------------*/
+
+/* This function takes a filename and returns a file descriptor,
+   opened according to the method requested.  */
+
+static FILE *
+open_test_stream (enum test_source_case f, enum test_stream_case s)
+{
+  int fd;
+  FILE *fp;
+  const char *fname;
+
+  debug;
+  fname = prepare_test_file (f, s);
+  if (fname == NULL)
+    return NULL;
+
+  switch (s)
+    {
+    case test_stream_stdin:
+      fp = xfopen (fname, "r");
+      break;
+
+    case test_stream_stdout:
+      fp = xfopen (fname, "w");
+      break;
+
+    case test_stream_stderr:
+      fp = xfopen (fname, "w");
+      break;
+
+    case test_stream_fopen_r:
+      fp = xfopen (fname, "r");
+      break;
+
+    case test_stream_fdopen_r:
+      if (test_fd == -1)
+	fd = xopen (fname, O_RDONLY, 0);
+      else
+	fd = test_fd;
+      fp = fdopen (fd, "r");
+      break;
+
+    case test_stream_fopen_w:
+      fp = xfopen (fname, "w");
+      break;
+
+    case test_stream_fdopen_w:
+      fd = xopen (fname, O_WRONLY|O_CREAT|O_TRUNC, 0777);
+      fp = fdopen (fd, "w");
+      break;
+
+    default:
+      abort ();
+    }
+  TEST_VERIFY_EXIT (fp != NULL);
+
+  if (f == test_source_pseudo_terminal)
+    {
+      struct termios t;
+      /* We disable the NL to CR-LF conversion so that we can compare
+	 data without having to remove the extra CRs.  */
+      if (tcgetattr (fileno (fp), &t) < 0)
+	FAIL_EXIT1 ("tcgetattr failed: %m");
+      t.c_oflag &= ~ONLCR;
+      if (tcsetattr (fileno (fp), TCSANOW, &t) < 0)
+	FAIL_EXIT1 ("tcsetattr failed: %m");
+    }
+
+  debug;
+  printf ("source %s stream %s file %s fd %d\n",
+	  test_source_name[f],
+	  test_stream_name[s], fname, fileno (fp));
+  return fp;
+}
+
+#endif
+
+/*------------------------------------------------------------*/
+
+/* These functions do the actual testing - setting various buffering
+   options and verifying that they buffer as expected.  */
+
+static void
+test_put_string (FILE *fp, const char *s, int count)
+{
+  while (*s && count--)
+    {
+      fputc (*s++, fp);
+      TEST_VERIFY_EXIT (!ferror (fp));
+    }
+}
+
+int
+verify_fully_buffered (FILE *fp,
+		       enum test_source_case f,
+		       enum test_stream_case s,
+		       enum test_config_case c)
+{
+  debug;
+  if (test_stream_reads[s])
+    {
+      char buf[10];
+      dumpfp (fp);
+      size_t fc = fread (buf, 1, 10-1, fp);
+      dumpfp (fp);
+
+      ssize_t count = fp->_IO_read_ptr - fp->_IO_read_base;
+
+      TEST_VERIFY (fp->_IO_read_base != NULL);
+      if (f == test_source_dev_null)
+	{
+	  TEST_VERIFY (fc == 0);
+	  TEST_VERIFY (count == 0);
+	}
+      else if (f == test_source_pseudo_terminal)
+	{
+	  TEST_VERIFY (fc == 9);
+	  TEST_VERIFY (count == 3 || count == 10);
+	}
+      else
+	{
+	  TEST_VERIFY (fc == 9);
+	  TEST_VERIFY (count == 10);
+	}
+
+      /* We already checked for the first character being 'a'.  */
+      if (count > 1)
+	{
+	  TEST_COMPARE_BLOB (buf, count-1, test_data+1, count-1);
+	  TEST_COMPARE_BLOB (fp->_IO_read_base, count, test_data, count);
+	}
+    }
+  else
+    {
+      dumpfp (fp);
+      test_put_string (fp, test_data+1, 10-1);
+      dumpfp (fp);
+      TEST_COMPARE (fp->_IO_write_ptr - fp->_IO_write_base, 10);
+      TEST_COMPARE_BLOB (fp->_IO_write_base, 10, test_data, 10);
+    }
+
+  TEST_COMPARE ((fp->_flags & (_IO_UNBUFFERED | _IO_LINE_BUF)), 0);
+  if (c != test_config_none)
+    TEST_COMPARE (__fbufsize (fp), bufsize);
+  return 0;
+}
+
+int
+verify_line_buffered (FILE *fp,
+		      enum test_source_case f,
+		      enum test_stream_case s,
+		      enum test_config_case c)
+{
+  debug;
+  /* "line buffered" for inputs is not really defined; what you really
+     want here is to control the device providing input.  For GLIBC a
+     line-buffered input is treated as fully buffered.  */
+  if (test_stream_reads[s])
+    {
+      char buf[10];
+      dumpfp (fp);
+      size_t fc = fread (buf, 1, 10-1, fp);
+      dumpfp (fp);
+
+      ssize_t count = fp->_IO_read_ptr - fp->_IO_read_base;
+
+      TEST_VERIFY (fp->_IO_read_base != NULL);
+      if (f == test_source_dev_null)
+	{
+	  TEST_VERIFY (fc == 0);
+	  TEST_VERIFY (count == 0);
+	}
+      else if (f == test_source_pseudo_terminal)
+	{
+	  TEST_VERIFY (fc == 9);
+	  TEST_VERIFY (count == 3 || count == 10);
+	}
+      else
+	{
+	  TEST_VERIFY (fc == 9);
+	  TEST_VERIFY (count == 10);
+	}
+
+      /* We already checked for the first character being 'a'.  */
+      if (count > 1)
+	{
+	  TEST_COMPARE_BLOB (buf, count-1, test_data+1, count-1);
+	  TEST_COMPARE_BLOB (fp->_IO_read_base, count, test_data, count);
+	}
+    }
+  else
+    {
+      dumpfp (fp);
+      test_put_string (fp, test_data+1, 10-1);
+      dumpfp (fp);
+      TEST_COMPARE (fp->_IO_write_ptr - fp->_IO_write_base, 3);
+      /* The first "abcdef\n" got flushed, leaving "abc".  */
+      TEST_COMPARE_BLOB (fp->_IO_write_base, 3, test_data + 7, 3);
+    }
+
+  TEST_COMPARE ((fp->_flags & (_IO_UNBUFFERED | _IO_LINE_BUF)), _IO_LINE_BUF);
+  if (c != test_config_none)
+    TEST_COMPARE (__fbufsize (fp), bufsize);
+  return 0;
+}
+
+int
+verify_unbuffered (FILE *fp,
+		   enum test_source_case f,
+		   enum test_stream_case s,
+		   enum test_config_case c)
+{
+  debug;
+  if (test_stream_reads[s])
+    {
+      /* We've already read one byte.  */
+      dumpfp (fp);
+      TEST_VERIFY (fp->_IO_read_base != NULL);
+      if (f == test_source_dev_null)
+	TEST_COMPARE (fp->_IO_read_ptr - fp->_IO_read_base, 0);
+      else
+	{
+	  TEST_COMPARE (fp->_IO_read_ptr - fp->_IO_read_base, 1);
+	  TEST_COMPARE (fp->_IO_read_base[0], test_data[0]);
+	  TEST_VERIFY (fp->_IO_read_ptr == fp->_IO_read_end);
+	}
+    }
+  else
+    {
+      dumpfp (fp);
+      fputc (test_data[1], fp);
+      dumpfp (fp);
+      TEST_COMPARE (fp->_IO_write_ptr - fp->_IO_write_base, 0);
+      TEST_COMPARE (fp->_IO_write_base[0], test_data[1]);
+      TEST_VERIFY (fp->_IO_write_end == fp->_IO_write_base);
+    }
+  TEST_COMPARE ((fp->_flags & (_IO_UNBUFFERED | _IO_LINE_BUF)),
+	       _IO_UNBUFFERED);
+  TEST_COMPARE (__fbufsize (fp), 1);
+  return 0;
+}
+
+static int
+do_setvbuf (FILE *fp, void *buf, int flags, int size,
+	    enum test_stream_case s)
+{
+  if (s != test_stream_stdout)
+    printf ("SETVBUF %p %p %s %d\n",
+	    fp, buf,
+	    flags == _IONBF ? "_IONBF"
+	    : flags == _IOLBF ? "_IOLBF"
+	    : flags == _IOFBF ? "_IOFBF"
+	    : "???", size);
+  if (setvbuf (fp, buf, flags, size))
+    {
+      perror ("setvbuf");
+      return 1;
+    }
+  return 0;
+}
+
+int
+do_second_part (FILE *fp,
+		enum test_source_case f,
+		enum test_stream_case s,
+		enum test_config_case c)
+{
+  /* At this point, FP is the stream to test according to the other
+     parameters.  */
+
+  int rv = 0;
+  int flags_before;
+  int flags_after;
+
+  debug;
+
+  flags_before = fp->_flags & (_IO_UNBUFFERED | _IO_LINE_BUF);
+
+  /* This is where we do the thing we're testing for.  */
+  switch (c)
+    {
+    case test_config_none:
+      /* Buffering is unchanged.  */
+      break;
+
+    case test_config_unbuffered:
+      do_setvbuf (fp, NULL, _IONBF, 0, s);
+      break;
+
+    case test_config_line:
+      do_setvbuf (fp, buffer, _IOLBF, bufsize, s);
+      break;
+
+    case test_config_fully:
+      do_setvbuf (fp, buffer, _IOFBF, bufsize, s);
+      break;
+
+    default:
+      abort ();
+    }
+
+  flags_after = fp->_flags & (_IO_UNBUFFERED | _IO_LINE_BUF);
+
+  /* Check the buffer mode after we touch it, if we touched it.  */
+  switch (c)
+    {
+    case test_config_none:
+      /* Buffering is unchanged, but may change on the first read/write.  */
+      TEST_COMPARE (flags_after, flags_before);
+      break;
+
+    case test_config_unbuffered:
+      TEST_COMPARE (flags_after, _IO_UNBUFFERED);
+      break;
+
+    case test_config_line:
+      TEST_COMPARE (flags_after, _IO_LINE_BUF);
+      break;
+
+    case test_config_fully:
+      TEST_COMPARE (flags_after, 0);
+      break;
+
+    default:
+      abort ();
+    }
+
+  /* Glibc defers calculating the appropriate buffering mechanism
+     until it reads from or writes to the device.  So we read one
+     character here, and account for that in the tests.  */
+  if (test_stream_reads[s])
+    {
+      dumpfp (fp);
+      int c = fgetc (fp);
+      if (c != TEST_STRING[0] && f != test_source_dev_null)
+	FAIL ("first char read is %c not %c", c, TEST_STRING[0]);
+      dumpfp (fp);
+    }
+  else
+    {
+      dumpfp (fp);
+      fputc (TEST_STRING[0], fp);
+      dumpfp (fp);
+    }
+
+  switch (fp->_flags & (_IO_UNBUFFERED | _IO_LINE_BUF))
+    {
+    case _IO_LINE_BUF:
+      rv += verify_line_buffered (fp, f, s, c);
+      break;
+
+    case _IO_UNBUFFERED:
+      rv += verify_unbuffered (fp, f, s, c);
+      break;
+
+    case 0: /* Fully buffered.  */
+      rv += verify_fully_buffered (fp, f, s, c);
+      break;
+
+    default:
+      abort ();
+    }
+
+
+  fclose (fp);
+  return rv;
+}
+
+/*------------------------------------------------------------*/
+
+#ifdef INDEPENDENT_PART
+/* This part is the independent sub-process we call to test stdin et
+   al.  */
+
+int
+main (int argc, char **argv)
+{
+  /* This is one of the subprocesses we created to test stdin et
+     al.  */
+  FILE *fp;
+
+  /* If we're called as a regular test, instead of as a sub-process,
+     don't complain.  */
+  if (argc == 1)
+    return 0;
+
+  if (argc != 4)
+    {
+      int i;
+      for (i=0; i<=argc; i++)
+	printf ("argv[%d] = `%s'\n", i, argv[i] ?: "(null)");
+      FAIL_EXIT1 ("sub-process called wrong");
+    }
+
+  prepare_test_data ();
+
+  enum test_source_case f = atoi (argv[1]);
+  enum test_stream_case s = atoi (argv[2]);
+  enum test_config_case c = atoi (argv[3]);
+
+  if (s != test_stream_stdout)
+    printf ("\n\033[41mRunning test %s : %s : %s\033[0m\n",
+	    test_source_name[f],
+	    test_stream_name[s],
+	    test_config_name[c]);
+
+  switch (s)
+    {
+    case test_stream_stdin:
+      fp = stdin;
+      break;
+    case test_stream_stdout:
+      fp = stdout;
+      break;
+    case test_stream_stderr:
+      fp = stderr;
+      break;
+    default:
+      abort ();
+    }
+
+  return do_second_part (fp, f, s, c);
+}
+
+#else
+/* This part is the standard test process.  */
+
+/* Spawn an independent sub-process with std* redirected.  */
+int
+recurse (FILE *fp,
+	 enum test_source_case f,
+	 enum test_stream_case s,
+	 enum test_config_case c)
+{
+  /* We need to test stdin, stdout, or stderr, which means creating a
+     subprocess with one of those redirected from FP.  */
+  debug;
+
+  pid_t pid;
+  int status;
+
+  pid = fork ();
+
+  switch (pid)
+    {
+    case -1: /* error */
+      perror ("fork");
+      return 1;
+      break;
+
+    default: /* parent */
+      fclose (fp);
+      xwaitpid (pid, &status, 0);
+      if (WIFEXITED (status)
+	  && WEXITSTATUS (status) == 0)
+	return 0;
+      return 1;
+
+    case 0: /* child */
+      switch (s)
+	{
+	case test_stream_stdin:
+	  xclose (0);
+	  dup2 (fileno (fp), 0);
+	  break;
+	case test_stream_stdout:
+	  xclose (1);
+	  dup2 (fileno (fp), 1);
+	  break;
+	case test_stream_stderr:
+	  xclose (2);
+	  dup2 (fileno (fp), 2);
+	  break;
+	default:
+	  abort ();
+	}
+      fclose (fp);
+
+      /* At this point, we have to run a program... which is tricky to
+	 properly support for remote targets or crosses, because of
+	 glibc versions etc.  Hence we run in a test-container.  */
+
+      char fs[10], ss[10], cs[10];
+      sprintf (fs, "%d", f);
+      sprintf (ss, "%d", s);
+      sprintf (cs, "%d", c);
+      execl (IND_PROC, IND_PROC, fs, ss, cs, NULL);
+      if (s == test_stream_stdout)
+	fprintf (stderr, "execl (%s) failed, ", IND_PROC);
+      else
+	printf ("execl (%s) failed, ", IND_PROC);
+      perror ("The error was");
+      exit (1);
+      break;
+    }
+
+  return 0;
+}
+
+int
+do_test (void)
+{
+  int rv = 0;
+
+  signal (SIGPIPE, SIG_IGN);
+
+  prepare_test_data ();
+
+  for (enum test_source_case f = 0; f < test_source_count; ++ f)
+    for (enum test_stream_case s = 0; s < test_stream_count; ++ s)
+      for (enum test_config_case c = 0; c < test_config_count; ++ c)
+	{
+	  printf ("\n\033[43mRunning test %s : %s : %s\033[0m\n",
+		  test_source_name[f],
+		  test_stream_name[s],
+		  test_config_name[c]);
+
+	  FILE *fp = open_test_stream (f, s);
+
+	  if (fp)
+	    {
+
+	      if (s <= test_stream_stderr)
+		rv += recurse (fp, f, s, c);
+	      else
+		rv += do_second_part (fp, f, s, c);
+
+	      unprepare_test_file (fp, f, s);
+	    }
+	}
+
+  free (buffer);
+
+  printf ("return %d\n", rv);
+  return rv;
+}
+
+# include <support/test-driver.c>
+#endif
+