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 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.sysread(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.sysseek(0, IO::SEEK_CUR)) 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