t0081 (line-buffer): add buffering tests
authorJonathan Nieder <jrnieder@gmail.com>
Mon, 3 Jan 2011 01:07:16 +0000 (19:07 -0600)
committerJonathan Nieder <jrnieder@gmail.com>
Sat, 26 Feb 2011 10:58:21 +0000 (04:58 -0600)
POSIX makes the behavior of read(2) from a pipe fairly clear: a read
from an empty pipe will block until there is data available and any
other read will not block, prefering to return a partial result.
Likewise, fread(3) and fgets(3) are clearly specified to act as
though implemented by calling fgetc(3) in a simple loop.  But the
buffering behavior of fgetc is less clear.

Luckily, no sane platform is going to implement fgetc by calling the
equivalent of read(2) more than once.  fgetc has to be able to
return without filling its buffer to preserve errno when errors are
encountered anyway.  So let's assume the simpler behavior (trust) but
add some tests to catch insane platforms that violate that when they
come (verify).

First check that fread can handle a 0-length read from an empty fifo.
Because open(O_RDONLY) blocks until the writing end is open, open the
writing end of the fifo in advance in a subshell.

Next try short inputs from a pipe that is not filled all the way.

Lastly (two tests) try very large inputs from a pipe that will not fit
in the relevant buffers.  The first of these tests reads a little
more than 8192 bytes, which is BUFSIZ (the size of stdio's buffers)
on this Linux machine.  The second reads a little over 64 KiB (the
pipe capacity on Linux) and is not run unless requested by setting
the GIT_REMOTE_SVN_TEST_BIG_FILES environment variable.

Signed-off-by: Jonathan Nieder <jrnieder@gmail.com>
t/t0081-line-buffer.sh
test-line-buffer.c

index 68d616399548effd6285d26278eee29aa33f245d..33a728ed98edba5c963be666472824e9f493842e 100755 (executable)
@@ -1,10 +1,76 @@
 #!/bin/sh
 
 test_description="Test the svn importer's input handling routines.
+
+These tests exercise the line_buffer library, but their real purpose
+is to check the assumptions that library makes of the platform's input
+routines.  Processes engaged in bi-directional communication would
+hang if fread or fgets is too greedy.
+
+While at it, check that input of newlines and null bytes are handled
+correctly.
 "
 . ./test-lib.sh
 
-test_expect_success 'read greeting' '
+test -n "$GIT_REMOTE_SVN_TEST_BIG_FILES" && test_set_prereq EXPENSIVE
+
+generate_tens_of_lines () {
+       tens=$1 &&
+       line=$2 &&
+
+       i=0 &&
+       while test $i -lt "$tens"
+       do
+               for j in a b c d e f g h i j
+               do
+                       echo "$line"
+               done &&
+               : $((i = $i + 1)) ||
+               return
+       done
+}
+
+long_read_test () {
+       : each line is 10 bytes, including newline &&
+       line=abcdefghi &&
+       echo "$line" >expect &&
+
+       if ! test_declared_prereq PIPE
+       then
+               echo >&4 "long_read_test: need to declare PIPE prerequisite"
+               return 127
+       fi &&
+       tens_of_lines=$(($1 / 100 + 1)) &&
+       lines=$(($tens_of_lines * 10)) &&
+       readsize=$((($lines - 1) * 10 + 3)) &&
+       copysize=7 &&
+       rm -f input &&
+       mkfifo input &&
+       {
+               {
+                       generate_tens_of_lines $tens_of_lines "$line" &&
+                       sleep 100
+               } >input &
+       } &&
+       test-line-buffer input <<-EOF >output &&
+       read $readsize
+       copy $copysize
+       EOF
+       kill $! &&
+       test_line_count = $lines output &&
+       tail -n 1 <output >actual &&
+       test_cmp expect actual
+}
+
+test_expect_success 'setup: have pipes?' '
+      rm -f frob &&
+      if mkfifo frob
+      then
+               test_set_prereq PIPE
+      fi
+'
+
+test_expect_success 'hello world' '
        echo HELLO >expect &&
        test-line-buffer <<-\EOF >actual &&
        read 6
@@ -13,6 +79,21 @@ test_expect_success 'read greeting' '
        test_cmp expect actual
 '
 
+test_expect_success PIPE '0-length read, no input available' '
+       >expect &&
+       rm -f input &&
+       mkfifo input &&
+       {
+               sleep 100 >input &
+       } &&
+       test-line-buffer input <<-\EOF >actual &&
+       read 0
+       copy 0
+       EOF
+       kill $! &&
+       test_cmp expect actual
+'
+
 test_expect_success '0-length read, send along greeting' '
        echo HELLO >expect &&
        test-line-buffer <<-\EOF >actual &&
@@ -23,6 +104,33 @@ test_expect_success '0-length read, send along greeting' '
        test_cmp expect actual
 '
 
+test_expect_success PIPE '1-byte read, no input available' '
+       printf "%s" ab >expect &&
+       rm -f input &&
+       mkfifo input &&
+       {
+               {
+                       printf "%s" a &&
+                       printf "%s" b &&
+                       sleep 100
+               } >input &
+       } &&
+       test-line-buffer input <<-\EOF >actual &&
+       read 1
+       copy 1
+       EOF
+       kill $! &&
+       test_cmp expect actual
+'
+
+test_expect_success PIPE 'long read (around 8192 bytes)' '
+       long_read_test 8192
+'
+
+test_expect_success PIPE,EXPENSIVE 'longer read (around 65536 bytes)' '
+       long_read_test 65536
+'
+
 test_expect_success 'buffer_read_string copes with null byte' '
        >expect &&
        q_to_nul <<-\EOF | test-line-buffer >actual &&
index da0bc6502cd609814035083f3bc4bde8a598629d..ec19b13ee23d0de5a2423941655452627d3d00cc 100644 (file)
@@ -49,15 +49,31 @@ static void handle_line(const char *line, struct line_buffer *stdin_buf)
 int main(int argc, char *argv[])
 {
        struct line_buffer stdin_buf = LINE_BUFFER_INIT;
+       struct line_buffer file_buf = LINE_BUFFER_INIT;
+       struct line_buffer *input = &stdin_buf;
+       const char *filename;
        char *s;
 
-       if (argc != 1)
-               usage("test-line-buffer < script");
+       if (argc == 1)
+               filename = NULL;
+       else if (argc == 2)
+               filename = argv[1];
+       else
+               usage("test-line-buffer [file] < script");
 
        if (buffer_init(&stdin_buf, NULL))
                die_errno("open error");
+       if (filename) {
+               if (buffer_init(&file_buf, filename))
+                       die_errno("error opening %s", filename);
+               input = &file_buf;
+       }
+
        while ((s = buffer_read_line(&stdin_buf)))
-               handle_line(s, &stdin_buf);
+               handle_line(s, input);
+
+       if (filename && buffer_deinit(&file_buf))
+               die("error reading from %s", filename);
        if (buffer_deinit(&stdin_buf))
                die("input error");
        if (ferror(stdout))