1093 lines
25 KiB
Diff
1093 lines
25 KiB
Diff
From 95b780c1d0549678c0a244c6e2112ec97edf0839 Mon Sep 17 00:00:00 2001
|
|
From: DJ Delorie <dj@redhat.com>
|
|
Date: Fri, 14 Mar 2025 16:08:35 -0400
|
|
Subject: stdio: Add more setvbuf tests
|
|
|
|
|
|
Conflicts:
|
|
stdio-common/Makefile (context, line numbers)
|
|
|
|
diff --git a/stdio-common/Makefile b/stdio-common/Makefile
|
|
index 31f40cf57c..3fd33b836d 100644
|
|
--- a/stdio-common/Makefile
|
|
+++ b/stdio-common/Makefile
|
|
@@ -304,6 +304,11 @@ endif
|
|
endif
|
|
endif
|
|
|
|
+tests-internal = \
|
|
+ tst-setvbuf2 \
|
|
+ tst-setvbuf2-ind \
|
|
+ # tests-internal
|
|
+
|
|
test-srcs = \
|
|
$(xprintf-srcs) \
|
|
$(xscanf-srcs) \
|
|
@@ -348,6 +353,11 @@ tests-special += \
|
|
$(objpfx)tst-vfprintf-width-prec-mem.out \
|
|
# tests-special
|
|
|
|
+tests-container += \
|
|
+ tst-setvbuf2 \
|
|
+ tst-setvbuf2-ind
|
|
+ # tests-container
|
|
+
|
|
generated += \
|
|
$(foreach f,$(xprintf-stems),$(f)-mem.out) \
|
|
$(foreach f,$(xprintf-stems),$(f).mtrace) \
|
|
@@ -602,6 +612,10 @@ $(objpfx)tst-setvbuf1-cmp.out: tst-setvb
|
|
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..6cc83355f3
|
|
--- /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;
|
|
+ xpipe (test_pipe);
|
|
+ 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
|
|
+
|