add copy_stream test from MRI
authorEric Wong <normalperson@yhbt.net>
Fri, 13 May 2011 00:25:08 +0000 (12 17:25 -0700)
committerEric Wong <normalperson@yhbt.net>
Fri, 13 May 2011 00:25:08 +0000 (12 17:25 -0700)
This helps flesh out some inconsistencies between our
implementation and IO.copy_stream in MRI.  Some things
we won't care about though, like userspace buffering.

test/test_copy_stream.rb [new file with mode: 0644]

diff --git a/test/test_copy_stream.rb b/test/test_copy_stream.rb
new file mode 100644 (file)
index 0000000..1858257
--- /dev/null
@@ -0,0 +1,341 @@
+require 'test/unit'
+require 'tmpdir'
+require "fcntl"
+require 'io/nonblock'
+require 'socket'
+require 'timeout'
+require 'tempfile'
+require 'io/splice'
+
+class TestIOCopyStreamCompat < Test::Unit::TestCase
+  def have_nonblock?
+    IO.method_defined?("nonblock=")
+  end
+
+  def pipe(wp, rp)
+    re, we = nil, nil
+    r, w = IO.pipe
+    rt = Thread.new do
+      begin
+        rp.call(r)
+      rescue Exception
+        r.close
+        re = $!
+      end
+    end
+    wt = Thread.new do
+      begin
+        wp.call(w)
+      rescue Exception
+        w.close
+        we = $!
+      end
+    end
+    flunk("timeout") unless wt.join(10) && rt.join(10)
+  ensure
+    w.close unless !w || w.closed?
+    r.close unless !r || r.closed?
+    (wt.kill; wt.join) if wt
+    (rt.kill; rt.join) if rt
+    raise we if we
+    raise re if re
+  end
+
+  def with_pipe
+    r, w = IO.pipe
+    begin
+      yield r, w
+    ensure
+      r.close unless r.closed?
+      w.close unless w.closed?
+    end
+  end
+
+  def with_read_pipe(content)
+    pipe(proc do |w|
+      w << content
+      w.close
+    end, proc do |r|
+      yield r
+    end)
+  end
+
+  def mkcdtmpdir
+    Dir.mktmpdir {|d|
+      Dir.chdir(d) {
+        yield
+      }
+    }
+  end
+
+  def trapping_usr1
+    @usr1_rcvd  = 0
+    trap(:USR1) { @usr1_rcvd += 1 }
+    yield
+    ensure
+      trap(:USR1, "DEFAULT")
+  end
+
+  def test_copy_stream
+    mkcdtmpdir {
+      content = "foobar"
+      File.open("src", "w") {|f| f << content }
+      ret = IO::Splice.copy_stream("src", "dst")
+      assert_equal(content.bytesize, ret)
+      assert_equal(content, File.read("dst"))
+
+      # overwrite by smaller file.
+      content = "baz"
+      File.open("src", "w") {|f| f << content }
+      ret = IO::Splice.copy_stream("src", "dst")
+      assert_equal(content.bytesize, ret)
+      assert_equal(content, File.read("dst"))
+
+      ret = IO::Splice.copy_stream("src", "dst", 2)
+      assert_equal(2, ret)
+      assert_equal(content[0,2], File.read("dst"))
+
+      ret = IO::Splice.copy_stream("src", "dst", 0)
+      assert_equal(0, ret)
+      assert_equal("", File.read("dst"))
+
+      ret = IO::Splice.copy_stream("src", "dst", nil, 1)
+      assert_equal(content.bytesize-1, ret)
+      assert_equal(content[1..-1], File.read("dst"))
+
+      assert_raise(Errno::ENOENT) {
+        IO::Splice.copy_stream("nodir/foo", "dst")
+      }
+
+      assert_raise(Errno::ENOENT) {
+        IO::Splice.copy_stream("src", "nodir/bar")
+      }
+
+      pipe(proc do |w|
+        ret = IO::Splice.copy_stream("src", w)
+        assert_equal(content.bytesize, ret)
+        w.close
+      end, proc do |r|
+        assert_equal(content, r.read)
+      end)
+
+      with_pipe {|r, w|
+        w.close
+        assert_raise(IOError) { IO::Splice.copy_stream("src", w) }
+      }
+
+      pipe_content = "abc"
+      with_read_pipe(pipe_content) {|r|
+        ret = IO::Splice.copy_stream(r, "dst")
+        assert_equal(pipe_content.bytesize, ret)
+        assert_equal(pipe_content, File.read("dst"))
+      }
+
+      pipe(proc do |w|
+        ret = IO::Splice.copy_stream("src", w, 1, 1)
+        assert_equal(1, ret)
+        w.close
+      end, proc do |r|
+        assert_equal(content[1,1], r.read)
+      end)
+
+      bigcontent = "abc" * 123456
+      File.open("bigsrc", "w") {|f| f << bigcontent }
+      ret = IO::Splice.copy_stream("bigsrc", "bigdst")
+      assert_equal(bigcontent.bytesize, ret)
+      assert_equal(bigcontent, File.read("bigdst"))
+
+      File.unlink("bigdst")
+      ret = IO::Splice.copy_stream("bigsrc", "bigdst", nil, 100)
+      assert_equal(bigcontent.bytesize-100, ret)
+      assert_equal(bigcontent[100..-1], File.read("bigdst"))
+
+      File.unlink("bigdst")
+      ret = IO::Splice.copy_stream("bigsrc", "bigdst", 30000, 100)
+      assert_equal(30000, ret)
+      assert_equal(bigcontent[100, 30000], File.read("bigdst"))
+
+      File.open("bigsrc") {|f|
+        begin
+          assert_equal(0, f.pos)
+          ret = IO::Splice.copy_stream(f, "bigdst", nil, 10)
+          assert_equal(bigcontent.bytesize-10, ret)
+          assert_equal(bigcontent[10..-1], File.read("bigdst"))
+          assert_equal(0, f.pos)
+          ret = IO::Splice.copy_stream(f, "bigdst", 40, 30)
+          assert_equal(40, ret)
+          assert_equal(bigcontent[30, 40], File.read("bigdst"))
+          assert_equal(0, f.pos)
+        rescue NotImplementedError
+          #skip "pread(2) is not implemtented."
+        end
+      }
+
+      with_pipe {|r, w|
+        w.close
+        assert_raise(IOError) { IO::Splice.copy_stream("src", w) }
+      }
+
+      megacontent = "abc" * 1234567
+      File.open("megasrc", "w") {|f| f << megacontent }
+
+      if have_nonblock?
+        with_pipe {|r1, w1|
+          with_pipe {|r2, w2|
+            begin
+              r1.nonblock = true
+              w2.nonblock = true
+            rescue Errno::EBADF
+              skip "nonblocking IO for pipe is not implemented"
+            end
+            t1 = Thread.new { w1 << megacontent; w1.close }
+            t2 = Thread.new { r2.read }
+            ret = IO::Splice.copy_stream(r1, w2)
+            assert_equal(megacontent.bytesize, ret)
+            w2.close
+            t1.join
+            assert_equal(megacontent, t2.value)
+          }
+        }
+      end
+
+      with_pipe {|r1, w1|
+        with_pipe {|r2, w2|
+          t1 = Thread.new { w1 << megacontent; w1.close }
+          t2 = Thread.new { r2.read }
+          ret = IO::Splice.copy_stream(r1, w2)
+          assert_equal(megacontent.bytesize, ret)
+          w2.close
+          t1.join
+          assert_equal(megacontent, t2.value)
+        }
+      }
+
+      with_pipe {|r, w|
+        t = Thread.new { r.read }
+        ret = IO::Splice.copy_stream("megasrc", w)
+        assert_equal(megacontent.bytesize, ret)
+        w.close
+        assert_equal(megacontent, t.value)
+      }
+    }
+  end
+
+  def with_socketpair
+    s1, s2 = UNIXSocket.pair
+    begin
+      yield s1, s2
+    ensure
+      s1.close unless s1.closed?
+      s2.close unless s2.closed?
+    end
+  end
+
+  def test_copy_stream_socket
+    return
+    mkcdtmpdir {
+
+      content = "foobar"
+      File.open("src", "w") {|f| f << content }
+
+      with_socketpair {|s1, s2|
+        ret = IO::Splice.copy_stream("src", s1)
+        assert_equal(content.bytesize, ret)
+        s1.close
+        assert_equal(content, s2.read)
+      }
+
+      bigcontent = "abc" * 123456
+      File.open("bigsrc", "w") {|f| f << bigcontent }
+
+      with_socketpair {|s1, s2|
+        t = Thread.new { s2.read }
+        ret = IO::Splice.copy_stream("bigsrc", s1)
+        assert_equal(bigcontent.bytesize, ret)
+        s1.close
+        result = t.value
+        assert_equal(bigcontent, result)
+      }
+
+      with_socketpair {|s1, s2|
+        t = Thread.new { s2.read }
+        ret = IO::Splice.copy_stream("bigsrc", s1, 10000)
+        assert_equal(10000, ret)
+        s1.close
+        result = t.value
+        assert_equal(bigcontent[0,10000], result)
+      }
+
+      File.open("bigsrc") {|f|
+        assert_equal(0, f.pos)
+        with_socketpair {|s1, s2|
+          t = Thread.new { s2.read }
+          ret = IO::Splice.copy_stream(f, s1, nil, 100)
+          assert_equal(bigcontent.bytesize-100, ret)
+          assert_equal(0, f.pos)
+          s1.close
+          result = t.value
+          assert_equal(bigcontent[100..-1], result)
+        }
+      }
+
+      File.open("bigsrc") {|f|
+        assert_equal(bigcontent[0,100], f.read(100))
+        assert_equal(100, f.pos)
+        with_socketpair {|s1, s2|
+          t = Thread.new { s2.read }
+          ret = IO::Splice.copy_stream(f, s1)
+          assert_equal(bigcontent.bytesize-100, ret)
+          assert_equal(bigcontent.length, f.pos)
+          s1.close
+          result = t.value
+          assert_equal(bigcontent[100..-1], result)
+        }
+      }
+
+      megacontent = "abc" * 1234567
+      File.open("megasrc", "w") {|f| f << megacontent }
+
+      if have_nonblock?
+        with_socketpair {|s1, s2|
+          begin
+            s1.nonblock = true
+          rescue Errno::EBADF
+            skip "nonblocking IO for pipe is not implemented"
+          end
+          t = Thread.new { s2.read }
+          ret = IO::Splice.copy_stream("megasrc", s1)
+          assert_equal(megacontent.bytesize, ret)
+          s1.close
+          result = t.value
+          assert_equal(megacontent, result)
+        }
+        with_socketpair {|s1, s2|
+          begin
+            s1.nonblock = true
+          rescue Errno::EBADF
+            skip "nonblocking IO for pipe is not implemented"
+          end
+          trapping_usr1 do
+            nr = 10
+            pid = fork do
+              s1.close
+              IO.select([s2])
+              Process.kill(:USR1, Process.ppid)
+              s2.read
+            end
+            s2.close
+            nr.times do
+              assert_equal megacontent.bytesize,
+                          IO::Splice.copy_stream("megasrc", s1)
+            end
+            assert_equal(1, @usr1_rcvd)
+            s1.close
+            _, status = Process.waitpid2(pid)
+            assert status.success?, status.inspect
+          end
+        }
+      end
+    }
+  end
+end