From ec812c7f2d83681b7beecdf21334e3a43c2b7090 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Thu, 12 May 2011 17:25:08 -0700 Subject: [PATCH] add copy_stream test from MRI 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 | 341 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 341 insertions(+) create mode 100644 test/test_copy_stream.rb diff --git a/test/test_copy_stream.rb b/test/test_copy_stream.rb new file mode 100644 index 0000000..1858257 --- /dev/null +++ b/test/test_copy_stream.rb @@ -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 -- 2.11.4.GIT