# coding: US-ASCII # frozen_string_literal: false require 'test/unit' require 'tmpdir' require "fcntl" require 'io/nonblock' require 'pathname' require 'socket' require 'stringio' require 'timeout' require 'tempfile' require 'weakref' class TestIO < Test::Unit::TestCase module Feature def have_close_on_exec? $stdin.close_on_exec? true rescue NotImplementedError false end def have_nonblock? IO.method_defined?("nonblock=") end end include Feature extend Feature 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 r&.close (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 w.close 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_usr2 @usr2_rcvd = 0 r, w = IO.pipe trap(:USR2) do w.write([@usr2_rcvd += 1].pack('L')) end yield r ensure trap(:USR2, "DEFAULT") w&.close r&.close end def test_pipe r, w = IO.pipe assert_instance_of(IO, r) assert_instance_of(IO, w) [ Thread.start{ w.print "abc" w.close }, Thread.start{ assert_equal("abc", r.read) r.close } ].each{|thr| thr.join} end def test_binmode_pipe EnvUtil.with_default_internal(Encoding::UTF_8) do EnvUtil.with_default_external(Encoding::UTF_8) do begin reader0, writer0 = IO.pipe reader0.binmode writer0.binmode reader1, writer1 = IO.pipe reader2, writer2 = IO.pipe(binmode: true) assert_predicate writer0, :binmode? assert_predicate writer2, :binmode? assert_equal writer0.binmode?, writer2.binmode? assert_equal writer0.external_encoding, writer2.external_encoding assert_equal writer0.internal_encoding, writer2.internal_encoding assert_predicate reader0, :binmode? assert_predicate reader2, :binmode? assert_equal reader0.binmode?, reader2.binmode? assert_equal reader0.external_encoding, reader2.external_encoding assert_equal reader0.internal_encoding, reader2.internal_encoding reader3, writer3 = IO.pipe("UTF-8:UTF-8", binmode: true) assert_predicate writer3, :binmode? assert_equal writer1.external_encoding, writer3.external_encoding assert_equal writer1.internal_encoding, writer3.internal_encoding assert_predicate reader3, :binmode? assert_equal reader1.external_encoding, reader3.external_encoding assert_equal reader1.internal_encoding, reader3.internal_encoding reader4, writer4 = IO.pipe("UTF-8:UTF-8", binmode: true) assert_predicate writer4, :binmode? assert_equal writer1.external_encoding, writer4.external_encoding assert_equal writer1.internal_encoding, writer4.internal_encoding assert_predicate reader4, :binmode? assert_equal reader1.external_encoding, reader4.external_encoding assert_equal reader1.internal_encoding, reader4.internal_encoding reader5, writer5 = IO.pipe("UTF-8", "UTF-8", binmode: true) assert_predicate writer5, :binmode? assert_equal writer1.external_encoding, writer5.external_encoding assert_equal writer1.internal_encoding, writer5.internal_encoding assert_predicate reader5, :binmode? assert_equal reader1.external_encoding, reader5.external_encoding assert_equal reader1.internal_encoding, reader5.internal_encoding ensure [ reader0, writer0, reader1, writer1, reader2, writer2, reader3, writer3, reader4, writer4, reader5, writer5, ].compact.map(&:close) end end end end def test_pipe_block x = nil ret = IO.pipe {|r, w| x = [r,w] assert_instance_of(IO, r) assert_instance_of(IO, w) [ Thread.start do w.print "abc" w.close end, Thread.start do assert_equal("abc", r.read) end ].each{|thr| thr.join} assert_not_predicate(r, :closed?) assert_predicate(w, :closed?) :foooo } assert_equal(:foooo, ret) assert_predicate(x[0], :closed?) assert_predicate(x[1], :closed?) end def test_pipe_block_close 4.times {|i| x = nil IO.pipe {|r, w| x = [r,w] r.close if (i&1) == 0 w.close if (i&2) == 0 } assert_predicate(x[0], :closed?) assert_predicate(x[1], :closed?) } end def test_gets_rs rs = ":" pipe(proc do |w| w.print "aaa:bbb" w.close end, proc do |r| assert_equal "aaa:", r.gets(rs) assert_equal "bbb", r.gets(rs) assert_nil r.gets(rs) r.close end) end def test_gets_default_rs pipe(proc do |w| w.print "aaa\nbbb\n" w.close end, proc do |r| assert_equal "aaa\n", r.gets assert_equal "bbb\n", r.gets assert_nil r.gets r.close end) end def test_gets_rs_nil pipe(proc do |w| w.print "a\n\nb\n\n" w.close end, proc do |r| assert_equal "a\n\nb\n\n", r.gets(nil) assert_nil r.gets("") r.close end) end def test_gets_rs_377 pipe(proc do |w| w.print "\377xyz" w.close end, proc do |r| r.binmode assert_equal("\377", r.gets("\377"), "[ruby-dev:24460]") r.close end) end def test_gets_paragraph pipe(proc do |w| w.print "a\n\nb\n\n" w.close end, proc do |r| assert_equal "a\n\n", r.gets(""), "[ruby-core:03771]" assert_equal "b\n\n", r.gets("") assert_nil r.gets("") r.close end) end def test_gets_chomp_rs rs = ":" pipe(proc do |w| w.print "aaa:bbb" w.close end, proc do |r| assert_equal "aaa", r.gets(rs, chomp: true) assert_equal "bbb", r.gets(rs, chomp: true) assert_nil r.gets(rs, chomp: true) r.close end) end def test_gets_chomp_default_rs pipe(proc do |w| w.print "aaa\r\nbbb\nccc" w.close end, proc do |r| assert_equal "aaa", r.gets(chomp: true) assert_equal "bbb", r.gets(chomp: true) assert_equal "ccc", r.gets(chomp: true) assert_nil r.gets r.close end) (0..3).each do |i| pipe(proc do |w| w.write("a" * ((4096 << i) - 4), "\r\n" "a\r\n") w.close end, proc do |r| r.gets assert_equal "a", r.gets(chomp: true) assert_nil r.gets r.close end) end end def test_gets_chomp_rs_nil pipe(proc do |w| w.print "a\n\nb\n\n" w.close end, proc do |r| assert_equal "a\n\nb\n", r.gets(nil, chomp: true) assert_nil r.gets("") r.close end) end def test_gets_chomp_paragraph pipe(proc do |w| w.print "a\n\nb\n\n" w.close end, proc do |r| assert_equal "a", r.gets("", chomp: true) assert_equal "b", r.gets("", chomp: true) assert_nil r.gets("", chomp: true) r.close end) end def test_gets_limit_extra_arg pipe(proc do |w| w << "0123456789\n0123456789" w.close end, proc do |r| assert_equal("0123456789\n0", r.gets(nil, 12)) assert_raise(TypeError) { r.gets(3,nil) } end) end # This test cause SEGV. def test_ungetc pipe(proc do |w| w.close end, proc do |r| s = "a" * 1000 assert_raise(IOError, "[ruby-dev:31650]") { 200.times { r.ungetc s } } end) end def test_ungetbyte make_tempfile {|t| t.open t.binmode t.ungetbyte(0x41) assert_equal(-1, t.pos) assert_equal(0x41, t.getbyte) t.rewind assert_equal(0, t.pos) t.ungetbyte("qux") assert_equal(-3, t.pos) assert_equal("quxfoo\n", t.gets) assert_equal(4, t.pos) t.set_encoding("utf-8") t.ungetbyte(0x89) t.ungetbyte(0x8e) t.ungetbyte("\xe7") t.ungetbyte("\xe7\xb4\x85") assert_equal(-2, t.pos) assert_equal("\u7d05\u7389bar\n", t.gets) } end def test_each_byte pipe(proc do |w| w << "abc def" w.close end, proc do |r| r.each_byte {|byte| break if byte == 32 } assert_equal("def", r.read, "[ruby-dev:31659]") end) end def test_each_byte_with_seek make_tempfile {|t| bug5119 = '[ruby-core:38609]' i = 0 open(t.path) do |f| f.each_byte {i = f.pos} end assert_equal(12, i, bug5119) } end def test_each_codepoint make_tempfile {|t| bug2959 = '[ruby-core:28650]' a = "" File.open(t, 'rt') {|f| f.each_codepoint {|c| a << c} } assert_equal("foo\nbar\nbaz\n", a, bug2959) } end def test_codepoints make_tempfile {|t| bug2959 = '[ruby-core:28650]' a = "" File.open(t, 'rt') {|f| assert_warn(/deprecated/) { f.codepoints {|c| a << c} } } assert_equal("foo\nbar\nbaz\n", a, bug2959) } end def test_rubydev33072 t = make_tempfile path = t.path t.close! assert_raise(Errno::ENOENT, "[ruby-dev:33072]") do File.read(path, nil, nil, **{}) end end def with_srccontent(content = "baz") src = "src" mkcdtmpdir { File.open(src, "w") {|f| f << content } yield src, content } end def test_copy_stream_small with_srccontent("foobar") {|src, content| ret = IO.copy_stream(src, "dst") assert_equal(content.bytesize, ret) assert_equal(content, File.read("dst")) } end def test_copy_stream_append with_srccontent("foobar") {|src, content| File.open('dst', 'ab') do |dst| ret = IO.copy_stream(src, dst) assert_equal(content.bytesize, ret) assert_equal(content, File.read("dst")) end } end def test_copy_stream_smaller with_srccontent {|src, content| # overwrite by smaller file. dst = "dst" File.open(dst, "w") {|f| f << "foobar"} ret = IO.copy_stream(src, dst) assert_equal(content.bytesize, ret) assert_equal(content, File.read(dst)) ret = IO.copy_stream(src, dst, 2) assert_equal(2, ret) assert_equal(content[0,2], File.read(dst)) ret = IO.copy_stream(src, dst, 0) assert_equal(0, ret) assert_equal("", File.read(dst)) ret = IO.copy_stream(src, dst, nil, 1) assert_equal(content.bytesize-1, ret) assert_equal(content[1..-1], File.read(dst)) } end def test_copy_stream_noent with_srccontent {|src, content| assert_raise(Errno::ENOENT) { IO.copy_stream("nodir/foo", "dst") } assert_raise(Errno::ENOENT) { IO.copy_stream(src, "nodir/bar") } } end def test_copy_stream_pipe with_srccontent {|src, content| pipe(proc do |w| ret = IO.copy_stream(src, w) assert_equal(content.bytesize, ret) w.close end, proc do |r| assert_equal(content, r.read) end) } end def test_copy_stream_write_pipe with_srccontent {|src, content| with_pipe {|r, w| w.close assert_raise(IOError) { IO.copy_stream(src, w) } } } end def with_pipecontent mkcdtmpdir { yield "abc" } end def test_copy_stream_pipe_to_file with_pipecontent {|pipe_content| dst = "dst" with_read_pipe(pipe_content) {|r| ret = IO.copy_stream(r, dst) assert_equal(pipe_content.bytesize, ret) assert_equal(pipe_content, File.read(dst)) } } end def test_copy_stream_read_pipe with_pipecontent {|pipe_content| with_read_pipe(pipe_content) {|r1| assert_equal("a", r1.getc) pipe(proc do |w2| w2.sync = false w2 << "def" ret = IO.copy_stream(r1, w2) assert_equal(2, ret) w2.close end, proc do |r2| assert_equal("defbc", r2.read) end) } with_read_pipe(pipe_content) {|r1| assert_equal("a", r1.getc) pipe(proc do |w2| w2.sync = false w2 << "def" ret = IO.copy_stream(r1, w2, 1) assert_equal(1, ret) w2.close end, proc do |r2| assert_equal("defb", r2.read) end) } with_read_pipe(pipe_content) {|r1| assert_equal("a", r1.getc) pipe(proc do |w2| ret = IO.copy_stream(r1, w2) assert_equal(2, ret) w2.close end, proc do |r2| assert_equal("bc", r2.read) end) } with_read_pipe(pipe_content) {|r1| assert_equal("a", r1.getc) pipe(proc do |w2| ret = IO.copy_stream(r1, w2, 1) assert_equal(1, ret) w2.close end, proc do |r2| assert_equal("b", r2.read) end) } with_read_pipe(pipe_content) {|r1| assert_equal("a", r1.getc) pipe(proc do |w2| ret = IO.copy_stream(r1, w2, 0) assert_equal(0, ret) w2.close end, proc do |r2| assert_equal("", r2.read) end) } pipe(proc do |w1| w1 << "abc" w1 << "def" w1.close end, proc do |r1| assert_equal("a", r1.getc) pipe(proc do |w2| ret = IO.copy_stream(r1, w2) assert_equal(5, ret) w2.close end, proc do |r2| assert_equal("bcdef", r2.read) end) end) } end def test_copy_stream_file_to_pipe with_srccontent {|src, content| pipe(proc do |w| ret = IO.copy_stream(src, w, 1, 1) assert_equal(1, ret) w.close end, proc do |r| assert_equal(content[1,1], r.read) end) } end if have_nonblock? def test_copy_stream_no_busy_wait skip "MJIT has busy wait on GC. This sometimes fails with --jit." if RubyVM::MJIT.enabled? skip "multiple threads already active" if Thread.list.size > 1 msg = 'r58534 [ruby-core:80969] [Backport #13533]' IO.pipe do |r,w| r.nonblock = true assert_cpu_usage_low(msg, stop: ->{w.close}) do IO.copy_stream(r, IO::NULL) end end end def test_copy_stream_pipe_nonblock mkcdtmpdir { with_read_pipe("abc") {|r1| assert_equal("a", r1.getc) with_pipe {|r2, w2| begin w2.nonblock = true rescue Errno::EBADF skip "nonblocking IO for pipe is not implemented" end s = w2.syswrite("a" * 100000) t = Thread.new { sleep 0.1; r2.read } ret = IO.copy_stream(r1, w2) w2.close assert_equal(2, ret) assert_equal("a" * s + "bc", t.value) } } } end end def with_bigcontent yield "abc" * 123456 end def with_bigsrc mkcdtmpdir { with_bigcontent {|bigcontent| bigsrc = "bigsrc" File.open("bigsrc", "w") {|f| f << bigcontent } yield bigsrc, bigcontent } } end def test_copy_stream_bigcontent with_bigsrc {|bigsrc, bigcontent| ret = IO.copy_stream(bigsrc, "bigdst") assert_equal(bigcontent.bytesize, ret) assert_equal(bigcontent, File.read("bigdst")) } end def test_copy_stream_bigcontent_chop with_bigsrc {|bigsrc, bigcontent| ret = IO.copy_stream(bigsrc, "bigdst", nil, 100) assert_equal(bigcontent.bytesize-100, ret) assert_equal(bigcontent[100..-1], File.read("bigdst")) } end def test_copy_stream_bigcontent_mid with_bigsrc {|bigsrc, bigcontent| ret = IO.copy_stream(bigsrc, "bigdst", 30000, 100) assert_equal(30000, ret) assert_equal(bigcontent[100, 30000], File.read("bigdst")) } end def test_copy_stream_bigcontent_fpos with_bigsrc {|bigsrc, bigcontent| File.open(bigsrc) {|f| begin assert_equal(0, f.pos) ret = IO.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.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 implemented." end } } end def test_copy_stream_closed_pipe with_srccontent {|src,| with_pipe {|r, w| w.close assert_raise(IOError) { IO.copy_stream(src, w) } } } end def with_megacontent yield "abc" * 1234567 end def with_megasrc mkcdtmpdir { with_megacontent {|megacontent| megasrc = "megasrc" File.open(megasrc, "w") {|f| f << megacontent } yield megasrc, megacontent } } end if have_nonblock? def test_copy_stream_megacontent_nonblock with_megacontent {|megacontent| 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 } t3 = Thread.new { ret = IO.copy_stream(r1, w2) assert_equal(megacontent.bytesize, ret) w2.close } _, t2_value, _ = assert_join_threads([t1, t2, t3]) assert_equal(megacontent, t2_value) } } } end end def test_copy_stream_megacontent_pipe_to_file with_megasrc {|megasrc, megacontent| with_pipe {|r1, w1| with_pipe {|r2, w2| t1 = Thread.new { w1 << megacontent; w1.close } t2 = Thread.new { r2.read } t3 = Thread.new { ret = IO.copy_stream(r1, w2) assert_equal(megacontent.bytesize, ret) w2.close } _, t2_value, _ = assert_join_threads([t1, t2, t3]) assert_equal(megacontent, t2_value) } } } end def test_copy_stream_megacontent_file_to_pipe with_megasrc {|megasrc, megacontent| with_pipe {|r, w| t1 = Thread.new { r.read } t2 = Thread.new { ret = IO.copy_stream(megasrc, w) assert_equal(megacontent.bytesize, ret) w.close } t1_value, _ = assert_join_threads([t1, t2]) assert_equal(megacontent, t1_value) } } end def test_copy_stream_rbuf mkcdtmpdir { begin pipe(proc do |w| File.open("foo", "w") {|f| f << "abcd" } File.open("foo") {|f| f.read(1) assert_equal(3, IO.copy_stream(f, w, 10, 1)) } w.close end, proc do |r| assert_equal("bcd", r.read) end) rescue NotImplementedError skip "pread(2) is not implemtented." end } 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_socket1 with_srccontent("foobar") {|src, content| with_socketpair {|s1, s2| ret = IO.copy_stream(src, s1) assert_equal(content.bytesize, ret) s1.close assert_equal(content, s2.read) } } end if defined? UNIXSocket def test_copy_stream_socket2 with_bigsrc {|bigsrc, bigcontent| with_socketpair {|s1, s2| t1 = Thread.new { s2.read } t2 = Thread.new { ret = IO.copy_stream(bigsrc, s1) assert_equal(bigcontent.bytesize, ret) s1.close } result, _ = assert_join_threads([t1, t2]) assert_equal(bigcontent, result) } } end if defined? UNIXSocket def test_copy_stream_socket3 with_bigsrc {|bigsrc, bigcontent| with_socketpair {|s1, s2| t1 = Thread.new { s2.read } t2 = Thread.new { ret = IO.copy_stream(bigsrc, s1, 10000) assert_equal(10000, ret) s1.close } result, _ = assert_join_threads([t1, t2]) assert_equal(bigcontent[0,10000], result) } } end if defined? UNIXSocket def test_copy_stream_socket4 with_bigsrc {|bigsrc, bigcontent| File.open(bigsrc) {|f| assert_equal(0, f.pos) with_socketpair {|s1, s2| t1 = Thread.new { s2.read } t2 = Thread.new { ret = IO.copy_stream(f, s1, nil, 100) assert_equal(bigcontent.bytesize-100, ret) assert_equal(0, f.pos) s1.close } result, _ = assert_join_threads([t1, t2]) assert_equal(bigcontent[100..-1], result) } } } end if defined? UNIXSocket def test_copy_stream_socket5 with_bigsrc {|bigsrc, bigcontent| File.open(bigsrc) {|f| assert_equal(bigcontent[0,100], f.read(100)) assert_equal(100, f.pos) with_socketpair {|s1, s2| t1 = Thread.new { s2.read } t2 = Thread.new { ret = IO.copy_stream(f, s1) assert_equal(bigcontent.bytesize-100, ret) assert_equal(bigcontent.length, f.pos) s1.close } result, _ = assert_join_threads([t1, t2]) assert_equal(bigcontent[100..-1], result) } } } end if defined? UNIXSocket def test_copy_stream_socket6 mkcdtmpdir { megacontent = "abc" * 1234567 File.open("megasrc", "w") {|f| f << megacontent } with_socketpair {|s1, s2| begin s1.nonblock = true rescue Errno::EBADF skip "nonblocking IO for pipe is not implemented" end t1 = Thread.new { s2.read } t2 = Thread.new { ret = IO.copy_stream("megasrc", s1) assert_equal(megacontent.bytesize, ret) s1.close } result, _ = assert_join_threads([t1, t2]) assert_equal(megacontent, result) } } end if defined? UNIXSocket def test_copy_stream_socket7 GC.start mkcdtmpdir { megacontent = "abc" * 1234567 File.open("megasrc", "w") {|f| f << megacontent } with_socketpair {|s1, s2| begin s1.nonblock = true rescue Errno::EBADF skip "nonblocking IO for pipe is not implemented" end trapping_usr2 do |rd| nr = 30 begin pid = fork do s1.close IO.select([s2]) Process.kill(:USR2, Process.ppid) buf = String.new(capacity: 16384) nil while s2.read(16384, buf) end s2.close nr.times do assert_equal megacontent.bytesize, IO.copy_stream("megasrc", s1) end assert_equal(1, rd.read(4).unpack1('L')) ensure s1.close _, status = Process.waitpid2(pid) if pid end assert_predicate(status, :success?) end } } end if defined? UNIXSocket and IO.method_defined?("nonblock=") def test_copy_stream_strio src = StringIO.new("abcd") dst = StringIO.new ret = IO.copy_stream(src, dst) assert_equal(4, ret) assert_equal("abcd", dst.string) assert_equal(4, src.pos) end def test_copy_stream_strio_len src = StringIO.new("abcd") dst = StringIO.new ret = IO.copy_stream(src, dst, 3) assert_equal(3, ret) assert_equal("abc", dst.string) assert_equal(3, src.pos) end def test_copy_stream_strio_off src = StringIO.new("abcd") with_pipe {|r, w| assert_raise(ArgumentError) { IO.copy_stream(src, w, 3, 1) } } end def test_copy_stream_fname_to_strio mkcdtmpdir { File.open("foo", "w") {|f| f << "abcd" } src = "foo" dst = StringIO.new ret = IO.copy_stream(src, dst, 3) assert_equal(3, ret) assert_equal("abc", dst.string) } end def test_copy_stream_strio_to_fname mkcdtmpdir { # StringIO to filename src = StringIO.new("abcd") ret = IO.copy_stream(src, "fooo", 3) assert_equal(3, ret) assert_equal("abc", File.read("fooo")) assert_equal(3, src.pos) } end def test_copy_stream_io_to_strio mkcdtmpdir { # IO to StringIO File.open("bar", "w") {|f| f << "abcd" } File.open("bar") {|src| dst = StringIO.new ret = IO.copy_stream(src, dst, 3) assert_equal(3, ret) assert_equal("abc", dst.string) assert_equal(3, src.pos) } } end def test_copy_stream_strio_to_io mkcdtmpdir { # StringIO to IO src = StringIO.new("abcd") ret = File.open("baz", "w") {|dst| IO.copy_stream(src, dst, 3) } assert_equal(3, ret) assert_equal("abc", File.read("baz")) assert_equal(3, src.pos) } end def test_copy_stream_strio_to_tempfile bug11015 = '[ruby-core:68676] [Bug #11015]' # StringIO to Tempfile src = StringIO.new("abcd") dst = Tempfile.new("baz") ret = IO.copy_stream(src, dst) assert_equal(4, ret) pos = dst.pos dst.rewind assert_equal("abcd", dst.read) assert_equal(4, pos, bug11015) ensure dst.close! end def test_copy_stream_pathname_to_pathname bug11199 = '[ruby-dev:49008] [Bug #11199]' mkcdtmpdir { File.open("src", "w") {|f| f << "ok" } src = Pathname.new("src") dst = Pathname.new("dst") IO.copy_stream(src, dst) assert_equal("ok", IO.read("dst"), bug11199) } end def test_copy_stream_write_in_binmode bug8767 = '[ruby-core:56518] [Bug #8767]' mkcdtmpdir { EnvUtil.with_default_internal(Encoding::UTF_8) do # StringIO to object with to_path bytes = "\xDE\xAD\xBE\xEF".force_encoding(Encoding::ASCII_8BIT) src = StringIO.new(bytes) dst = Object.new def dst.to_path "qux" end assert_nothing_raised(bug8767) { IO.copy_stream(src, dst) } assert_equal(bytes, File.binread("qux"), bug8767) assert_equal(4, src.pos, bug8767) end } end def test_copy_stream_read_in_binmode bug8767 = '[ruby-core:56518] [Bug #8767]' mkcdtmpdir { EnvUtil.with_default_internal(Encoding::UTF_8) do # StringIO to object with to_path bytes = "\xDE\xAD\xBE\xEF".force_encoding(Encoding::ASCII_8BIT) File.binwrite("qux", bytes) dst = StringIO.new src = Object.new def src.to_path "qux" end assert_nothing_raised(bug8767) { IO.copy_stream(src, dst) } assert_equal(bytes, dst.string.b, bug8767) assert_equal(4, dst.pos, bug8767) end } end class Rot13IO def initialize(io) @io = io end def readpartial(*args) ret = @io.readpartial(*args) ret.tr!('a-zA-Z', 'n-za-mN-ZA-M') ret end def write(str) @io.write(str.tr('a-zA-Z', 'n-za-mN-ZA-M')) end def to_io @io end end def test_copy_stream_io_to_rot13 mkcdtmpdir { File.open("bar", "w") {|f| f << "vex" } File.open("bar") {|src| File.open("baz", "w") {|dst0| dst = Rot13IO.new(dst0) ret = IO.copy_stream(src, dst, 3) assert_equal(3, ret) } assert_equal("irk", File.read("baz")) } } end def test_copy_stream_rot13_to_io mkcdtmpdir { File.open("bar", "w") {|f| f << "flap" } File.open("bar") {|src0| src = Rot13IO.new(src0) File.open("baz", "w") {|dst| ret = IO.copy_stream(src, dst, 4) assert_equal(4, ret) } } assert_equal("sync", File.read("baz")) } end def test_copy_stream_rot13_to_rot13 mkcdtmpdir { File.open("bar", "w") {|f| f << "bin" } File.open("bar") {|src0| src = Rot13IO.new(src0) File.open("baz", "w") {|dst0| dst = Rot13IO.new(dst0) ret = IO.copy_stream(src, dst, 3) assert_equal(3, ret) } } assert_equal("bin", File.read("baz")) } end def test_copy_stream_strio_flush with_pipe {|r, w| w.sync = false w.write "zz" src = StringIO.new("abcd") IO.copy_stream(src, w) t1 = Thread.new { w.close } t2 = Thread.new { r.read } _, result = assert_join_threads([t1, t2]) assert_equal("zzabcd", result) } end def test_copy_stream_strio_rbuf pipe(proc do |w| w << "abcd" w.close end, proc do |r| assert_equal("a", r.read(1)) sio = StringIO.new IO.copy_stream(r, sio) assert_equal("bcd", sio.string) end) end def test_copy_stream_src_wbuf mkcdtmpdir { pipe(proc do |w| File.open("foe", "w+") {|f| f.write "abcd\n" f.rewind f.write "xy" IO.copy_stream(f, w) } assert_equal("xycd\n", File.read("foe")) w.close end, proc do |r| assert_equal("cd\n", r.read) r.close end) } end class Bug5237 attr_reader :count def initialize @count = 0 end def read(bytes, buffer) @count += 1 buffer.replace "this is a test" nil end end def test_copy_stream_broken_src_read_eof src = Bug5237.new dst = StringIO.new assert_equal 0, src.count th = Thread.new { IO.copy_stream(src, dst) } flunk("timeout") unless th.join(10) assert_equal 1, src.count end def test_copy_stream_dst_rbuf mkcdtmpdir { pipe(proc do |w| w << "xyz" w.close end, proc do |r| File.open("fom", "w+b") {|f| f.write "abcd\n" f.rewind assert_equal("abc", f.read(3)) f.ungetc "c" IO.copy_stream(r, f) } assert_equal("abxyz", File.read("fom")) end) } end def test_copy_stream_to_duplex_io result = IO.pipe {|a,w| th = Thread.start {w.puts "yes"; w.close} IO.popen([EnvUtil.rubybin, '-pe$_="#$.:#$_"'], "r+") {|b| IO.copy_stream(a, b) b.close_write assert_join_threads([th]) b.read } } assert_equal("1:yes\n", result) end def ruby(*args) args = ['-e', '$>.write($<.read)'] if args.empty? ruby = EnvUtil.rubybin opts = {} if defined?(Process::RLIMIT_NPROC) lim = Process.getrlimit(Process::RLIMIT_NPROC)[1] opts[:rlimit_nproc] = [lim, 2048].min end f = IO.popen([ruby] + args, 'r+', opts) pid = f.pid yield(f) ensure f.close unless !f || f.closed? begin Process.wait(pid) rescue Errno::ECHILD, Errno::ESRCH end end def test_try_convert assert_equal(STDOUT, IO.try_convert(STDOUT)) assert_equal(nil, IO.try_convert("STDOUT")) end def test_ungetc2 f = false pipe(proc do |w| Thread.pass until f w.write("1" * 10000) w.close end, proc do |r| r.ungetc("0" * 10000) f = true assert_equal("0" * 10000 + "1" * 10000, r.read) end) end def test_write_with_multiple_arguments pipe(proc do |w| w.write("foo", "bar") w.close end, proc do |r| assert_equal("foobar", r.read) end) end def test_write_with_multiple_arguments_and_buffer mkcdtmpdir do line = "x"*9+"\n" file = "test.out" open(file, "wb") do |w| w.write(line) assert_equal(11, w.write(line, "\n")) end open(file, "rb") do |r| assert_equal([line, line, "\n"], r.readlines) end line = "x"*99+"\n" open(file, "wb") do |w| w.write(line*81) # 8100 bytes assert_equal(100, w.write("a"*99, "\n")) end open(file, "rb") do |r| 81.times {assert_equal(line, r.gets)} assert_equal("a"*99+"\n", r.gets) end end end def test_write_with_many_arguments [1023, 1024].each do |n| pipe(proc do |w| w.write(*(["a"] * n)) w.close end, proc do |r| assert_equal("a" * n, r.read) end) end end def test_write_with_multiple_nonstring_arguments assert_in_out_err([], "STDOUT.write(:foo, :bar)", ["foobar"]) end def test_write_buffered_with_multiple_arguments out, err, (_, status) = EnvUtil.invoke_ruby(["-e", "sleep 0.1;puts 'foo'"], "", true, true) do |_, o, e, i| [o.read, e.read, Process.waitpid2(i)] end assert_predicate(status, :success?) assert_equal("foo\n", out) assert_empty(err) end def test_write_no_args IO.pipe do |r, w| assert_equal 0, w.write, '[ruby-core:86285] [Bug #14338]' assert_equal :wait_readable, r.read_nonblock(1, exception: false) end end def test_write_non_writable with_pipe do |r, w| assert_raise(IOError) do r.write "foobarbaz" end end end def test_dup ruby do |f| begin f2 = f.dup f.puts "foo" f2.puts "bar" f.close_write f2.close_write assert_equal("foo\nbar\n", f.read) assert_equal("", f2.read) ensure f2.close end end end def test_dup_many opts = {} opts[:rlimit_nofile] = 1024 if defined?(Process::RLIMIT_NOFILE) assert_separately([], <<-'End', **opts) a = [] assert_raise(Errno::EMFILE, Errno::ENFILE, Errno::ENOMEM) do loop {a << IO.pipe} end assert_raise(Errno::EMFILE, Errno::ENFILE, Errno::ENOMEM) do loop {a << [a[-1][0].dup, a[-1][1].dup]} end End end def test_inspect with_pipe do |r, w| assert_match(/^#$/, r.inspect) r.freeze assert_match(/^#$/, r.inspect) end end def test_readpartial pipe(proc do |w| w.write "foobarbaz" w.close end, proc do |r| assert_raise(ArgumentError) { r.readpartial(-1) } assert_equal("fooba", r.readpartial(5)) r.readpartial(5, s = "") assert_equal("rbaz", s) end) end def test_readpartial_lock with_pipe do |r, w| s = "" t = Thread.new { r.readpartial(5, s) } Thread.pass until t.stop? assert_raise(RuntimeError) { s.clear } w.write "foobarbaz" w.close assert_equal("fooba", t.value) end end def test_readpartial_pos mkcdtmpdir { open("foo", "w") {|f| f << "abc" } open("foo") {|f| f.seek(0) assert_equal("ab", f.readpartial(2)) assert_equal(2, f.pos) } } end def test_readpartial_with_not_empty_buffer pipe(proc do |w| w.write "foob" w.close end, proc do |r| r.readpartial(5, s = "01234567") assert_equal("foob", s) end) end def test_readpartial_buffer_error with_pipe do |r, w| s = "" t = Thread.new { r.readpartial(5, s) } Thread.pass until t.stop? t.kill t.value assert_equal("", s) end end if /cygwin/ !~ RUBY_PLATFORM def test_read pipe(proc do |w| w.write "foobarbaz" w.close end, proc do |r| assert_raise(ArgumentError) { r.read(-1) } assert_equal("fooba", r.read(5)) r.read(nil, s = "") assert_equal("rbaz", s) end) end def test_read_lock with_pipe do |r, w| s = "" t = Thread.new { r.read(5, s) } Thread.pass until t.stop? assert_raise(RuntimeError) { s.clear } w.write "foobarbaz" w.close assert_equal("fooba", t.value) end end def test_read_with_not_empty_buffer pipe(proc do |w| w.write "foob" w.close end, proc do |r| r.read(nil, s = "01234567") assert_equal("foob", s) end) end def test_read_buffer_error with_pipe do |r, w| s = "" t = Thread.new { r.read(5, s) } Thread.pass until t.stop? t.kill t.value assert_equal("", s) end with_pipe do |r, w| s = "xxx" t = Thread.new {r.read(2, s)} Thread.pass until t.stop? t.kill t.value assert_equal("xxx", s) end end if /cygwin/ !~ RUBY_PLATFORM def test_write_nonblock pipe(proc do |w| w.write_nonblock(1) w.close end, proc do |r| assert_equal("1", r.read) end) end def test_read_nonblock_with_not_empty_buffer with_pipe {|r, w| w.write "foob" w.close r.read_nonblock(5, s = "01234567") assert_equal("foob", s) } end def test_write_nonblock_simple_no_exceptions pipe(proc do |w| w.write_nonblock('1', exception: false) w.close end, proc do |r| assert_equal("1", r.read) end) end def test_read_nonblock_error with_pipe {|r, w| begin r.read_nonblock 4096 rescue Errno::EWOULDBLOCK assert_kind_of(IO::WaitReadable, $!) end } with_pipe {|r, w| begin r.read_nonblock 4096, "" rescue Errno::EWOULDBLOCK assert_kind_of(IO::WaitReadable, $!) end } end if have_nonblock? def test_read_nonblock_invalid_exception with_pipe {|r, w| assert_raise(ArgumentError) {r.read_nonblock(4096, exception: 1)} } end if have_nonblock? def test_read_nonblock_no_exceptions skip '[ruby-core:90895] MJIT worker may leave fd open in a forked child' if RubyVM::MJIT.enabled? # TODO: consider acquiring GVL from MJIT worker. with_pipe {|r, w| assert_equal :wait_readable, r.read_nonblock(4096, exception: false) w.puts "HI!" assert_equal "HI!\n", r.read_nonblock(4096, exception: false) w.close assert_equal nil, r.read_nonblock(4096, exception: false) } end if have_nonblock? def test_read_nonblock_with_buffer_no_exceptions with_pipe {|r, w| assert_equal :wait_readable, r.read_nonblock(4096, "", exception: false) w.puts "HI!" buf = "buf" value = r.read_nonblock(4096, buf, exception: false) assert_equal value, "HI!\n" assert_same(buf, value) w.close assert_equal nil, r.read_nonblock(4096, "", exception: false) } end if have_nonblock? def test_write_nonblock_error with_pipe {|r, w| begin loop { w.write_nonblock "a"*100000 } rescue Errno::EWOULDBLOCK assert_kind_of(IO::WaitWritable, $!) end } end if have_nonblock? def test_write_nonblock_invalid_exception with_pipe {|r, w| assert_raise(ArgumentError) {w.write_nonblock(4096, exception: 1)} } end if have_nonblock? def test_write_nonblock_no_exceptions with_pipe {|r, w| loop { ret = w.write_nonblock("a"*100000, exception: false) if ret.is_a?(Symbol) assert_equal :wait_writable, ret break end } } end if have_nonblock? def test_gets pipe(proc do |w| w.write "foobarbaz" w.close end, proc do |r| assert_equal("", r.gets(0)) assert_equal("foobarbaz", r.gets(9)) end) end def test_close_read ruby do |f| f.close_read f.write "foobarbaz" assert_raise(IOError) { f.read } assert_nothing_raised(IOError) {f.close_read} assert_nothing_raised(IOError) {f.close} assert_nothing_raised(IOError) {f.close_read} end end def test_close_read_pipe with_pipe do |r, w| r.close_read assert_raise(Errno::EPIPE) { w.write "foobarbaz" } assert_nothing_raised(IOError) {r.close_read} assert_nothing_raised(IOError) {r.close} assert_nothing_raised(IOError) {r.close_read} end end def test_write_epipe_nosync assert_separately([], <<-"end;") r, w = IO.pipe r.close w.sync = false assert_raise(Errno::EPIPE) { loop { w.write "a" } } end; end def test_close_read_non_readable with_pipe do |r, w| assert_raise(IOError) do w.close_read end end end def test_close_write ruby do |f| f.write "foobarbaz" f.close_write assert_equal("foobarbaz", f.read) assert_nothing_raised(IOError) {f.close_write} assert_nothing_raised(IOError) {f.close} assert_nothing_raised(IOError) {f.close_write} end end def test_close_write_non_readable with_pipe do |r, w| assert_raise(IOError) do r.close_write end end end def test_close_read_write_separately bug = '[ruby-list:49598]' (1..10).each do |i| assert_nothing_raised(IOError, "#{bug} trying ##{i}") do IO.popen(EnvUtil.rubybin, "r+") {|f| th = Thread.new {f.close_write} f.close_read th.join } end end end def test_pid IO.pipe {|r, w| assert_equal(nil, r.pid) assert_equal(nil, w.pid) } begin pipe = IO.popen(EnvUtil.rubybin, "r+") pid1 = pipe.pid pipe.puts "p $$" pipe.close_write pid2 = pipe.read.chomp.to_i assert_equal(pid2, pid1) assert_equal(pid2, pipe.pid) ensure pipe.close end assert_raise(IOError) { pipe.pid } end def test_pid_after_close_read pid1 = pid2 = nil IO.popen("exit ;", "r+") do |io| pid1 = io.pid io.close_read pid2 = io.pid end assert_not_nil(pid1) assert_equal(pid1, pid2) end def make_tempfile t = Tempfile.new("test_io") t.binmode t.puts "foo" t.puts "bar" t.puts "baz" t.close if block_given? begin yield t ensure t.close(true) end else t end end def test_set_lineno make_tempfile {|t| assert_separately(["-", t.path], <<-SRC) open(ARGV[0]) do |f| assert_equal(0, $.) f.gets; assert_equal(1, $.) f.gets; assert_equal(2, $.) f.lineno = 1000; assert_equal(2, $.) f.gets; assert_equal(1001, $.) f.gets; assert_equal(1001, $.) f.rewind; assert_equal(1001, $.) f.gets; assert_equal(1, $.) f.gets; assert_equal(2, $.) f.gets; assert_equal(3, $.) f.gets; assert_equal(3, $.) end SRC } end def test_set_lineno_gets pipe(proc do |w| w.puts "foo" w.puts "bar" w.puts "baz" w.close end, proc do |r| r.gets; assert_equal(1, $.) r.gets; assert_equal(2, $.) r.lineno = 1000; assert_equal(2, $.) r.gets; assert_equal(1001, $.) r.gets; assert_equal(1001, $.) end) end def test_set_lineno_readline pipe(proc do |w| w.puts "foo" w.puts "bar" w.puts "baz" w.close end, proc do |r| r.readline; assert_equal(1, $.) r.readline; assert_equal(2, $.) r.lineno = 1000; assert_equal(2, $.) r.readline; assert_equal(1001, $.) assert_raise(EOFError) { r.readline } end) end def test_each_char pipe(proc do |w| w.puts "foo" w.puts "bar" w.puts "baz" w.close end, proc do |r| a = [] r.each_char {|c| a << c } assert_equal(%w(f o o) + ["\n"] + %w(b a r) + ["\n"] + %w(b a z) + ["\n"], a) end) end def test_lines verbose, $VERBOSE = $VERBOSE, nil pipe(proc do |w| w.puts "foo" w.puts "bar" w.puts "baz" w.close end, proc do |r| e = nil assert_warn(/deprecated/) { e = r.lines } assert_equal("foo\n", e.next) assert_equal("bar\n", e.next) assert_equal("baz\n", e.next) assert_raise(StopIteration) { e.next } end) ensure $VERBOSE = verbose end def test_bytes verbose, $VERBOSE = $VERBOSE, nil pipe(proc do |w| w.binmode w.puts "foo" w.puts "bar" w.puts "baz" w.close end, proc do |r| e = nil assert_warn(/deprecated/) { e = r.bytes } (%w(f o o) + ["\n"] + %w(b a r) + ["\n"] + %w(b a z) + ["\n"]).each do |c| assert_equal(c.ord, e.next) end assert_raise(StopIteration) { e.next } end) ensure $VERBOSE = verbose end def test_chars verbose, $VERBOSE = $VERBOSE, nil pipe(proc do |w| w.puts "foo" w.puts "bar" w.puts "baz" w.close end, proc do |r| e = nil assert_warn(/deprecated/) { e = r.chars } (%w(f o o) + ["\n"] + %w(b a r) + ["\n"] + %w(b a z) + ["\n"]).each do |c| assert_equal(c, e.next) end assert_raise(StopIteration) { e.next } end) ensure $VERBOSE = verbose end def test_readbyte pipe(proc do |w| w.binmode w.puts "foo" w.puts "bar" w.puts "baz" w.close end, proc do |r| r.binmode (%w(f o o) + ["\n"] + %w(b a r) + ["\n"] + %w(b a z) + ["\n"]).each do |c| assert_equal(c.ord, r.readbyte) end assert_raise(EOFError) { r.readbyte } end) end def test_readchar pipe(proc do |w| w.puts "foo" w.puts "bar" w.puts "baz" w.close end, proc do |r| (%w(f o o) + ["\n"] + %w(b a r) + ["\n"] + %w(b a z) + ["\n"]).each do |c| assert_equal(c, r.readchar) end assert_raise(EOFError) { r.readchar } end) end def test_close_on_exec ruby do |f| assert_equal(true, f.close_on_exec?) f.close_on_exec = false assert_equal(false, f.close_on_exec?) f.close_on_exec = true assert_equal(true, f.close_on_exec?) f.close_on_exec = false assert_equal(false, f.close_on_exec?) end with_pipe do |r, w| assert_equal(true, r.close_on_exec?) r.close_on_exec = false assert_equal(false, r.close_on_exec?) r.close_on_exec = true assert_equal(true, r.close_on_exec?) r.close_on_exec = false assert_equal(false, r.close_on_exec?) assert_equal(true, w.close_on_exec?) w.close_on_exec = false assert_equal(false, w.close_on_exec?) w.close_on_exec = true assert_equal(true, w.close_on_exec?) w.close_on_exec = false assert_equal(false, w.close_on_exec?) end end if have_close_on_exec? def test_pos make_tempfile {|t| open(t.path, IO::RDWR|IO::CREAT|IO::TRUNC, 0600) do |f| f.write "Hello" assert_equal(5, f.pos) end open(t.path, IO::RDWR|IO::CREAT|IO::TRUNC, 0600) do |f| f.sync = true f.read f.write "Hello" assert_equal(5, f.pos) end } end def test_pos_with_getc _bug6179 = '[ruby-core:43497]' make_tempfile {|t| ["", "t", "b"].each do |mode| open(t.path, "w#{mode}") do |f| f.write "0123456789\n" end open(t.path, "r#{mode}") do |f| assert_equal 0, f.pos, "mode=r#{mode}" assert_equal '0', f.getc, "mode=r#{mode}" assert_equal 1, f.pos, "mode=r#{mode}" assert_equal '1', f.getc, "mode=r#{mode}" assert_equal 2, f.pos, "mode=r#{mode}" assert_equal '2', f.getc, "mode=r#{mode}" assert_equal 3, f.pos, "mode=r#{mode}" assert_equal '3', f.getc, "mode=r#{mode}" assert_equal 4, f.pos, "mode=r#{mode}" assert_equal '4', f.getc, "mode=r#{mode}" end end } end def can_seek_data(f) if /linux/ =~ RUBY_PLATFORM require "-test-/file" # lseek(2) case Bug::File::Fs.fsname(f.path) when "btrfs" return true if (Etc.uname[:release].split('.').map(&:to_i) <=> [3,1]) >= 0 when "ocfs" return true if (Etc.uname[:release].split('.').map(&:to_i) <=> [3,2]) >= 0 when "xfs" return true if (Etc.uname[:release].split('.').map(&:to_i) <=> [3,5]) >= 0 when "ext4" return true if (Etc.uname[:release].split('.').map(&:to_i) <=> [3,8]) >= 0 when "tmpfs" return true if (Etc.uname[:release].split('.').map(&:to_i) <=> [3,8]) >= 0 end end false end def test_seek make_tempfile {|t| open(t.path) { |f| f.seek(9) assert_equal("az\n", f.read) } open(t.path) { |f| f.seek(9, IO::SEEK_SET) assert_equal("az\n", f.read) } open(t.path) { |f| f.seek(-4, IO::SEEK_END) assert_equal("baz\n", f.read) } open(t.path) { |f| assert_equal("foo\n", f.gets) f.seek(2, IO::SEEK_CUR) assert_equal("r\nbaz\n", f.read) } if defined?(IO::SEEK_DATA) open(t.path) { |f| break unless can_seek_data(f) assert_equal("foo\n", f.gets) f.seek(0, IO::SEEK_DATA) assert_equal("foo\nbar\nbaz\n", f.read) } open(t.path, 'r+') { |f| break unless can_seek_data(f) f.seek(100*1024, IO::SEEK_SET) f.print("zot\n") f.seek(50*1024, IO::SEEK_DATA) assert_operator(f.pos, :>=, 50*1024) assert_match(/\A\0*zot\n\z/, f.read) } end if defined?(IO::SEEK_HOLE) open(t.path) { |f| break unless can_seek_data(f) assert_equal("foo\n", f.gets) f.seek(0, IO::SEEK_HOLE) assert_operator(f.pos, :>, 20) f.seek(100*1024, IO::SEEK_HOLE) assert_equal("", f.read) } end } end def test_seek_symwhence make_tempfile {|t| open(t.path) { |f| f.seek(9, :SET) assert_equal("az\n", f.read) } open(t.path) { |f| f.seek(-4, :END) assert_equal("baz\n", f.read) } open(t.path) { |f| assert_equal("foo\n", f.gets) f.seek(2, :CUR) assert_equal("r\nbaz\n", f.read) } if defined?(IO::SEEK_DATA) open(t.path) { |f| break unless can_seek_data(f) assert_equal("foo\n", f.gets) f.seek(0, :DATA) assert_equal("foo\nbar\nbaz\n", f.read) } open(t.path, 'r+') { |f| break unless can_seek_data(f) f.seek(100*1024, :SET) f.print("zot\n") f.seek(50*1024, :DATA) assert_operator(f.pos, :>=, 50*1024) assert_match(/\A\0*zot\n\z/, f.read) } end if defined?(IO::SEEK_HOLE) open(t.path) { |f| break unless can_seek_data(f) assert_equal("foo\n", f.gets) f.seek(0, :HOLE) assert_operator(f.pos, :>, 20) f.seek(100*1024, :HOLE) assert_equal("", f.read) } end } end def test_sysseek make_tempfile {|t| open(t.path) do |f| f.sysseek(-4, IO::SEEK_END) assert_equal("baz\n", f.read) end open(t.path) do |f| a = [f.getc, f.getc, f.getc] a.reverse_each {|c| f.ungetc c } assert_raise(IOError) { f.sysseek(1) } end } end def test_syswrite make_tempfile {|t| open(t.path, "w") do |f| o = Object.new def o.to_s; "FOO\n"; end f.syswrite(o) end assert_equal("FOO\n", File.read(t.path)) } end def test_sysread make_tempfile {|t| open(t.path) do |f| a = [f.getc, f.getc, f.getc] a.reverse_each {|c| f.ungetc c } assert_raise(IOError) { f.sysread(1) } end } end def test_sysread_with_not_empty_buffer pipe(proc do |w| w.write "foob" w.close end, proc do |r| r.sysread( 5, s = "01234567" ) assert_equal( "foob", s ) end) end def test_flag make_tempfile {|t| assert_raise(ArgumentError) do open(t.path, "z") { } end assert_raise(ArgumentError) do open(t.path, "rr") { } end assert_raise(ArgumentError) do open(t.path, "rbt") { } end } end def test_sysopen make_tempfile {|t| fd = IO.sysopen(t.path) assert_kind_of(Integer, fd) f = IO.for_fd(fd) assert_equal("foo\nbar\nbaz\n", f.read) f.close fd = IO.sysopen(t.path, "w", 0666) assert_kind_of(Integer, fd) if defined?(Fcntl::F_GETFL) f = IO.for_fd(fd) else f = IO.for_fd(fd, 0666) end f.write("FOO\n") f.close fd = IO.sysopen(t.path, "r") assert_kind_of(Integer, fd) f = IO.for_fd(fd) assert_equal("FOO\n", f.read) f.close } end def try_fdopen(fd, autoclose = true, level = 50) if level > 0 begin 1.times {return try_fdopen(fd, autoclose, level - 1)} ensure GC.start end else WeakRef.new(IO.for_fd(fd, autoclose: autoclose)) end end def test_autoclose feature2250 = '[ruby-core:26222]' pre = 'ft2250' Dir.mktmpdir {|d| t = open("#{d}/#{pre}", "w") f = IO.for_fd(t.fileno) assert_equal(true, f.autoclose?) f.autoclose = false assert_equal(false, f.autoclose?) f.close assert_nothing_raised(Errno::EBADF, feature2250) {t.close} t = open("#{d}/#{pre}", "w") f = IO.for_fd(t.fileno, autoclose: false) assert_equal(false, f.autoclose?) f.autoclose = true assert_equal(true, f.autoclose?) f.close assert_raise(Errno::EBADF, feature2250) {t.close} } end def test_autoclose_true_closed_by_finalizer # http://ci.rvm.jp/results/trunk-mjit@silicon-docker/1465760 # http://ci.rvm.jp/results/trunk-mjit@silicon-docker/1469765 skip 'this randomly fails with MJIT' if RubyVM::MJIT.enabled? feature2250 = '[ruby-core:26222]' pre = 'ft2250' t = Tempfile.new(pre) w = try_fdopen(t.fileno) begin w.close begin t.close rescue Errno::EBADF end skip "expect IO object was GC'ed but not recycled yet" rescue WeakRef::RefError assert_raise(Errno::EBADF, feature2250) {t.close} end ensure t&.close! end def test_autoclose_false_closed_by_finalizer feature2250 = '[ruby-core:26222]' pre = 'ft2250' t = Tempfile.new(pre) w = try_fdopen(t.fileno, false) begin w.close t.close skip "expect IO object was GC'ed but not recycled yet" rescue WeakRef::RefError assert_nothing_raised(Errno::EBADF, feature2250) {t.close} end ensure t.close! end def test_open_redirect o = Object.new def o.to_open; self; end assert_equal(o, open(o)) o2 = nil open(o) do |f| o2 = f end assert_equal(o, o2) end def test_open_pipe open("|" + EnvUtil.rubybin, "r+") do |f| f.puts "puts 'foo'" f.close_write assert_equal("foo\n", f.read) end end def test_read_command assert_equal("foo\n", IO.read("|echo foo")) assert_raise(Errno::ENOENT, Errno::EINVAL) do File.read("|#{EnvUtil.rubybin} -e puts") end assert_raise(Errno::ENOENT, Errno::EINVAL) do File.binread("|#{EnvUtil.rubybin} -e puts") end assert_raise(Errno::ENOENT, Errno::EINVAL) do Class.new(IO).read("|#{EnvUtil.rubybin} -e puts") end assert_raise(Errno::ENOENT, Errno::EINVAL) do Class.new(IO).binread("|#{EnvUtil.rubybin} -e puts") end assert_raise(Errno::ESPIPE) do IO.read("|echo foo", 1, 1) end end def test_reopen make_tempfile {|t| open(__FILE__) do |f| f.gets assert_nothing_raised { f.reopen(t.path) assert_equal("foo\n", f.gets) } end open(__FILE__) do |f| f.gets f2 = open(t.path) begin f2.gets assert_nothing_raised { f.reopen(f2) assert_equal("bar\n", f.gets, '[ruby-core:24240]') } ensure f2.close end end open(__FILE__) do |f| f2 = open(t.path) begin f.reopen(f2) assert_equal("foo\n", f.gets) assert_equal("bar\n", f.gets) f.reopen(f2) assert_equal("baz\n", f.gets, '[ruby-dev:39479]') ensure f2.close end end } end def test_reopen_inherit mkcdtmpdir { system(EnvUtil.rubybin, '-e', <<-"End") f = open("out", "w") STDOUT.reopen(f) STDERR.reopen(f) system(#{EnvUtil.rubybin.dump}, '-e', 'STDOUT.print "out"') system(#{EnvUtil.rubybin.dump}, '-e', 'STDERR.print "err"') End assert_equal("outerr", File.read("out")) } end def test_reopen_stdio mkcdtmpdir { fname = 'bug11319' File.write(fname, 'hello') system(EnvUtil.rubybin, '-e', "STDOUT.reopen('#{fname}', 'w+')") assert_equal('', File.read(fname)) } end def test_reopen_mode feature7067 = '[ruby-core:47694]' make_tempfile {|t| open(__FILE__) do |f| assert_nothing_raised { f.reopen(t.path, "r") assert_equal("foo\n", f.gets) } end open(__FILE__) do |f| assert_nothing_raised(feature7067) { f.reopen(t.path, File::RDONLY) assert_equal("foo\n", f.gets) } end } end def test_reopen_opt feature7103 = '[ruby-core:47806]' make_tempfile {|t| open(__FILE__) do |f| assert_nothing_raised(feature7103) { f.reopen(t.path, "r", binmode: true) } assert_equal("foo\n", f.gets) end open(__FILE__) do |f| assert_nothing_raised(feature7103) { f.reopen(t.path, autoclose: false) } assert_equal("foo\n", f.gets) end } end def make_tempfile_for_encoding t = make_tempfile open(t.path, "rb+:utf-8") {|f| f.puts "\u7d05\u7389bar\n"} if block_given? yield t else t end ensure t&.close(true) if block_given? end def test_reopen_encoding make_tempfile_for_encoding {|t| open(__FILE__) {|f| f.reopen(t.path, "r:utf-8") s = f.gets assert_equal(Encoding::UTF_8, s.encoding) assert_equal("\u7d05\u7389bar\n", s) } open(__FILE__) {|f| f.reopen(t.path, "r:UTF-8:EUC-JP") s = f.gets assert_equal(Encoding::EUC_JP, s.encoding) assert_equal("\xB9\xC8\xB6\xCCbar\n".force_encoding(Encoding::EUC_JP), s) } } end def test_reopen_opt_encoding feature7103 = '[ruby-core:47806]' make_tempfile_for_encoding {|t| open(__FILE__) {|f| assert_nothing_raised(feature7103) {f.reopen(t.path, encoding: "ASCII-8BIT")} s = f.gets assert_equal(Encoding::ASCII_8BIT, s.encoding) assert_equal("\xe7\xb4\x85\xe7\x8e\x89bar\n", s) } open(__FILE__) {|f| assert_nothing_raised(feature7103) {f.reopen(t.path, encoding: "UTF-8:EUC-JP")} s = f.gets assert_equal(Encoding::EUC_JP, s.encoding) assert_equal("\xB9\xC8\xB6\xCCbar\n".force_encoding(Encoding::EUC_JP), s) } } end bug11320 = '[ruby-core:69780] [Bug #11320]' ["UTF-8", "EUC-JP", "Shift_JIS"].each do |enc| define_method("test_reopen_nonascii(#{enc})") do mkcdtmpdir do fname = "\u{30eb 30d3 30fc}".encode(enc) File.write(fname, '') assert_file.exist?(fname) stdin = $stdin.dup begin assert_nothing_raised(Errno::ENOENT, "#{bug11320}: #{enc}") { $stdin.reopen(fname, 'r') } ensure $stdin.reopen(stdin) stdin.close end end end end def test_foreach a = [] IO.foreach("|" + EnvUtil.rubybin + " -e 'puts :foo; puts :bar; puts :baz'") {|x| a << x } assert_equal(["foo\n", "bar\n", "baz\n"], a) a = [] IO.foreach("|" + EnvUtil.rubybin + " -e 'puts :zot'", :open_args => ["r"]) {|x| a << x } assert_equal(["zot\n"], a) make_tempfile {|t| a = [] IO.foreach(t.path) {|x| a << x } assert_equal(["foo\n", "bar\n", "baz\n"], a) a = [] IO.foreach(t.path, :mode => "r") {|x| a << x } assert_equal(["foo\n", "bar\n", "baz\n"], a) a = [] IO.foreach(t.path, :open_args => []) {|x| a << x } assert_equal(["foo\n", "bar\n", "baz\n"], a) a = [] IO.foreach(t.path, :open_args => ["r"]) {|x| a << x } assert_equal(["foo\n", "bar\n", "baz\n"], a) a = [] IO.foreach(t.path, "b") {|x| a << x } assert_equal(["foo\nb", "ar\nb", "az\n"], a) a = [] IO.foreach(t.path, 3) {|x| a << x } assert_equal(["foo", "\n", "bar", "\n", "baz", "\n"], a) a = [] IO.foreach(t.path, "b", 3) {|x| a << x } assert_equal(["foo", "\nb", "ar\n", "b", "az\n"], a) bug = '[ruby-dev:31525]' assert_raise(ArgumentError, bug) {IO.foreach} a = nil assert_nothing_raised(ArgumentError, bug) {a = IO.foreach(t.path).to_a} assert_equal(["foo\n", "bar\n", "baz\n"], a, bug) bug6054 = '[ruby-dev:45267]' assert_raise_with_message(IOError, /not opened for reading/, bug6054) do IO.foreach(t.path, mode:"w").next end } end def test_s_readlines make_tempfile {|t| assert_equal(["foo\n", "bar\n", "baz\n"], IO.readlines(t.path)) assert_equal(["foo\nb", "ar\nb", "az\n"], IO.readlines(t.path, "b")) assert_equal(["fo", "o\n", "ba", "r\n", "ba", "z\n"], IO.readlines(t.path, 2)) assert_equal(["fo", "o\n", "b", "ar", "\nb", "az", "\n"], IO.readlines(t.path, "b", 2)) } end def test_printf pipe(proc do |w| printf(w, "foo %s baz\n", "bar") w.close_write end, proc do |r| assert_equal("foo bar baz\n", r.read) end) end def test_print make_tempfile {|t| assert_in_out_err(["-", t.path], "print while $<.gets", %w(foo bar baz), []) } end def test_print_separators EnvUtil.suppress_warning {$, = ':'} $\ = "\n" pipe(proc do |w| w.print('a') w.print('a','b','c') w.close end, proc do |r| assert_equal("a\n", r.gets) assert_equal("a:b:c\n", r.gets) assert_nil r.gets r.close end) ensure $, = nil $\ = nil end def test_putc pipe(proc do |w| w.putc "A" w.putc "BC" w.putc 68 w.close_write end, proc do |r| assert_equal("ABD", r.read) end) assert_in_out_err([], "putc 65", %w(A), []) end def test_puts_recursive_array a = ["foo"] a << a pipe(proc do |w| w.puts a w.close end, proc do |r| assert_equal("foo\n[...]\n", r.read) end) end def test_puts_parallel skip "not portable" pipe(proc do |w| threads = [] 100.times do threads << Thread.new { w.puts "hey" } end threads.each(&:join) w.close end, proc do |r| assert_equal("hey\n" * 100, r.read) end) end def test_puts_old_write capture = String.new def capture.write(str) self << str end capture.clear assert_warning(/[.#]write is outdated/) do stdout, $stdout = $stdout, capture puts "hey" ensure $stdout = stdout end assert_equal("hey\n", capture) end def test_display pipe(proc do |w| "foo".display(w) w.close end, proc do |r| assert_equal("foo", r.read) end) assert_in_out_err([], "'foo'.display", %w(foo), []) end def test_set_stdout assert_raise(TypeError) { $> = Object.new } assert_in_out_err([], "$> = $stderr\nputs 'foo'", [], %w(foo)) assert_separately(%w[-Eutf-8], "#{<<~"begin;"}\n#{<<~"end;"}") begin; alias $\u{6a19 6e96 51fa 529b} $stdout x = eval("class X\u{307b 3052}; self; end".encode("euc-jp")) assert_raise_with_message(TypeError, /\\$\u{6a19 6e96 51fa 529b} must.*, X\u{307b 3052} given/) do $\u{6a19 6e96 51fa 529b} = x.new end end; end def test_initialize return unless defined?(Fcntl::F_GETFL) make_tempfile {|t| fd = IO.sysopen(t.path, "w") assert_kind_of(Integer, fd) %w[r r+ w+ a+].each do |mode| assert_raise(Errno::EINVAL, "#{mode} [ruby-dev:38571]") {IO.new(fd, mode)} end f = IO.new(fd, "w") f.write("FOO\n") f.close assert_equal("FOO\n", File.read(t.path)) } end def test_reinitialize make_tempfile {|t| f = open(t.path) begin assert_raise(RuntimeError) do f.instance_eval { initialize } end ensure f.close end } end def test_new_with_block assert_in_out_err([], "r, w = IO.pipe; r.autoclose=false; IO.new(r.fileno) {}.close", [], /^.+$/) n = "IO\u{5165 51fa 529b}" c = eval("class #{n} < IO; self; end") IO.pipe do |r, w| assert_warning(/#{n}/) { r.autoclose=false io = c.new(r.fileno) {} io.close } end end def test_readline2 assert_in_out_err(["-e", <<-SRC], "foo\nbar\nbaz\n", %w(foo bar baz end), []) puts readline puts readline puts readline begin puts readline rescue EOFError puts "end" end SRC end def test_readlines assert_in_out_err(["-e", "p readlines"], "foo\nbar\nbaz\n", ["[\"foo\\n\", \"bar\\n\", \"baz\\n\"]"], []) end def test_s_read make_tempfile {|t| assert_equal("foo\nbar\nbaz\n", File.read(t.path)) assert_equal("foo\nba", File.read(t.path, 6)) assert_equal("bar\n", File.read(t.path, 4, 4)) } end def test_uninitialized assert_raise(IOError) { IO.allocate.print "" } end def test_nofollow # O_NOFOLLOW is not standard. mkcdtmpdir { open("file", "w") {|f| f << "content" } begin File.symlink("file", "slnk") rescue NotImplementedError return end assert_raise(Errno::EMLINK, Errno::ELOOP) { open("slnk", File::RDONLY|File::NOFOLLOW) {} } assert_raise(Errno::EMLINK, Errno::ELOOP) { File.foreach("slnk", :open_args=>[File::RDONLY|File::NOFOLLOW]) {} } } end if /freebsd|linux/ =~ RUBY_PLATFORM and defined? File::NOFOLLOW def test_tainted make_tempfile {|t| assert_predicate(File.read(t.path, 4), :tainted?, '[ruby-dev:38826]') assert_predicate(File.open(t.path) {|f| f.read(4)}, :tainted?, '[ruby-dev:38826]') } end def test_binmode_after_closed make_tempfile {|t| assert_raise(IOError) {t.binmode} } end def test_DATA_binmode assert_separately([], <<-SRC) assert_not_predicate(DATA, :binmode?) __END__ SRC end def test_threaded_flush bug3585 = '[ruby-core:31348]' src = "#{<<~"begin;"}\n#{<<~'end;'}" begin; t = Thread.new { sleep 3 } Thread.new {sleep 1; t.kill; p 'hi!'} t.join end; 10.times.map do Thread.start do assert_in_out_err([], src, timeout: 20) {|stdout, stderr| assert_no_match(/hi.*hi/, stderr.join, bug3585) } end end.each {|th| th.join} end def test_flush_in_finalizer1 bug3910 = '[ruby-dev:42341]' tmp = Tempfile.open("bug3910") {|t| path = t.path t.close fds = [] assert_nothing_raised(TypeError, bug3910) do 500.times { f = File.open(path, "w") f.instance_variable_set(:@test_flush_in_finalizer1, true) fds << f.fileno f.print "hoge" } end t } ensure ObjectSpace.each_object(File) {|f| if f.instance_variables.include?(:@test_flush_in_finalizer1) f.close end } tmp.close! end def test_flush_in_finalizer2 bug3910 = '[ruby-dev:42341]' Tempfile.open("bug3910") {|t| path = t.path t.close begin 1.times do io = open(path,"w") io.instance_variable_set(:@test_flush_in_finalizer2, true) io.print "hoge" end assert_nothing_raised(TypeError, bug3910) do GC.start end ensure ObjectSpace.each_object(File) {|f| if f.instance_variables.include?(:@test_flush_in_finalizer2) f.close end } end t.close! } end def test_readlines_limit_0 bug4024 = '[ruby-dev:42538]' make_tempfile {|t| open(t.path, "r") do |io| assert_raise(ArgumentError, bug4024) do io.readlines(0) end end } end def test_each_line_limit_0 bug4024 = '[ruby-dev:42538]' make_tempfile {|t| open(t.path, "r") do |io| assert_raise(ArgumentError, bug4024) do io.each_line(0).next end end } end def os_and_fs(path) uname = Etc.uname os = "#{uname[:sysname]} #{uname[:release]}" fs = nil if uname[:sysname] == 'Linux' # [ruby-dev:45703] Old Linux's fadvise() doesn't work on tmpfs. mount = `mount` mountpoints = [] mount.scan(/ on (\S+) type (\S+) /) { mountpoints << [$1, $2] } mountpoints.sort_by {|mountpoint, fstype| mountpoint.length }.reverse_each {|mountpoint, fstype| if path == mountpoint fs = fstype break end mountpoint += "/" if %r{/\z} !~ mountpoint if path.start_with?(mountpoint) fs = fstype break end } end if fs "#{fs} on #{os}" else os end end def test_advise make_tempfile {|tf| assert_raise(ArgumentError, "no arguments") { tf.advise } %w{normal random sequential willneed dontneed noreuse}.map(&:to_sym).each do |adv| [[0,0], [0, 20], [400, 2]].each do |offset, len| open(tf.path) do |t| ret = assert_nothing_raised(lambda { os_and_fs(tf.path) }) { begin t.advise(adv, offset, len) rescue Errno::EINVAL => e if /linux/ =~ RUBY_PLATFORM && (Etc.uname[:release].split('.').map(&:to_i) <=> [3,6]) < 0 next # [ruby-core:65355] tmpfs is not supported else raise e end end } assert_nil(ret) assert_raise(ArgumentError, "superfluous arguments") do t.advise(adv, offset, len, offset) end assert_raise(TypeError, "wrong type for first argument") do t.advise(adv.to_s, offset, len) end assert_raise(TypeError, "wrong type for last argument") do t.advise(adv, offset, Array(len)) end assert_raise(RangeError, "last argument too big") do t.advise(adv, offset, 9999e99) end end assert_raise(IOError, "closed file") do make_tempfile {|tf2| tf2.advise(adv.to_sym, offset, len) } end end end } end def test_invalid_advise feature4204 = '[ruby-dev:42887]' make_tempfile {|tf| %W{Normal rand glark will_need zzzzzzzzzzzz \u2609}.map(&:to_sym).each do |adv| [[0,0], [0, 20], [400, 2]].each do |offset, len| open(tf.path) do |t| assert_raise_with_message(NotImplementedError, /#{Regexp.quote(adv.inspect)}/, feature4204) { t.advise(adv, offset, len) } end end end } end def test_fcntl_lock_linux pad = 0 Tempfile.create(self.class.name) do |f| r, w = IO.pipe pid = fork do r.close lock = [Fcntl::F_WRLCK, IO::SEEK_SET, pad, 12, 34, 0].pack("s!s!i!L!L!i!") f.fcntl Fcntl::F_SETLKW, lock w.syswrite "." sleep end w.close assert_equal ".", r.read(1) r.close pad = 0 getlock = [Fcntl::F_WRLCK, 0, pad, 0, 0, 0].pack("s!s!i!L!L!i!") f.fcntl Fcntl::F_GETLK, getlock ptype, whence, pad, start, len, lockpid = getlock.unpack("s!s!i!L!L!i!") assert_equal(ptype, Fcntl::F_WRLCK) assert_equal(whence, IO::SEEK_SET) assert_equal(start, 12) assert_equal(len, 34) assert_equal(pid, lockpid) Process.kill :TERM, pid Process.waitpid2(pid) end end if /x86_64-linux/ =~ RUBY_PLATFORM and # A binary form of struct flock depend on platform [nil].pack("p").bytesize == 8 # unless x32 platform. def test_fcntl_lock_freebsd start = 12 len = 34 sysid = 0 Tempfile.create(self.class.name) do |f| r, w = IO.pipe pid = fork do r.close lock = [start, len, 0, Fcntl::F_WRLCK, IO::SEEK_SET, sysid].pack("qqis!s!i!") f.fcntl Fcntl::F_SETLKW, lock w.syswrite "." sleep end w.close assert_equal ".", r.read(1) r.close getlock = [0, 0, 0, Fcntl::F_WRLCK, 0, 0].pack("qqis!s!i!") f.fcntl Fcntl::F_GETLK, getlock start, len, lockpid, ptype, whence, sysid = getlock.unpack("qqis!s!i!") assert_equal(ptype, Fcntl::F_WRLCK) assert_equal(whence, IO::SEEK_SET) assert_equal(start, 12) assert_equal(len, 34) assert_equal(pid, lockpid) Process.kill :TERM, pid Process.waitpid2(pid) end end if /freebsd/ =~ RUBY_PLATFORM # A binary form of struct flock depend on platform def test_fcntl_dupfd Tempfile.create(self.class.name) do |f| fd = f.fcntl(Fcntl::F_DUPFD, 63) begin assert_operator(fd, :>=, 63) ensure IO.for_fd(fd).close end end end def test_cross_thread_close_fd with_pipe do |r,w| read_thread = Thread.new do begin r.read(1) rescue => e e end end sleep(0.1) until read_thread.stop? r.close read_thread.join assert_kind_of(IOError, read_thread.value) end end def test_cross_thread_close_stdio assert_separately([], <<-'end;') IO.pipe do |r,w| $stdin.reopen(r) r.close read_thread = Thread.new do begin $stdin.read(1) rescue IOError => e e end end sleep(0.1) until read_thread.stop? $stdin.close assert_kind_of(IOError, read_thread.value) end end; end def test_single_exception_on_close a = [] t = [] 10.times do r, w = IO.pipe a << [r, w] t << Thread.new do while r.gets end rescue IOError Thread.current.pending_interrupt? end end a.each do |r, w| w.write(-"\n") w.close r.close end t.each do |th| assert_equal false, th.value, '[ruby-core:81581] [Bug #13632]' end end def test_open_mode feature4742 = "[ruby-core:36338]" bug6055 = '[ruby-dev:45268]' mkcdtmpdir do assert_not_nil(f = File.open('symbolic', 'w')) f.close assert_not_nil(f = File.open('numeric', File::WRONLY|File::TRUNC|File::CREAT)) f.close assert_not_nil(f = File.open('hash-symbolic', :mode => 'w')) f.close assert_not_nil(f = File.open('hash-numeric', :mode => File::WRONLY|File::TRUNC|File::CREAT), feature4742) f.close assert_nothing_raised(bug6055) {f = File.open('hash-symbolic', binmode: true)} f.close end end def test_s_write mkcdtmpdir do path = "test_s_write" File.write(path, "foo\nbar\nbaz") assert_equal("foo\nbar\nbaz", File.read(path)) File.write(path, "FOO", 0) assert_equal("FOO\nbar\nbaz", File.read(path)) File.write(path, "BAR") assert_equal("BAR", File.read(path)) File.write(path, "\u{3042}", mode: "w", encoding: "EUC-JP") assert_equal("\u{3042}".encode("EUC-JP"), File.read(path, encoding: "EUC-JP")) File.delete path assert_equal(6, File.write(path, 'string', 2)) File.delete path assert_raise(Errno::EINVAL) { File.write('nonexisting','string', -2) } assert_equal(6, File.write(path, 'string')) assert_equal(3, File.write(path, 'sub', 1)) assert_equal("ssubng", File.read(path)) File.delete path assert_equal(3, File.write(path, "foo", encoding: "UTF-8")) File.delete path assert_equal(3, File.write(path, "foo", 0, encoding: "UTF-8")) assert_equal("foo", File.read(path)) assert_equal(1, File.write(path, "f", 1, encoding: "UTF-8")) assert_equal("ffo", File.read(path)) File.delete path assert_equal(1, File.write(path, "f", 1, encoding: "UTF-8")) assert_equal("\00f", File.read(path)) assert_equal(1, File.write(path, "f", 0, encoding: "UTF-8")) assert_equal("ff", File.read(path)) assert_raise(TypeError) { assert_warn(/The last argument is split into positional and keyword parameters/) do File.write(path, "foo", Object.new => Object.new) end } end end def test_s_binread_does_not_leak_with_invalid_offset assert_raise(Errno::EINVAL) { IO.binread(__FILE__, 0, -1) } end def test_s_binwrite mkcdtmpdir do path = "test_s_binwrite" File.binwrite(path, "foo\nbar\nbaz") assert_equal("foo\nbar\nbaz", File.read(path)) File.binwrite(path, "FOO", 0) assert_equal("FOO\nbar\nbaz", File.read(path)) File.binwrite(path, "BAR") assert_equal("BAR", File.read(path)) File.binwrite(path, "\u{3042}") assert_equal("\u{3042}".force_encoding("ASCII-8BIT"), File.binread(path)) File.delete path assert_equal(6, File.binwrite(path, 'string', 2)) File.delete path assert_equal(6, File.binwrite(path, 'string')) assert_equal(3, File.binwrite(path, 'sub', 1)) assert_equal("ssubng", File.binread(path)) assert_equal(6, File.size(path)) assert_raise(Errno::EINVAL) { File.binwrite('nonexisting', 'string', -2) } assert_nothing_raised(TypeError) { File.binwrite(path, "string", mode: "w", encoding: "EUC-JP") } end end def test_race_between_read Tempfile.create("test") {|file| begin path = file.path file.close write_file = File.open(path, "wt") read_file = File.open(path, "rt") threads = [] 10.times do |i| threads << Thread.new {write_file.print(i)} threads << Thread.new {read_file.read} end assert_join_threads(threads) assert(true, "[ruby-core:37197]") ensure read_file.close write_file.close end } end def test_warn assert_warning "warning\n" do warn "warning" end assert_warning '' do warn end assert_warning "[Feature #5029]\n[ruby-core:38070]\n" do warn "[Feature #5029]", "[ruby-core:38070]" end end def test_cloexec return unless defined? Fcntl::FD_CLOEXEC open(__FILE__) {|f| assert_predicate(f, :close_on_exec?) g = f.dup begin assert_predicate(g, :close_on_exec?) f.reopen(g) assert_predicate(f, :close_on_exec?) ensure g.close end g = IO.new(f.fcntl(Fcntl::F_DUPFD)) begin assert_predicate(g, :close_on_exec?) ensure g.close end } IO.pipe {|r,w| assert_predicate(r, :close_on_exec?) assert_predicate(w, :close_on_exec?) } end def test_ioctl_linux # Alpha, mips, sparc and ppc have an another ioctl request number scheme. # So, hardcoded 0x80045200 may fail. assert_nothing_raised do File.open('/dev/urandom'){|f1| entropy_count = "" # RNDGETENTCNT(0x80045200) mean "get entropy count". f1.ioctl(0x80045200, entropy_count) } end buf = '' assert_nothing_raised do fionread = 0x541B File.open(__FILE__){|f1| f1.ioctl(fionread, buf) } end assert_equal(File.size(__FILE__), buf.unpack('i!')[0]) end if /^(?:i.?86|x86_64)-linux/ =~ RUBY_PLATFORM def test_ioctl_linux2 return unless STDIN.tty? # stdin is not a terminal begin f = File.open('/dev/tty') rescue Errno::ENOENT, Errno::ENXIO => e skip e.message else tiocgwinsz=0x5413 winsize="" assert_nothing_raised { f.ioctl(tiocgwinsz, winsize) } ensure f&.close end end if /^(?:i.?86|x86_64)-linux/ =~ RUBY_PLATFORM def test_setpos mkcdtmpdir { File.open("tmp.txt", "wb") {|f| f.puts "a" f.puts "bc" f.puts "def" } pos1 = pos2 = pos3 = nil File.open("tmp.txt", "rb") {|f| assert_equal("a\n", f.gets) pos1 = f.pos assert_equal("bc\n", f.gets) pos2 = f.pos assert_equal("def\n", f.gets) pos3 = f.pos assert_equal(nil, f.gets) } File.open("tmp.txt", "rb") {|f| f.pos = pos1 assert_equal("bc\n", f.gets) assert_equal("def\n", f.gets) assert_equal(nil, f.gets) } File.open("tmp.txt", "rb") {|f| f.pos = pos2 assert_equal("def\n", f.gets) assert_equal(nil, f.gets) } File.open("tmp.txt", "rb") {|f| f.pos = pos3 assert_equal(nil, f.gets) } File.open("tmp.txt", "rb") {|f| f.pos = File.size("tmp.txt") s = "not empty string " assert_equal("", f.read(0,s)) } } end def test_std_fileno assert_equal(0, STDIN.fileno) assert_equal(1, STDOUT.fileno) assert_equal(2, STDERR.fileno) assert_equal(0, $stdin.fileno) assert_equal(1, $stdout.fileno) assert_equal(2, $stderr.fileno) end def test_frozen_fileno bug9865 = '[ruby-dev:48241] [Bug #9865]' with_pipe do |r,w| fd = r.fileno assert_equal(fd, r.freeze.fileno, bug9865) end end def test_frozen_autoclose with_pipe do |r,w| assert_equal(true, r.freeze.autoclose?) end end def test_sysread_locktmp bug6099 = '[ruby-dev:45297]' buf = " " * 100 data = "a" * 100 with_pipe do |r,w| th = Thread.new {r.sysread(100, buf)} Thread.pass until th.stop? buf.replace("") assert_empty(buf, bug6099) w.write(data) Thread.pass while th.alive? th.join end assert_equal(data, buf, bug6099) end def test_readpartial_locktmp bug6099 = '[ruby-dev:45297]' buf = " " * 100 data = "a" * 100 th = nil with_pipe do |r,w| r.nonblock = true th = Thread.new {r.readpartial(100, buf)} Thread.pass until th.stop? assert_equal 100, buf.bytesize msg = /can't modify string; temporarily locked/ assert_raise_with_message(RuntimeError, msg) do buf.replace("") end assert_predicate(th, :alive?) w.write(data) th.join end assert_equal(data, buf, bug6099) end def test_advise_pipe # we don't know if other platforms have a real posix_fadvise() with_pipe do |r,w| # Linux 2.6.15 and earlier returned EINVAL instead of ESPIPE assert_raise(Errno::ESPIPE, Errno::EINVAL) { r.advise(:willneed) or skip "fadvise(2) is not implemented" } assert_raise(Errno::ESPIPE, Errno::EINVAL) { w.advise(:willneed) or skip "fadvise(2) is not implemented" } end end if /linux/ =~ RUBY_PLATFORM def assert_buffer_not_raise_shared_string_error bug6764 = '[ruby-core:46586]' bug9847 = '[ruby-core:62643] [Bug #9847]' size = 28 data = [*"a".."z", *"A".."Z"].shuffle.join("") t = Tempfile.new("test_io") t.write(data) t.close w = [] assert_nothing_raised(RuntimeError, bug6764) do buf = '' File.open(t.path, "r") do |r| while yield(r, size, buf) w << buf.dup end end end assert_equal(data, w.join(""), bug9847) ensure t.close! end def test_read_buffer_not_raise_shared_string_error assert_buffer_not_raise_shared_string_error do |r, size, buf| r.read(size, buf) end end def test_sysread_buffer_not_raise_shared_string_error assert_buffer_not_raise_shared_string_error do |r, size, buf| begin r.sysread(size, buf) rescue EOFError nil end end end def test_readpartial_buffer_not_raise_shared_string_error assert_buffer_not_raise_shared_string_error do |r, size, buf| begin r.readpartial(size, buf) rescue EOFError nil end end end def test_puts_recursive_ary bug5986 = '[ruby-core:42444]' c = Class.new { def to_ary [self] end } s = StringIO.new s.puts(c.new) assert_equal("[...]\n", s.string, bug5986) end def test_io_select_with_many_files bug8080 = '[ruby-core:53349]' assert_normal_exit %q{ require "tempfile" # Unfortunately, ruby doesn't export FD_SETSIZE. then we assume it's 1024. fd_setsize = 1024 # try to raise RLIM_NOFILE to >FD_SETSIZE begin Process.setrlimit(Process::RLIMIT_NOFILE, fd_setsize+20) rescue Errno::EPERM exit 0 end tempfiles = [] (0..fd_setsize+1).map {|i| tempfiles << Tempfile.open("test_io_select_with_many_files") } IO.select(tempfiles) }, bug8080, timeout: 100 end if defined?(Process::RLIMIT_NOFILE) def test_read_32bit_boundary bug8431 = '[ruby-core:55098] [Bug #8431]' make_tempfile {|t| assert_separately(["-", bug8431, t.path], <<-"end;") msg = ARGV.shift f = open(ARGV[0], "rb") f.seek(0xffff_ffff) assert_nil(f.read(1), msg) end; } end if /mswin|mingw/ =~ RUBY_PLATFORM def test_write_32bit_boundary bug8431 = '[ruby-core:55098] [Bug #8431]' make_tempfile {|t| def t.close(unlink_now = false) # TODO: Tempfile should deal with this delay on Windows? # NOTE: re-opening with O_TEMPORARY does not work. path = self.path ret = super if unlink_now begin File.unlink(path) rescue Errno::ENOENT rescue Errno::EACCES sleep(2) retry end end ret end begin assert_separately(["-", bug8431, t.path], <<-"end;", timeout: 30) msg = ARGV.shift f = open(ARGV[0], "wb") f.seek(0xffff_ffff) begin # this will consume very long time or fail by ENOSPC on a # filesystem which sparse file is not supported f.write('1') pos = f.tell rescue Errno::ENOSPC skip "non-sparse file system" rescue SystemCallError else assert_equal(0x1_0000_0000, pos, msg) end end; rescue Timeout::Error skip "Timeout because of slow file writing" end } end if /mswin|mingw/ =~ RUBY_PLATFORM def test_read_unlocktmp_ensure bug8669 = '[ruby-core:56121] [Bug #8669]' str = "" IO.pipe {|r,| t = Thread.new { assert_raise(RuntimeError) { r.read(nil, str) } } sleep 0.1 until t.stop? t.raise sleep 0.1 while t.alive? assert_nothing_raised(RuntimeError, bug8669) { str.clear } t.join } end if /cygwin/ !~ RUBY_PLATFORM def test_readpartial_unlocktmp_ensure bug8669 = '[ruby-core:56121] [Bug #8669]' str = "" IO.pipe {|r, w| t = Thread.new { assert_raise(RuntimeError) { r.readpartial(4096, str) } } sleep 0.1 until t.stop? t.raise sleep 0.1 while t.alive? assert_nothing_raised(RuntimeError, bug8669) { str.clear } t.join } end if /cygwin/ !~ RUBY_PLATFORM def test_readpartial_bad_args IO.pipe do |r, w| w.write '.' buf = String.new assert_raise(ArgumentError) { r.readpartial(1, buf, exception: false) } assert_raise(TypeError) { r.readpartial(1, exception: false) } assert_equal [[r],[],[]], IO.select([r], nil, nil, 1) assert_equal '.', r.readpartial(1) end end def test_sysread_unlocktmp_ensure bug8669 = '[ruby-core:56121] [Bug #8669]' str = "" IO.pipe {|r, w| t = Thread.new { assert_raise(RuntimeError) { r.sysread(4096, str) } } sleep 0.1 until t.stop? t.raise sleep 0.1 while t.alive? assert_nothing_raised(RuntimeError, bug8669) { str.clear } t.join } end if /cygwin/ !~ RUBY_PLATFORM def test_exception_at_close bug10153 = '[ruby-core:64463] [Bug #10153] exception in close at the end of block' assert_raise(Errno::EBADF, bug10153) do IO.pipe do |r, w| assert_nothing_raised {IO.open(w.fileno) {}} end end end def test_close_twice open(__FILE__) {|f| assert_equal(nil, f.close) assert_equal(nil, f.close) } end def test_close_uninitialized io = IO.allocate assert_raise(IOError) { io.close } end def test_open_fifo_does_not_block_other_threads mkcdtmpdir { File.mkfifo("fifo") assert_separately([], <<-'EOS') t1 = Thread.new { open("fifo", "r") {|r| r.read } } t2 = Thread.new { open("fifo", "w") {|w| w.write "foo" } } t1_value, _ = assert_join_threads([t1, t2]) assert_equal("foo", t1_value) EOS } end if /mswin|mingw|bccwin|cygwin/ !~ RUBY_PLATFORM def test_open_flag make_tempfile do |t| assert_raise(Errno::EEXIST){ open(t.path, File::WRONLY|File::CREAT, flags: File::EXCL){} } assert_raise(Errno::EEXIST){ open(t.path, 'w', flags: File::EXCL){} } assert_raise(Errno::EEXIST){ open(t.path, mode: 'w', flags: File::EXCL){} } end end def test_open_flag_binary make_tempfile do |t| open(t.path, File::RDONLY, flags: File::BINARY) do |f| assert_equal true, f.binmode? end open(t.path, 'r', flags: File::BINARY) do |f| assert_equal true, f.binmode? end open(t.path, mode: 'r', flags: File::BINARY) do |f| assert_equal true, f.binmode? end end end if File::BINARY != 0 def test_exclusive_mode make_tempfile do |t| assert_raise(Errno::EEXIST){ open(t.path, 'wx'){} } assert_raise(ArgumentError){ open(t.path, 'rx'){} } assert_raise(ArgumentError){ open(t.path, 'ax'){} } end end def test_race_gets_and_close opt = { signal: :ABRT, timeout: 200 } assert_separately([], "#{<<-"begin;"}\n#{<<-"end;"}", **opt) bug13076 = '[ruby-core:78845] [Bug #13076]' begin; 10.times do |i| a = [] t = [] 10.times do r,w = IO.pipe a << [r,w] t << Thread.new do begin while r.gets end rescue IOError end end end a.each do |r,w| w.puts "hoge" w.close r.close end t.each do |th| assert_same(th, th.join(2), bug13076) end end end; end def test_race_closed_stream assert_separately([], "#{<<-"begin;"}\n#{<<-"end;"}") begin; bug13158 = '[ruby-core:79262] [Bug #13158]' closed = nil q = Queue.new IO.pipe do |r, w| thread = Thread.new do begin q << true assert_raise_with_message(IOError, /stream closed/) do while r.gets end end ensure closed = r.closed? end end q.pop sleep 0.01 until thread.stop? r.close thread.join assert_equal(true, closed, bug13158 + ': stream should be closed') end end; end if RUBY_ENGINE == "ruby" # implementation details def test_foreach_rs_conversion make_tempfile {|t| a = [] rs = Struct.new(:count).new(0) def rs.to_str; self.count += 1; "\n"; end IO.foreach(t.path, rs) {|x| a << x } assert_equal(["foo\n", "bar\n", "baz\n"], a) assert_equal(1, rs.count) } end def test_foreach_rs_invalid make_tempfile {|t| rs = Object.new def rs.to_str; raise "invalid rs"; end assert_raise(RuntimeError) do IO.foreach(t.path, rs, mode:"w") {} end assert_equal(["foo\n", "bar\n", "baz\n"], IO.foreach(t.path).to_a) } end def test_foreach_limit_conversion make_tempfile {|t| a = [] lim = Struct.new(:count).new(0) def lim.to_int; self.count += 1; -1; end IO.foreach(t.path, lim) {|x| a << x } assert_equal(["foo\n", "bar\n", "baz\n"], a) assert_equal(1, lim.count) } end def test_foreach_limit_invalid make_tempfile {|t| lim = Object.new def lim.to_int; raise "invalid limit"; end assert_raise(RuntimeError) do IO.foreach(t.path, lim, mode:"w") {} end assert_equal(["foo\n", "bar\n", "baz\n"], IO.foreach(t.path).to_a) } end def test_readlines_rs_invalid make_tempfile {|t| rs = Object.new def rs.to_str; raise "invalid rs"; end assert_raise(RuntimeError) do IO.readlines(t.path, rs, mode:"w") end assert_equal(["foo\n", "bar\n", "baz\n"], IO.readlines(t.path)) } end def test_readlines_limit_invalid make_tempfile {|t| lim = Object.new def lim.to_int; raise "invalid limit"; end assert_raise(RuntimeError) do IO.readlines(t.path, lim, mode:"w") end assert_equal(["foo\n", "bar\n", "baz\n"], IO.readlines(t.path)) } end def test_closed_stream_in_rescue assert_separately([], "#{<<-"begin;"}\n#{<<~"end;"}") begin; 10.times do assert_nothing_raised(RuntimeError, /frozen IOError/) do IO.pipe do |r, w| th = Thread.start {r.close} r.gets rescue IOError # swallow pending exceptions begin sleep 0.001 rescue IOError retry end ensure th.kill.join end end end end; end def test_write_no_garbage skip "multiple threads already active" if Thread.list.size > 1 res = {} ObjectSpace.count_objects(res) # creates strings on first call [ 'foo'.b, '*' * 24 ].each do |buf| with_pipe do |r, w| GC.disable begin before = ObjectSpace.count_objects(res)[:T_STRING] n = w.write(buf) s = w.syswrite(buf) after = ObjectSpace.count_objects(res)[:T_STRING] ensure GC.enable end assert_equal before, after, "no strings left over after write [ruby-core:78898] [Bug #13085]: #{ before } strings before write -> #{ after } strings after write" assert_not_predicate buf, :frozen?, 'no inadvertent freeze' assert_equal buf.bytesize, n, 'IO#write wrote expected size' assert_equal s, n, 'IO#syswrite wrote expected size' end end end end def test_pread make_tempfile { |t| open(t.path) do |f| assert_equal("bar", f.pread(3, 4)) buf = "asdf" assert_equal("bar", f.pread(3, 4, buf)) assert_equal("bar", buf) assert_raise(EOFError) { f.pread(1, f.size) } end } end if IO.method_defined?(:pread) def test_pwrite make_tempfile { |t| open(t.path, IO::RDWR) do |f| assert_equal(3, f.pwrite("ooo", 4)) assert_equal("ooo", f.pread(3, 4)) end } end if IO.method_defined?(:pread) and IO.method_defined?(:pwrite) def test_select_exceptfds if Etc.uname[:sysname] == 'SunOS' str = 'h'.freeze #(???) Only 1 byte with MSG_OOB on Solaris else str = 'hello'.freeze end TCPServer.open('localhost', 0) do |svr| con = TCPSocket.new('localhost', svr.addr[1]) acc = svr.accept assert_equal str.length, con.send(str, Socket::MSG_OOB) set = IO.select(nil, nil, [acc], 30) assert_equal([[], [], [acc]], set, 'IO#select exceptions array OK') acc.close con.close end end if Socket.const_defined?(:MSG_OOB) def test_recycled_fd_close dot = -'.' IO.pipe do |sig_rd, sig_wr| noex = Thread.new do # everything right and never see exceptions :) until sig_rd.wait_readable(0) IO.pipe do |r, w| th = Thread.new { r.read(1) } w.write(dot) assert_same th, th.join(15), '"good" reader timeout' assert_equal(dot, th.value) end end sig_rd.read(4) end 1000.times do |i| # stupid things and make exceptions: IO.pipe do |r,w| th = Thread.new do begin while r.gets end rescue IOError => e e end end Thread.pass until th.stop? r.close assert_same th, th.join(30), '"bad" reader timeout' assert_match(/stream closed/, th.value.message) end end sig_wr.write 'done' assert_same noex, noex.join(20), '"good" writer timeout' assert_equal 'done', noex.value ,'r63216' end end def test_select_leak # avoid malloc arena explosion from glibc and jemalloc: env = { 'MALLOC_ARENA_MAX' => '1', 'MALLOC_ARENA_TEST' => '1', 'MALLOC_CONF' => 'narenas:1', } assert_no_memory_leak([env], <<-"end;", <<-"end;", rss: true, timeout: 60) r, w = IO.pipe rset = [r] wset = [w] exc = StandardError.new(-"select used to leak on exception") exc.set_backtrace([]) Thread.new { IO.select(rset, wset, nil, 0) }.join end; th = Thread.new do Thread.handle_interrupt(StandardError => :on_blocking) do begin IO.select(rset, wset) rescue retry end while true end end 50_000.times do Thread.pass until th.stop? th.raise(exc) end th.kill th.join end; end def test_external_encoding_index IO.pipe {|r, w| assert_raise(TypeError) {Marshal.dump(r)} assert_raise(TypeError) {Marshal.dump(w)} } end end