summaryrefslogtreecommitdiff
path: root/test/ruby/test_io.rb
diff options
context:
space:
mode:
Diffstat (limited to 'test/ruby/test_io.rb')
-rw-r--r--test/ruby/test_io.rb2557
1 files changed, 2155 insertions, 402 deletions
diff --git a/test/ruby/test_io.rb b/test/ruby/test_io.rb
index 58cae7e291..1adf47ac51 100644
--- a/test/ruby/test_io.rb
+++ b/test/ruby/test_io.rb
@@ -1,29 +1,33 @@
# 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'
-require_relative 'envutil'
class TestIO < Test::Unit::TestCase
- def have_close_on_exec?
- begin
+ module Feature
+ def have_close_on_exec?
$stdin.close_on_exec?
true
rescue NotImplementedError
false
end
- end
- def have_nonblock?
- IO.method_defined?("nonblock=")
+ 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
@@ -45,8 +49,8 @@ class TestIO < Test::Unit::TestCase
end
flunk("timeout") unless wt.join(10) && rt.join(10)
ensure
- w.close unless !w || w.closed?
- r.close unless !r || r.closed?
+ w&.close
+ r&.close
(wt.kill; wt.join) if wt
(rt.kill; rt.join) if rt
raise we if we
@@ -58,8 +62,8 @@ class TestIO < Test::Unit::TestCase
begin
yield r, w
ensure
- r.close unless r.closed?
- w.close unless w.closed?
+ r.close
+ w.close
end
end
@@ -80,12 +84,17 @@ class TestIO < Test::Unit::TestCase
}
end
- def trapping_usr1
- @usr1_rcvd = 0
- trap(:USR1) { @usr1_rcvd += 1 }
- yield
+ 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(:USR1, "DEFAULT")
+ trap(:USR2, "DEFAULT")
+ w&.close
+ r&.close
end
def test_pipe
@@ -104,6 +113,65 @@ class TestIO < Test::Unit::TestCase
].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|
@@ -119,13 +187,13 @@ class TestIO < Test::Unit::TestCase
assert_equal("abc", r.read)
end
].each{|thr| thr.join}
- assert(!r.closed?)
- assert(w.closed?)
+ assert_not_predicate(r, :closed?)
+ assert_predicate(w, :closed?)
:foooo
}
assert_equal(:foooo, ret)
- assert(x[0].closed?)
- assert(x[1].closed?)
+ assert_predicate(x[0], :closed?)
+ assert_predicate(x[1], :closed?)
end
def test_pipe_block_close
@@ -136,13 +204,25 @@ class TestIO < Test::Unit::TestCase
r.close if (i&1) == 0
w.close if (i&2) == 0
}
- assert(x[0].closed?)
- assert(x[1].closed?)
+ assert_predicate(x[0], :closed?)
+ assert_predicate(x[1], :closed?)
}
end
def test_gets_rs
- # default_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
@@ -152,8 +232,9 @@ class TestIO < Test::Unit::TestCase
assert_nil r.gets
r.close
end)
+ end
- # nil
+ def test_gets_rs_nil
pipe(proc do |w|
w.print "a\n\nb\n\n"
w.close
@@ -162,8 +243,9 @@ class TestIO < Test::Unit::TestCase
assert_nil r.gets("")
r.close
end)
+ end
- # "\377"
+ def test_gets_rs_377
pipe(proc do |w|
w.print "\377xyz"
w.close
@@ -172,8 +254,9 @@ class TestIO < Test::Unit::TestCase
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
@@ -185,6 +268,68 @@ class TestIO < Test::Unit::TestCase
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\n", r.gets(nil, chomp: true), "[Bug #18770]")
+ 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"
@@ -205,6 +350,19 @@ class TestIO < Test::Unit::TestCase
end)
end
+ def test_ungetc_with_seek
+ make_tempfile {|t|
+ t.open
+ t.write('0123456789')
+ t.rewind
+
+ t.ungetc('a')
+ t.seek(2, :SET)
+
+ assert_equal('2', t.getc)
+ }
+ end
+
def test_ungetbyte
make_tempfile {|t|
t.open
@@ -228,6 +386,19 @@ class TestIO < Test::Unit::TestCase
}
end
+ def test_ungetbyte_with_seek
+ make_tempfile {|t|
+ t.open
+ t.write('0123456789')
+ t.rewind
+
+ t.ungetbyte('a'.ord)
+ t.seek(2, :SET)
+
+ assert_equal('2'.ord, t.getbyte)
+ }
+ end
+
def test_each_byte
pipe(proc do |w|
w << "abc def"
@@ -249,6 +420,24 @@ class TestIO < Test::Unit::TestCase
}
end
+ def test_each_byte_closed
+ pipe(proc do |w|
+ w << "abc def"
+ w.close
+ end, proc do |r|
+ assert_raise(IOError) do
+ r.each_byte {|byte| r.close if byte == 32 }
+ end
+ end)
+ make_tempfile {|t|
+ File.open(t, 'rt') {|f|
+ assert_raise(IOError) do
+ f.each_byte {|c| f.close if c == 10}
+ end
+ }
+ }
+ end
+
def test_each_codepoint
make_tempfile {|t|
bug2959 = '[ruby-core:28650]'
@@ -260,16 +449,39 @@ class TestIO < Test::Unit::TestCase
}
end
- def test_codepoints
+ def test_each_codepoint_closed
+ pipe(proc do |w|
+ w.print("abc def")
+ w.close
+ end, proc do |r|
+ assert_raise(IOError) do
+ r.each_codepoint {|c| r.close if c == 32}
+ end
+ end)
make_tempfile {|t|
- bug2959 = '[ruby-core:28650]'
- a = ""
File.open(t, 'rt') {|f|
- assert_warn(/deprecated/) {
- f.codepoints {|c| a << c}
- }
+ assert_raise(IOError) do
+ f.each_codepoint {|c| f.close if c == 10}
+ end
}
- assert_equal("foo\nbar\nbaz\n", a, bug2959)
+ }
+ end
+
+ def test_each_codepoint_with_ungetc
+ bug21562 = '[ruby-core:123176] [Bug #21562]'
+ with_read_pipe("") {|p|
+ p.binmode
+ p.ungetc("aa")
+ a = ""
+ p.each_codepoint { |c| a << c }
+ assert_equal("aa", a, bug21562)
+ }
+ with_read_pipe("") {|p|
+ p.set_encoding("ascii-8bit", universal_newline: true)
+ p.ungetc("aa")
+ a = ""
+ p.each_codepoint { |c| a << c }
+ assert_equal("aa", a, bug21562)
}
end
@@ -278,67 +490,126 @@ class TestIO < Test::Unit::TestCase
path = t.path
t.close!
assert_raise(Errno::ENOENT, "[ruby-dev:33072]") do
- File.read(path, nil, nil, {})
+ File.read(path, nil, nil, **{})
end
end
- def test_copy_stream
+ def with_srccontent(content = "baz")
+ src = "src"
mkcdtmpdir {
+ File.open(src, "w") {|f| f << content }
+ yield src, content
+ }
+ end
- content = "foobar"
- File.open("src", "w") {|f| f << content }
- ret = IO.copy_stream("src", "dst")
+ 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_append_to_nonempty
+ with_srccontent("foobar") {|src, content|
+ preface = 'preface'
+ File.write('dst', preface)
+ File.open('dst', 'ab') do |dst|
+ ret = IO.copy_stream(src, dst)
+ assert_equal(content.bytesize, ret)
+ assert_equal(preface + content, File.read("dst"))
+ end
+ }
+ end
+
+ def test_copy_stream_smaller
+ with_srccontent {|src, content|
# overwrite by smaller file.
- content = "baz"
- File.open("src", "w") {|f| f << content }
- ret = IO.copy_stream("src", "dst")
+ 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"))
+ assert_equal(content, File.read(dst))
- ret = IO.copy_stream("src", "dst", 2)
+ ret = IO.copy_stream(src, dst, 2)
assert_equal(2, ret)
- assert_equal(content[0,2], File.read("dst"))
+ assert_equal(content[0,2], File.read(dst))
- ret = IO.copy_stream("src", "dst", 0)
+ ret = IO.copy_stream(src, dst, 0)
assert_equal(0, ret)
- assert_equal("", File.read("dst"))
+ assert_equal("", File.read(dst))
- ret = IO.copy_stream("src", "dst", nil, 1)
+ ret = IO.copy_stream(src, dst, nil, 1)
assert_equal(content.bytesize-1, ret)
- assert_equal(content[1..-1], File.read("dst"))
+ 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")
+ 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)
+ 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) }
+ assert_raise(IOError) { IO.copy_stream(src, w) }
}
+ }
+ end
+
+ def with_pipecontent
+ mkcdtmpdir {
+ yield "abc"
+ }
+ end
- pipe_content = "abc"
+ 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")
+ ret = IO.copy_stream(r, dst)
assert_equal(pipe_content.bytesize, ret)
- assert_equal(pipe_content, File.read("dst"))
+ assert_equal(pipe_content, File.read(dst))
}
+ }
+ end
- with_read_pipe("abc") {|r1|
+ 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
@@ -351,7 +622,7 @@ class TestIO < Test::Unit::TestCase
end)
}
- with_read_pipe("abc") {|r1|
+ with_read_pipe(pipe_content) {|r1|
assert_equal("a", r1.getc)
pipe(proc do |w2|
w2.sync = false
@@ -364,7 +635,7 @@ class TestIO < Test::Unit::TestCase
end)
}
- with_read_pipe("abc") {|r1|
+ with_read_pipe(pipe_content) {|r1|
assert_equal("a", r1.getc)
pipe(proc do |w2|
ret = IO.copy_stream(r1, w2)
@@ -375,7 +646,7 @@ class TestIO < Test::Unit::TestCase
end)
}
- with_read_pipe("abc") {|r1|
+ with_read_pipe(pipe_content) {|r1|
assert_equal("a", r1.getc)
pipe(proc do |w2|
ret = IO.copy_stream(r1, w2, 1)
@@ -386,7 +657,7 @@ class TestIO < Test::Unit::TestCase
end)
}
- with_read_pipe("abc") {|r1|
+ with_read_pipe(pipe_content) {|r1|
assert_equal("a", r1.getc)
pipe(proc do |w2|
ret = IO.copy_stream(r1, w2, 0)
@@ -411,24 +682,43 @@ class TestIO < Test::Unit::TestCase
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)
+ 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
+ omit "multiple threads already active" if Thread.list.size > 1
- if have_nonblock?
+ 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"
- break
+ omit "nonblocking IO for pipe is not implemented"
end
s = w2.syswrite("a" * 100000)
t = Thread.new { sleep 0.1; r2.read }
@@ -438,25 +728,51 @@ class TestIO < Test::Unit::TestCase
assert_equal("a" * s + "bc", t.value)
}
}
- end
+ }
+ 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
- bigcontent = "abc" * 123456
- File.open("bigsrc", "w") {|f| f << bigcontent }
- ret = IO.copy_stream("bigsrc", "bigdst")
+ 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
- File.unlink("bigdst")
- ret = IO.copy_stream("bigsrc", "bigdst", nil, 100)
+ 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
- File.unlink("bigdst")
- ret = IO.copy_stream("bigsrc", "bigdst", 30000, 100)
+ 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
- File.open("bigsrc") {|f|
+ 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)
@@ -468,56 +784,90 @@ class TestIO < Test::Unit::TestCase
assert_equal(bigcontent[30, 40], File.read("bigdst"))
assert_equal(0, f.pos)
rescue NotImplementedError
- #skip "pread(2) is not implemtented."
+ #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) }
+ assert_raise(IOError) { IO.copy_stream(src, w) }
}
+ }
+ end
- megacontent = "abc" * 1234567
- File.open("megasrc", "w") {|f| f << megacontent }
+ def with_megacontent
+ yield "abc" * 1234567
+ end
- if have_nonblock?
+ 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"
+ omit "nonblocking IO for pipe is not implemented"
end
t1 = Thread.new { w1 << megacontent; w1.close }
t2 = Thread.new { r2.read }
- ret = IO.copy_stream(r1, w2)
- assert_equal(megacontent.bytesize, ret)
- w2.close
- t1.join
- assert_equal(megacontent, t2.value)
+ 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
+ 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 }
- ret = IO.copy_stream(r1, w2)
- assert_equal(megacontent.bytesize, ret)
- w2.close
- t1.join
- assert_equal(megacontent, t2.value)
+ 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|
- t = Thread.new { r.read }
- ret = IO.copy_stream("megasrc", w)
- assert_equal(megacontent.bytesize, ret)
- w.close
- assert_equal(megacontent, t.value)
+ 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
@@ -536,7 +886,7 @@ class TestIO < Test::Unit::TestCase
assert_equal("bcd", r.read)
end)
rescue NotImplementedError
- skip "pread(2) is not implemtented."
+ omit "pread(2) is not implemtented."
end
}
end
@@ -552,12 +902,9 @@ class TestIO < Test::Unit::TestCase
end
def test_copy_stream_socket1
- mkcdtmpdir {
- content = "foobar"
- File.open("src", "w") {|f| f << content }
-
+ with_srccontent("foobar") {|src, content|
with_socketpair {|s1, s2|
- ret = IO.copy_stream("src", s1)
+ ret = IO.copy_stream(src, s1)
assert_equal(content.bytesize, ret)
s1.close
assert_equal(content, s2.read)
@@ -566,79 +913,87 @@ class TestIO < Test::Unit::TestCase
end if defined? UNIXSocket
def test_copy_stream_socket2
- mkcdtmpdir {
- bigcontent = "abc" * 123456
- File.open("bigsrc", "w") {|f| f << bigcontent }
-
+ with_bigsrc {|bigsrc, bigcontent|
with_socketpair {|s1, s2|
- t = Thread.new { s2.read }
- ret = IO.copy_stream("bigsrc", s1)
- assert_equal(bigcontent.bytesize, ret)
- s1.close
- result = t.value
+ 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
- mkcdtmpdir {
- bigcontent = "abc" * 123456
- File.open("bigsrc", "w") {|f| f << bigcontent }
-
+ with_bigsrc {|bigsrc, bigcontent|
with_socketpair {|s1, s2|
- t = Thread.new { s2.read }
- ret = IO.copy_stream("bigsrc", s1, 10000)
- assert_equal(10000, ret)
- s1.close
- result = t.value
+ 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
- mkcdtmpdir {
- bigcontent = "abc" * 123456
- File.open("bigsrc", "w") {|f| f << bigcontent }
+ if RUBY_PLATFORM =~ /mingw|mswin/
+ omit "pread(2) is not implemented."
+ end
- File.open("bigsrc") {|f|
+ with_bigsrc {|bigsrc, bigcontent|
+ File.open(bigsrc) {|f|
assert_equal(0, f.pos)
with_socketpair {|s1, s2|
- t = Thread.new { s2.read }
- ret = IO.copy_stream(f, s1, nil, 100)
- assert_equal(bigcontent.bytesize-100, ret)
- assert_equal(0, f.pos)
- s1.close
- result = t.value
+ 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
+ end
def test_copy_stream_socket5
- mkcdtmpdir {
- bigcontent = "abc" * 123456
- File.open("bigsrc", "w") {|f| f << bigcontent }
+ if RUBY_PLATFORM =~ /mingw|mswin/
+ omit "pread(2) is not implemented."
+ end
- File.open("bigsrc") {|f|
+ 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|
- t = Thread.new { s2.read }
- ret = IO.copy_stream(f, s1)
- assert_equal(bigcontent.bytesize-100, ret)
- assert_equal(bigcontent.length, f.pos)
- s1.close
- result = t.value
+ 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
+ end
def test_copy_stream_socket6
+ if RUBY_PLATFORM =~ /mingw|mswin/
+ omit "pread(2) is not implemented."
+ end
+
mkcdtmpdir {
megacontent = "abc" * 1234567
File.open("megasrc", "w") {|f| f << megacontent }
@@ -647,19 +1002,26 @@ class TestIO < Test::Unit::TestCase
begin
s1.nonblock = true
rescue Errno::EBADF
- skip "nonblocking IO for pipe is not implemented"
+ omit "nonblocking IO for pipe is not implemented"
end
- t = Thread.new { s2.read }
- ret = IO.copy_stream("megasrc", s1)
- assert_equal(megacontent.bytesize, ret)
- s1.close
- result = t.value
+ 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
+ end
def test_copy_stream_socket7
+ if RUBY_PLATFORM =~ /mingw|mswin/
+ omit "pread(2) is not implemented."
+ end
+
+ GC.start
mkcdtmpdir {
megacontent = "abc" * 1234567
File.open("megasrc", "w") {|f| f << megacontent }
@@ -668,31 +1030,32 @@ class TestIO < Test::Unit::TestCase
begin
s1.nonblock = true
rescue Errno::EBADF
- skip "nonblocking IO for pipe is not implemented"
+ omit "nonblocking IO for pipe is not implemented"
end
- trapping_usr1 do
+ trapping_usr2 do |rd|
nr = 30
begin
pid = fork do
s1.close
IO.select([s2])
- Process.kill(:USR1, Process.ppid)
- s2.read
+ 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, @usr1_rcvd)
+ assert_equal(1, rd.read(4).unpack1('L'))
ensure
s1.close
_, status = Process.waitpid2(pid) if pid
end
- assert status.success?, status.inspect
+ assert_predicate(status, :success?)
end
}
}
- end if defined? UNIXSocket and IO.method_defined?("nonblock=")
+ end
def test_copy_stream_strio
src = StringIO.new("abcd")
@@ -770,6 +1133,101 @@ class TestIO < Test::Unit::TestCase
}
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_dup_buffer
+ bug21131 = '[ruby-core:120961] [Bug #21131]'
+ mkcdtmpdir do
+ dst_class = Class.new do
+ def initialize(&block)
+ @block = block
+ end
+
+ def write(data)
+ @block.call(data.dup)
+ data.bytesize
+ end
+ end
+
+ rng = Random.new(42)
+ body = Tempfile.new("ruby-bug", binmode: true)
+ body.write(rng.bytes(16_385))
+ body.rewind
+
+ payload = []
+ IO.copy_stream(body, dst_class.new{payload << it})
+ body.rewind
+ assert_equal(body.read, payload.join, bug21131)
+ ensure
+ body&.close
+ end
+ 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
@@ -839,11 +1297,12 @@ class TestIO < Test::Unit::TestCase
w.write "zz"
src = StringIO.new("abcd")
IO.copy_stream(src, w)
- t = Thread.new {
+ t1 = Thread.new {
w.close
}
- assert_equal("zzabcd", r.read)
- t.join
+ t2 = Thread.new { r.read }
+ _, result = assert_join_threads([t1, t2])
+ assert_equal("zzabcd", result)
}
end
@@ -917,24 +1376,36 @@ class TestIO < Test::Unit::TestCase
}
end
- def safe_4
- t = Thread.new do
- $SAFE = 4
- yield
- end
- unless t.join(10)
- t.kill
- flunk("timeout in safe_4")
- 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
- f = IO.popen([ruby] + args, 'r+')
+ 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
@@ -955,6 +1426,70 @@ class TestIO < Test::Unit::TestCase
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
@@ -965,44 +1500,49 @@ class TestIO < Test::Unit::TestCase
def test_dup
ruby do |f|
- 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)
+ 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
- ruby('-e', <<-'End') {|f|
- ok = 0
+ opts = {}
+ opts[:rlimit_nofile] = 1024 if defined?(Process::RLIMIT_NOFILE)
+ assert_separately([], <<-'End', **opts)
a = []
- begin
+ assert_raise(Errno::EMFILE, Errno::ENFILE, Errno::ENOMEM) do
loop {a << IO.pipe}
- rescue Errno::EMFILE, Errno::ENFILE, Errno::ENOMEM
- ok += 1
end
- print "no" if ok != 1
- begin
+ assert_raise(Errno::EMFILE, Errno::ENFILE, Errno::ENOMEM) do
loop {a << [a[-1][0].dup, a[-1][1].dup]}
- rescue Errno::EMFILE, Errno::ENFILE, Errno::ENOMEM
- ok += 1
end
- print "no" if ok != 2
- print "ok"
End
- assert_equal("ok", f.read)
- }
+ end
+
+ def test_dup_timeout
+ with_pipe do |r, w|
+ r.timeout = 0.1
+ r2 = r.dup
+ assert_equal(0.1, r2.timeout)
+ ensure
+ r2&.close
+ end
end
def test_inspect
with_pipe do |r, w|
assert_match(/^#<IO:fd \d+>$/, r.inspect)
- assert_raise(SecurityError) do
- safe_4 { r.inspect }
- end
+ r.freeze
+ assert_match(/^#<IO:fd \d+>$/, r.inspect)
end
end
@@ -1051,6 +1591,13 @@ class TestIO < Test::Unit::TestCase
end)
end
+ def test_readpartial_zero_size
+ File.open(IO::NULL) do |r|
+ assert_empty(r.readpartial(0, s = "01234567"))
+ assert_empty(s)
+ end
+ end
+
def test_readpartial_buffer_error
with_pipe do |r, w|
s = ""
@@ -1060,7 +1607,7 @@ class TestIO < Test::Unit::TestCase
t.value
assert_equal("", s)
end
- end
+ end if /cygwin/ !~ RUBY_PLATFORM
def test_read
pipe(proc do |w|
@@ -1096,6 +1643,13 @@ class TestIO < Test::Unit::TestCase
end)
end
+ def test_read_zero_size
+ File.open(IO::NULL) do |r|
+ assert_empty(r.read(0, s = "01234567"))
+ assert_empty(s)
+ end
+ end
+
def test_read_buffer_error
with_pipe do |r, w|
s = ""
@@ -1105,10 +1659,17 @@ class TestIO < Test::Unit::TestCase
t.value
assert_equal("", s)
end
- 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
- skip "IO#write_nonblock is not supported on file/pipe." if /mswin|bccwin|mingw/ =~ RUBY_PLATFORM
pipe(proc do |w|
w.write_nonblock(1)
w.close
@@ -1118,7 +1679,6 @@ class TestIO < Test::Unit::TestCase
end
def test_read_nonblock_with_not_empty_buffer
- skip "IO#read_nonblock is not supported on file/pipe." if /mswin|bccwin|mingw/ =~ RUBY_PLATFORM
with_pipe {|r, w|
w.write "foob"
w.close
@@ -1127,9 +1687,45 @@ class TestIO < Test::Unit::TestCase
}
end
+ def test_read_nonblock_zero_size
+ File.open(IO::NULL) do |r|
+ assert_empty(r.read_nonblock(0, s = "01234567"))
+ assert_empty(s)
+ end
+ end
+
+ def test_read_nonblock_file
+ make_tempfile do |path|
+ File.open(path, 'r') do |file|
+ file.read_nonblock(4)
+ end
+ end
+ end
+
+ def test_write_nonblock_file
+ make_tempfile do |path|
+ File.open(path, 'w') do |file|
+ file.write_nonblock("Ruby")
+ end
+ end
+ end
+
+ def test_explicit_path
+ io = IO.for_fd(0, path: "Fake Path", autoclose: false)
+ assert_match %r"Fake Path", io.inspect
+ assert_equal "Fake Path", io.path
+ 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
- return if !have_nonblock?
- skip "IO#read_nonblock is not supported on file/pipe." if /mswin|bccwin|mingw/ =~ RUBY_PLATFORM
with_pipe {|r, w|
begin
r.read_nonblock 4096
@@ -1137,11 +1733,46 @@ class TestIO < Test::Unit::TestCase
assert_kind_of(IO::WaitReadable, $!)
end
}
- 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
+ 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
- return if !have_nonblock?
- skip "IO#write_nonblock is not supported on file/pipe." if /mswin|bccwin|mingw/ =~ RUBY_PLATFORM
with_pipe {|r, w|
begin
loop {
@@ -1151,7 +1782,25 @@ class TestIO < Test::Unit::TestCase
assert_kind_of(IO::WaitWritable, $!)
end
}
- 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|
@@ -1168,6 +1817,9 @@ class TestIO < Test::Unit::TestCase
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
@@ -1175,15 +1827,21 @@ class TestIO < Test::Unit::TestCase
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_close_read_security_error
- with_pipe do |r, w|
- assert_raise(SecurityError) do
- safe_4 { 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
@@ -1199,41 +1857,64 @@ class TestIO < Test::Unit::TestCase
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_security_error
+ def test_close_write_non_readable
with_pipe do |r, w|
- assert_raise(SecurityError) do
- safe_4 { r.close_write }
+ assert_raise(IOError) do
+ r.close_write
end
end
end
- def test_close_write_non_readable
- with_pipe do |r, w|
- assert_raise(IOError) do
- r.close_write
+ 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
- r, w = IO.pipe
- assert_equal(nil, r.pid)
- assert_equal(nil, w.pid)
-
- 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)
- pipe.close
+ 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
@@ -1254,40 +1935,182 @@ class TestIO < Test::Unit::TestCase
def test_set_lineno
make_tempfile {|t|
- ruby("-e", <<-SRC, t.path) do |f|
+ assert_separately(["-", t.path], <<-SRC)
open(ARGV[0]) do |f|
- p $.
- f.gets; p $.
- f.gets; p $.
- f.lineno = 1000; p $.
- f.gets; p $.
- f.gets; p $.
- f.rewind; p $.
- f.gets; p $.
- f.gets; p $.
- f.gets; p $.
- f.gets; p $.
+ 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
- assert_equal("0,1,2,2,1001,1001,1001,1,2,3,3", f.read.chomp.gsub("\n", ","))
+ }
+ 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_readline_bad_param_raises
+ File.open(__FILE__) do |f|
+ assert_raise(TypeError) do
+ f.readline Object.new
end
+ end
- 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)
+ File.open(__FILE__) do |f|
+ assert_raise(TypeError) do
+ f.readline 1, 2
+ end
+ end
+ end
+
+ def test_readline_raises
+ File.open(__FILE__) do |f|
+ assert_equal File.read(__FILE__), f.readline(nil)
+ assert_raise(EOFError) do
+ f.readline
+ end
+ end
+ end
+
+ def test_readline_separators
+ File.open(__FILE__) do |f|
+ line = f.readline("def")
+ assert_equal File.read(__FILE__)[/\A.*?def/m], line
+ end
+
+ File.open(__FILE__) do |f|
+ line = f.readline("def", chomp: true)
+ assert_equal File.read(__FILE__)[/\A.*?(?=def)/m], line
+ end
+ end
+
+ def test_readline_separators_limits
+ t = Tempfile.open("readline_limit")
+ str = "#" * 50
+ sep = "def"
+
+ t.write str
+ t.write sep
+ t.write str
+ t.flush
+
+ # over limit
+ File.open(t.path) do |f|
+ line = f.readline sep, str.bytesize
+ assert_equal(str, line)
+ end
+
+ # under limit
+ File.open(t.path) do |f|
+ line = f.readline(sep, str.bytesize + 5)
+ assert_equal(str + sep, line)
+ end
+
+ # under limit + chomp
+ File.open(t.path) do |f|
+ line = f.readline(sep, str.bytesize + 5, chomp: true)
+ assert_equal(str, line)
+ end
+ ensure
+ t&.close!
+ end
+
+ def test_readline_limit_without_separator
+ t = Tempfile.open("readline_limit")
+ str = "#" * 50
+ sep = "\n"
+
+ t.write str
+ t.write sep
+ t.write str
+ t.flush
+
+ # over limit
+ File.open(t.path) do |f|
+ line = f.readline str.bytesize
+ assert_equal(str, line)
+ end
+
+ # under limit
+ File.open(t.path) do |f|
+ line = f.readline(str.bytesize + 5)
+ assert_equal(str + sep, line)
+ end
+
+ # under limit + chomp
+ File.open(t.path) do |f|
+ line = f.readline(str.bytesize + 5, chomp: true)
+ assert_equal(str, line)
+ end
+ ensure
+ t&.close!
+ end
+
+ def test_readline_chomp_true
+ File.open(__FILE__) do |f|
+ line = f.readline(chomp: true)
+ assert_equal File.readlines(__FILE__).first.chomp, line
+ end
+ end
+
+ def test_readline_incompatible_rs
+ first_line = File.open(__FILE__, &:gets).encode("utf-32le")
+ File.open(__FILE__, encoding: "utf-8:utf-32le") {|f|
+ assert_equal first_line, f.readline
+ assert_raise(ArgumentError) {f.readline("\0")}
}
end
- def test_readline
+ def test_readline_limit_nonascii
+ mkcdtmpdir do
+ i = 0
+
+ File.open("text#{i+=1}", "w+:utf-8") do |f|
+ f.write("Test\nok\u{bf}ok\n")
+ f.rewind
+
+ assert_equal("Test\nok\u{bf}", f.readline("\u{bf}"))
+ assert_equal("ok\n", f.readline("\u{bf}"))
+ end
+
+ File.open("text#{i+=1}", "w+b:utf-32le") do |f|
+ f.write("0123456789")
+ f.rewind
+
+ assert_equal(4, f.readline(4).bytesize)
+ assert_equal(4, f.readline(3).bytesize)
+ end
+
+ File.open("text#{i+=1}", "w+:utf-8:utf-32le") do |f|
+ f.write("0123456789")
+ f.rewind
+
+ assert_equal(4, f.readline(4).bytesize)
+ assert_equal(4, f.readline(3).bytesize)
+ end
+ end
+ end
+
+ def test_set_lineno_readline
pipe(proc do |w|
w.puts "foo"
w.puts "bar"
@@ -1315,8 +2138,7 @@ class TestIO < Test::Unit::TestCase
end)
end
- def test_lines
- verbose, $VERBOSE = $VERBOSE, nil
+ def test_each_line
pipe(proc do |w|
w.puts "foo"
w.puts "bar"
@@ -1324,20 +2146,31 @@ class TestIO < Test::Unit::TestCase
w.close
end, proc do |r|
e = nil
- assert_warn(/deprecated/) {
- e = r.lines
+ assert_warn('') {
+ e = r.each_line
}
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
+
+ pipe(proc do |w|
+ w.write "foo\n"
+ w.close
+ end, proc do |r|
+ assert_equal(["foo\n"], r.each_line(nil, chomp: true).to_a, "[Bug #18770]")
+ end)
+
+ pipe(proc do |w|
+ w.write "foo\n"
+ w.close
+ end, proc do |r|
+ assert_equal(["fo", "o\n"], r.each_line(nil, 2, chomp: true).to_a, "[Bug #18770]")
+ end)
end
- def test_bytes
- verbose, $VERBOSE = $VERBOSE, nil
+ def test_each_byte2
pipe(proc do |w|
w.binmode
w.puts "foo"
@@ -1346,20 +2179,17 @@ class TestIO < Test::Unit::TestCase
w.close
end, proc do |r|
e = nil
- assert_warn(/deprecated/) {
- e = r.bytes
+ assert_warn('') {
+ e = r.each_byte
}
(%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
+ def test_each_char2
pipe(proc do |w|
w.puts "foo"
w.puts "bar"
@@ -1367,16 +2197,14 @@ class TestIO < Test::Unit::TestCase
w.close
end, proc do |r|
e = nil
- assert_warn(/deprecated/) {
- e = r.chars
+ assert_warn('') {
+ e = r.each_char
}
(%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
@@ -1410,7 +2238,6 @@ class TestIO < Test::Unit::TestCase
end
def test_close_on_exec
- skip "IO\#close_on_exec is not implemented." unless have_close_on_exec?
ruby do |f|
assert_equal(true, f.close_on_exec?)
f.close_on_exec = false
@@ -1438,19 +2265,10 @@ class TestIO < Test::Unit::TestCase
w.close_on_exec = false
assert_equal(false, w.close_on_exec?)
end
- end
-
- def test_close_security_error
- with_pipe do |r, w|
- assert_raise(SecurityError) do
- safe_4 { r.close }
- end
- end
- 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)
@@ -1465,7 +2283,7 @@ class TestIO < Test::Unit::TestCase
end
def test_pos_with_getc
- bug6179 = '[ruby-core:43497]'
+ _bug6179 = '[ruby-core:43497]'
make_tempfile {|t|
["", "t", "b"].each do |mode|
open(t.path, "w#{mode}") do |f|
@@ -1488,6 +2306,25 @@ class TestIO < Test::Unit::TestCase
}
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|
@@ -1511,6 +2348,34 @@ class TestIO < Test::Unit::TestCase
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
@@ -1531,6 +2396,34 @@ class TestIO < Test::Unit::TestCase
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
@@ -1580,6 +2473,14 @@ class TestIO < Test::Unit::TestCase
end)
end
+ def test_sysread_with_negative_length
+ make_tempfile {|t|
+ open(t.path) do |f|
+ assert_raise(ArgumentError) { f.sysread(-1) }
+ end
+ }
+ end
+
def test_flag
make_tempfile {|t|
assert_raise(ArgumentError) do
@@ -1589,6 +2490,10 @@ class TestIO < Test::Unit::TestCase
assert_raise(ArgumentError) do
open(t.path, "rr") { }
end
+
+ assert_raise(ArgumentError) do
+ open(t.path, "rbt") { }
+ end
}
end
@@ -1664,12 +2569,12 @@ class TestIO < Test::Unit::TestCase
t.close
rescue Errno::EBADF
end
- skip "expect IO object was GC'ed but not recycled yet"
+ omit "expect IO object was GC'ed but not recycled yet"
rescue WeakRef::RefError
assert_raise(Errno::EBADF, feature2250) {t.close}
end
ensure
- t.unlink
+ t&.close!
end
def test_autoclose_false_closed_by_finalizer
@@ -1680,12 +2585,12 @@ class TestIO < Test::Unit::TestCase
begin
w.close
t.close
- skip "expect IO object was GC'ed but not recycled yet"
+ omit "expect IO object was GC'ed but not recycled yet"
rescue WeakRef::RefError
assert_nothing_raised(Errno::EBADF, feature2250) {t.close}
end
ensure
- t.unlink
+ t.close!
end
def test_open_redirect
@@ -1699,22 +2604,35 @@ class TestIO < Test::Unit::TestCase
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)
+ def test_open_redirect_keyword
+ o = Object.new
+ def o.to_open(**kw); kw; end
+ assert_equal({:a=>1}, open(o, a: 1))
+
+ assert_raise(ArgumentError) { open(o, {a: 1}) }
+
+ class << o
+ remove_method(:to_open)
+ end
+ def o.to_open(kw); kw; end
+ assert_equal({:a=>1}, open(o, a: 1))
+ assert_equal({:a=>1}, open(o, {a: 1}))
+ end
+
+ def test_path_with_pipe
+ mkcdtmpdir do
+ cmd = "|echo foo"
+ assert_file.not_exist?(cmd)
+
+ pipe_errors = [Errno::ENOENT, Errno::EINVAL, Errno::EACCES, Errno::EPERM]
+ assert_raise(*pipe_errors) { open(cmd, "r+") }
+ assert_raise(*pipe_errors) { IO.read(cmd) }
+ assert_raise(*pipe_errors) { IO.foreach(cmd) {|x| assert false } }
end
end
def test_reopen
make_tempfile {|t|
- with_pipe do |r, w|
- assert_raise(SecurityError) do
- safe_4 { r.reopen(t.path) }
- end
- end
-
open(__FILE__) do |f|
f.gets
assert_nothing_raised {
@@ -1754,17 +2672,26 @@ class TestIO < Test::Unit::TestCase
def test_reopen_inherit
mkcdtmpdir {
- system(EnvUtil.rubybin, '-e', <<"End")
+ 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
+ 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|
@@ -1803,6 +2730,17 @@ End
}
end
+ def test_reopen_binmode
+ f1 = File.open(__FILE__)
+ f2 = File.open(__FILE__)
+ f1.binmode
+ f1.reopen(f2)
+ assert_not_operator(f1, :binmode?)
+ ensure
+ f2.close
+ f1.close
+ end
+
def make_tempfile_for_encoding
t = make_tempfile
open(t.path, "rb+:utf-8") {|f| f.puts "\u7d05\u7389bar\n"}
@@ -1812,7 +2750,7 @@ End
t
end
ensure
- t.close(true) if t and block_given?
+ t&.close(true) if block_given?
end
def test_reopen_encoding
@@ -1833,6 +2771,16 @@ End
}
end
+ def test_reopen_encoding_from_io
+ f1 = File.open(__FILE__, "rb:UTF-16LE")
+ f2 = File.open(__FILE__, "r:UTF-8")
+ f1.reopen(f2)
+ assert_equal(Encoding::UTF_8, f1.external_encoding)
+ ensure
+ f2.close
+ f1.close
+ end
+
def test_reopen_opt_encoding
feature7103 = '[ruby-core:47806]'
make_tempfile_for_encoding {|t|
@@ -1852,26 +2800,53 @@ 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)
+ 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_reopen_ivar
+ assert_ruby_status([], "#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+ f = File.open(IO::NULL)
+ f.instance_variable_set(:@foo, 42)
+ f.reopen(STDIN)
+ f.instance_variable_defined?(:@foo)
+ f.instance_variable_get(:@foo)
+ end;
+ end
+ def test_foreach
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 }
+ 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 }
+ 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 }
+ IO.foreach(t.path, :open_args => ["r"]) {|x| a << x }
assert_equal(["foo\n", "bar\n", "baz\n"], a)
a = []
@@ -1889,13 +2864,18 @@ End
bug = '[ruby-dev:31525]'
assert_raise(ArgumentError, bug) {IO.foreach}
+ assert_raise(ArgumentError, "[Bug #18767] [ruby-core:108499]") {IO.foreach(__FILE__, 0){}}
+
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]'
- e = assert_raise(IOError, bug6054) {IO.foreach(t.path, mode:"w").next}
- assert_match(/not opened for reading/, e.message, bug6054)
+ assert_raise_with_message(IOError, /not opened for reading/, bug6054) do
+ IO.foreach(t.path, mode:"w").next
+ end
+
+ assert_raise(ArgumentError, "[Bug #18771] [ruby-core:108503]") {IO.foreach(t, "\n", 10, true){}}
}
end
@@ -1905,6 +2885,7 @@ End
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))
+ assert_raise(ArgumentError, "[Bug #18771] [ruby-core:108503]") {IO.readlines(t, "\n", 10, true){}}
}
end
@@ -1926,11 +2907,13 @@ End
end
def test_print_separators
- $, = ':'
- $\ = "\n"
+ assert_deprecated_warning(/non-nil '\$,'/) {$, = ":"}
+ assert_raise(TypeError) {$, = 1}
+ assert_deprecated_warning(/non-nil '\$\\'/) {$\ = "\n"}
+ assert_raise(TypeError) {$/ = 1}
pipe(proc do |w|
w.print('a')
- w.print('a','b','c')
+ EnvUtil.suppress_warning {w.print('a','b','c')}
w.close
end, proc do |r|
assert_equal("a\n", r.gets)
@@ -1967,6 +2950,36 @@ End
end)
end
+ def test_puts_parallel
+ omit "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_deprecated_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)
@@ -1982,13 +2995,21 @@ End
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|
@@ -1999,6 +3020,15 @@ End
f.close
assert_equal("FOO\n", File.read(t.path))
+
+ fd = IO.sysopen(t.path)
+ %w[w r+ w+ a+].each do |mode|
+ assert_raise(Errno::EINVAL, "#{mode} [ruby-dev:38571]") {IO.new(fd, mode)}
+ end
+ f = IO.new(fd, "r")
+ data = f.read
+ f.close
+ assert_equal("FOO\n", data)
}
end
@@ -2016,7 +3046,16 @@ End
end
def test_new_with_block
- assert_in_out_err([], "r, w = IO.pipe; IO.new(r) {}", [], /^.+$/)
+ 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
@@ -2042,6 +3081,9 @@ End
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))
+
+ assert_raise(ArgumentError) { File.read(t.path, -1) }
+ assert_raise(ArgumentError) { File.read(t.path, 1, -1) }
}
end
@@ -2051,8 +3093,6 @@ End
def test_nofollow
# O_NOFOLLOW is not standard.
- return if /freebsd|linux/ !~ RUBY_PLATFORM
- return unless defined? File::NOFOLLOW
mkcdtmpdir {
open("file", "w") {|f| f << "content" }
begin
@@ -2067,14 +3107,7 @@ End
File.foreach("slnk", :open_args=>[File::RDONLY|File::NOFOLLOW]) {}
}
}
- end
-
- def test_tainted
- make_tempfile {|t|
- assert(File.read(t.path, 4).tainted?, '[ruby-dev:38826]')
- assert(File.open(t.path) {|f| f.read(4)}.tainted?, '[ruby-dev:38826]')
- }
- end
+ end if /freebsd|linux/ =~ RUBY_PLATFORM and defined? File::NOFOLLOW
def test_binmode_after_closed
make_tempfile {|t|
@@ -2082,16 +3115,24 @@ End
}
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 = %q{\
+ src = "#{<<~"begin;"}\n#{<<~'end;'}"
+ begin;
t = Thread.new { sleep 3 }
Thread.new {sleep 1; t.kill; p 'hi!'}
t.join
- }.gsub(/^\s+/, '')
+ end;
10.times.map do
Thread.start do
- assert_in_out_err([], src) {|stdout, stderr|
+ assert_in_out_err([], src, timeout: 20) {|stdout, stderr|
assert_no_match(/hi.*hi/, stderr.join, bug3585)
}
end
@@ -2099,39 +3140,51 @@ End
end
def test_flush_in_finalizer1
- require 'tempfile'
bug3910 = '[ruby-dev:42341]'
- Tempfile.open("bug3910") {|t|
+ 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.unlink
+ t
}
ensure
- GC.start
+ 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
- require 'tempfile'
bug3910 = '[ruby-dev:42341]'
- Tempfile.open("bug3910") {|t|
+ Tempfile.create("bug3910") {|t|
path = t.path
t.close
- 1.times do
- io = open(path,"w")
- io.print "hoge"
- end
- assert_nothing_raised(TypeError, bug3910) do
- GC.start
+ 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.unlink
}
end
@@ -2157,13 +3210,56 @@ 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|
- assert_equal(t.advise(adv, offset, len), nil)
+ 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
@@ -2190,10 +3286,10 @@ 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|
+ %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(NotImplementedError, feature4204) { t.advise(adv, offset, len) }
+ assert_raise_with_message(NotImplementedError, /#{Regexp.quote(adv.inspect)}/, feature4204) { t.advise(adv, offset, len) }
end
end
end
@@ -2201,9 +3297,7 @@ End
end
def test_fcntl_lock_linux
- return if /x86_64-linux/ !~ RUBY_PLATFORM # A binary form of struct flock depend on platform
-
- pad=0
+ pad = 0
Tempfile.create(self.class.name) do |f|
r, w = IO.pipe
pid = fork do
@@ -2231,11 +3325,10 @@ End
Process.kill :TERM, pid
Process.waitpid2(pid)
end
- 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
- return if /freebsd/ !~ RUBY_PLATFORM # A binary form of struct flock depend on platform
-
start = 12
len = 34
sysid = 0
@@ -2266,7 +3359,7 @@ End
Process.kill :TERM, pid
Process.waitpid2(pid)
end
- 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|
@@ -2280,7 +3373,6 @@ End
end
def test_cross_thread_close_fd
- skip "cross thread close causes hung-up if pipe." if /mswin|bccwin|mingw/ =~ RUBY_PLATFORM
with_pipe do |r,w|
read_thread = Thread.new do
begin
@@ -2298,25 +3390,46 @@ End
end
def test_cross_thread_close_stdio
- with_pipe do |r,w|
- pid = fork do
+ omit "[Bug #18613]" if /freebsd/ =~ RUBY_PLATFORM
+
+ assert_separately([], <<-'end;')
+ IO.pipe do |r,w|
$stdin.reopen(r)
r.close
read_thread = Thread.new do
begin
$stdin.read(1)
- rescue => e
+ rescue IOError => e
e
end
end
sleep(0.1) until read_thread.stop?
$stdin.close
- read_thread.join
- exit(IOError === read_thread.value)
+ 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
- assert Process.waitpid2(pid)[1].success?
end
- rescue NotImplementedError
+ 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
@@ -2367,9 +3480,15 @@ End
assert_equal("\00f", File.read(path))
assert_equal(1, File.write(path, "f", 0, encoding: "UTF-8"))
assert_equal("ff", File.read(path))
+ File.write(path, "foo", Object.new => Object.new)
+ assert_equal("foo", File.read(path))
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"
@@ -2406,7 +3525,7 @@ End
threads << Thread.new {write_file.print(i)}
threads << Thread.new {read_file.read}
end
- threads.each {|t| t.join}
+ assert_join_threads(threads)
assert(true, "[ruby-core:37197]")
ensure
read_file.close
@@ -2432,34 +3551,31 @@ End
def test_cloexec
return unless defined? Fcntl::FD_CLOEXEC
open(__FILE__) {|f|
- assert(f.close_on_exec?)
+ assert_predicate(f, :close_on_exec?)
g = f.dup
begin
- assert(g.close_on_exec?)
+ assert_predicate(g, :close_on_exec?)
f.reopen(g)
- assert(f.close_on_exec?)
+ assert_predicate(f, :close_on_exec?)
ensure
g.close
end
g = IO.new(f.fcntl(Fcntl::F_DUPFD))
begin
- assert(g.close_on_exec?)
+ assert_predicate(g, :close_on_exec?)
ensure
g.close
end
}
IO.pipe {|r,w|
- assert(r.close_on_exec?)
- assert(w.close_on_exec?)
+ assert_predicate(r, :close_on_exec?)
+ assert_predicate(w, :close_on_exec?)
}
end
def test_ioctl_linux
- return if /linux/ !~ RUBY_PLATFORM
# Alpha, mips, sparc and ppc have an another ioctl request number scheme.
# So, hardcoded 0x80045200 may fail.
- return if /^i.?86|^x86_64/ !~ RUBY_PLATFORM
-
assert_nothing_raised do
File.open('/dev/urandom'){|f1|
entropy_count = ""
@@ -2476,21 +3592,24 @@ End
}
end
assert_equal(File.size(__FILE__), buf.unpack('i!')[0])
- end
+ end if /^(?:i.?86|x86_64)-linux/ =~ RUBY_PLATFORM
def test_ioctl_linux2
- return if /linux/ !~ RUBY_PLATFORM
- return if /^i.?86|^x86_64/ !~ RUBY_PLATFORM
-
- return unless system('tty', '-s') # stdin is not a terminal
- File.open('/dev/tty') { |f|
+ return unless STDIN.tty? # stdin is not a terminal
+ begin
+ f = File.open('/dev/tty')
+ rescue Errno::ENOENT, Errno::ENXIO => e
+ omit e.message
+ else
tiocgwinsz=0x5413
winsize=""
assert_nothing_raised {
f.ioctl(tiocgwinsz, winsize)
}
- }
- end
+ ensure
+ f&.close
+ end
+ end if /^(?:i.?86|x86_64)-linux/ =~ RUBY_PLATFORM
def test_setpos
mkcdtmpdir {
@@ -2541,72 +3660,99 @@ End
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)
+
+ 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)
- Thread.pass while th.alive?
th.join
end
assert_equal(data, buf, bug6099)
end
def test_readpartial_locktmp
- skip "nonblocking mode is not supported for pipe on this platform" if /mswin|bccwin|mingw/ =~ RUBY_PLATFORM
bug6099 = '[ruby-dev:45297]'
buf = " " * 100
data = "a" * 100
+ th = nil
with_pipe do |r,w|
- r.fcntl(Fcntl::F_SETFL, Fcntl::O_NONBLOCK)
+ r.nonblock = true
th = Thread.new {r.readpartial(100, buf)}
+
Thread.pass until th.stop?
- buf.replace("")
- assert_empty(buf, bug6099)
+
+ 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)
- Thread.pass while th.alive?
th.join
end
assert_equal(data, buf, bug6099)
- rescue RuntimeError # can't modify string; temporarily locked
end
def test_advise_pipe
# we don't know if other platforms have a real posix_fadvise()
- return if /linux/ !~ RUBY_PLATFORM
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) }
- assert_raise(Errno::ESPIPE, Errno::EINVAL) { w.advise(:willneed) }
+ assert_raise(Errno::ESPIPE, Errno::EINVAL) {
+ r.advise(:willneed) or omit "fadvise(2) is not implemented"
+ }
+ assert_raise(Errno::ESPIPE, Errno::EINVAL) {
+ w.advise(:willneed) or omit "fadvise(2) is not implemented"
+ }
end
- 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 = Tempfile.new("test_io")
+ w = []
assert_nothing_raised(RuntimeError, bug6764) do
+ buf = ''
File.open(t.path, "r") do |r|
- buf = ''
while yield(r, size, buf)
- w << buf
+ w << buf.dup
end
end
end
- w.close
- assert_equal(data, w.open.read, bug6764)
+ assert_equal(data, w.join(""), bug9847)
ensure
t.close!
- w.close!
end
def test_read_buffer_not_raise_shared_string_error
@@ -2653,23 +3799,630 @@ End
assert_normal_exit %q{
require "tempfile"
- # try to raise RLIM_NOFILE to >FD_SETSIZE
- # Unfortunately, ruby export FD_SETSIZE. then we assume it's 1024.
+ # 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+10)
- rescue =>e
- # Process::RLIMIT_NOFILE couldn't be raised. skip the test
+ 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")
+ (0...fd_setsize).map {|i|
+ tempfiles << Tempfile.create("test_io_select_with_many_files")
+ }
+
+ begin
+ IO.select(tempfiles)
+ ensure
+ tempfiles.each { |t|
+ t.close
+ File.unlink(t.path)
+ }
+ end
+ }, 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
+ omit "non-sparse file system"
+ rescue SystemCallError
+ else
+ assert_equal(0x1_0000_0000, pos, msg)
+ end
+ end;
+ rescue Timeout::Error
+ omit "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 do
+ File.mkfifo("fifo")
+ rescue NotImplementedError
+ else
+ 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
+ end
+
+ def test_open_fifo_restart_at_signal_intterupt
+ mkcdtmpdir do
+ File.mkfifo("fifo")
+ rescue NotImplementedError
+ else
+ wait = EnvUtil.apply_timeout_scale(0.1)
+ data = "writing to fifo"
+
+ # Do not use assert_separately, because reading from stdin
+ # prevents to reproduce [Bug #20708]
+ assert_in_out_err(["-e", "#{<<~"begin;"}\n#{<<~'end;'}"], [], [data])
+ wait, data = #{wait}, #{data.dump}
+ ;
+ begin;
+ trap(:USR1) {}
+ Thread.new do
+ sleep wait; Process.kill(:USR1, $$)
+ sleep wait; File.write("fifo", data)
+ end
+ puts File.read("fifo")
+ end;
+ end
+ end if Signal.list[:USR1] # Pointless on platforms without signal
+
+ 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
+ binary_enc = Encoding.find("BINARY")
+ make_tempfile do |t|
+ open(t.path, File::RDONLY, flags: File::BINARY) do |f|
+ assert_equal true, f.binmode?
+ assert_equal binary_enc, f.external_encoding
+ end
+ open(t.path, 'r', flags: File::BINARY) do |f|
+ assert_equal true, f.binmode?
+ assert_equal binary_enc, f.external_encoding
+ end
+ open(t.path, mode: 'r', flags: File::BINARY) do |f|
+ assert_equal true, f.binmode?
+ assert_equal binary_enc, f.external_encoding
+ end
+ open(t.path, File::RDONLY|File::BINARY) do |f|
+ assert_equal true, f.binmode?
+ assert_equal binary_enc, f.external_encoding
+ end
+ open(t.path, File::RDONLY|File::BINARY, autoclose: true) do |f|
+ assert_equal true, f.binmode?
+ assert_equal binary_enc, f.external_encoding
+ 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: 10 }
+ 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
+ omit "[Bug #18613]" if /freebsd/ =~ RUBY_PLATFORM
+
+ assert_separately([], "#{<<-"begin;"}\n#{<<-"end;"}")
+ begin;
+ bug13158 = '[ruby-core:79262] [Bug #13158]'
+ closed = nil
+ q = Thread::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
+ omit "[Bug #18613]" if /freebsd/ =~ RUBY_PLATFORM
+
+ 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
+ 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
+
+ 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
+
+ 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_select_timeout
+ assert_equal(nil, IO.select(nil,nil,nil,0))
+ assert_equal(nil, IO.select(nil,nil,nil,0.0))
+ assert_raise(TypeError) { IO.select(nil,nil,nil,"invalid-timeout") }
+ assert_raise(ArgumentError) { IO.select(nil,nil,nil,-1) }
+ assert_raise(ArgumentError) { IO.select(nil,nil,nil,-0.1) }
+ assert_raise(ArgumentError) { IO.select(nil,nil,nil,-Float::INFINITY) }
+ assert_raise(RangeError) { IO.select(nil,nil,nil,Float::NAN) }
+ IO.pipe {|r, w|
+ w << "x"
+ ret = [[r], [], []]
+ assert_equal(ret, IO.select([r],nil,nil,0.1))
+ assert_equal(ret, IO.select([r],nil,nil,1))
+ assert_equal(ret, IO.select([r],nil,nil,Float::INFINITY))
+ }
+ end
+
+ 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|
+ assert_nil r.timeout
+ assert_nil w.timeout
+
+ 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_memory_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], "#{<<~"begin;"}\n#{<<~'else;'}", "#{<<~'end;'}", rss: true, timeout: 60)
+ begin;
+ 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
+ else;
+ 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
+
+ def test_marshal_closed_io
+ bug18077 = '[ruby-core:104927] [Bug #18077]'
+ r, w = IO.pipe
+ r.close; w.close
+ assert_raise(TypeError, bug18077) {Marshal.dump(r)}
+
+ class << r
+ undef_method :closed?
+ end
+ assert_raise(TypeError, bug18077) {Marshal.dump(r)}
+ end
+
+ def test_stdout_to_closed_pipe
+ EnvUtil.invoke_ruby(["-e", "loop {puts :ok}"], "", true, true) do
+ |in_p, out_p, err_p, pid|
+ out = out_p.gets
+ out_p.close
+ err = err_p.read
+ ensure
+ status = Process.wait2(pid)[1]
+ assert_equal("ok\n", out)
+ assert_empty(err)
+ assert_not_predicate(status, :success?)
+ if Signal.list["PIPE"]
+ assert_predicate(status, :signaled?)
+ assert_equal("PIPE", Signal.signame(status.termsig) || status.termsig)
+ end
+ end
+ end
+
+ def test_blocking_timeout
+ assert_separately([], <<~'RUBY')
+ IO.pipe do |r, w|
+ trap(:INT) do
+ w.puts "INT"
+ end
+
+ main = Thread.current
+ thread = Thread.new do
+ # Wait until the main thread has entered `$stdin.gets`:
+ Thread.pass until main.status == 'sleep'
+
+ # Cause an interrupt while handling `$stdin.gets`:
+ Process.kill :INT, $$
+ end
+
+ r.timeout = 1
+ assert_equal("INT", r.gets.chomp)
+ rescue IO::TimeoutError
+ # Ignore - some platforms don't support interrupting `gets`.
+ ensure
+ thread&.join
+ end
+ RUBY
+ end
+
+ def test_fork_close
+ omit "fork is not supported" unless Process.respond_to?(:fork)
+
+ assert_separately([], <<~'RUBY')
+ r, w = IO.pipe
+
+ thread = Thread.new do
+ r.read
+ end
+
+ Thread.pass until thread.status == "sleep"
+
+ pid = fork do
+ r.close
+ end
+
+ w.close
+
+ status = Process.wait2(pid).last
+ thread.join
- IO.select(tempfiles)
- }, bug8080
+ assert_predicate(status, :success?)
+ RUBY
end
end