diff options
Diffstat (limited to 'test/ruby/test_io.rb')
| -rw-r--r-- | test/ruby/test_io.rb | 408 |
1 files changed, 360 insertions, 48 deletions
diff --git a/test/ruby/test_io.rb b/test/ruby/test_io.rb index 5ea9a8e574..1adf47ac51 100644 --- a/test/ruby/test_io.rb +++ b/test/ruby/test_io.rb @@ -350,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 @@ -373,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" @@ -441,6 +467,24 @@ class TestIO < Test::Unit::TestCase } 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 + def test_rubydev33072 t = make_tempfile path = t.path @@ -655,7 +699,6 @@ class TestIO < Test::Unit::TestCase if have_nonblock? def test_copy_stream_no_busy_wait - omit "MJIT has busy wait on GC. This sometimes fails with --jit." if defined?(RubyVM::MJIT) && RubyVM::MJIT.enabled? omit "multiple threads already active" if Thread.list.size > 1 msg = 'r58534 [ruby-core:80969] [Backport #13533]' @@ -1116,6 +1159,34 @@ class TestIO < Test::Unit::TestCase } 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 { @@ -1679,7 +1750,6 @@ class TestIO < Test::Unit::TestCase end if have_nonblock? def test_read_nonblock_no_exceptions - omit '[ruby-core:90895] MJIT worker may leave fd open in a forked child' if defined?(RubyVM::MJIT) && 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!" @@ -1898,6 +1968,148 @@ class TestIO < Test::Unit::TestCase end) end + def test_readline_bad_param_raises + File.open(__FILE__) do |f| + assert_raise(TypeError) do + f.readline Object.new + end + 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_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" @@ -2347,10 +2559,6 @@ class TestIO < Test::Unit::TestCase 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 - omit 'this randomly fails with MJIT' if defined?(RubyVM::MJIT) && RubyVM::MJIT.enabled? - feature2250 = '[ruby-core:26222]' pre = 'ft2250' t = Tempfile.new(pre) @@ -2411,30 +2619,15 @@ class TestIO < Test::Unit::TestCase assert_equal({:a=>1}, open(o, {a: 1})) 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_path_with_pipe + mkcdtmpdir do + cmd = "|echo foo" + assert_file.not_exist?(cmd) - 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) + 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 @@ -2537,6 +2730,17 @@ class TestIO < Test::Unit::TestCase } 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"} @@ -2567,6 +2771,16 @@ class TestIO < Test::Unit::TestCase } 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| @@ -2618,14 +2832,6 @@ class TestIO < Test::Unit::TestCase 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 } @@ -2701,10 +2907,10 @@ class TestIO < Test::Unit::TestCase end def test_print_separators - EnvUtil.suppress_warning { - $, = ':' - $\ = "\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') EnvUtil.suppress_warning {w.print('a','b','c')} @@ -2814,6 +3020,15 @@ class TestIO < Test::Unit::TestCase 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 @@ -2866,6 +3081,9 @@ class TestIO < Test::Unit::TestCase 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 @@ -3592,7 +3810,7 @@ __END__ end tempfiles = [] - (0..fd_setsize+1).map {|i| + (0...fd_setsize).map {|i| tempfiles << Tempfile.create("test_io_select_with_many_files") } @@ -3749,8 +3967,10 @@ __END__ end def test_open_fifo_does_not_block_other_threads - mkcdtmpdir { + mkcdtmpdir do File.mkfifo("fifo") + rescue NotImplementedError + else assert_separately([], <<-'EOS') t1 = Thread.new { open("fifo", "r") {|r| @@ -3765,8 +3985,32 @@ __END__ t1_value, _ = assert_join_threads([t1, t2]) assert_equal("foo", t1_value) EOS - } - end if /mswin|mingw|bccwin|cygwin/ !~ RUBY_PLATFORM + 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| @@ -3811,7 +4055,7 @@ __END__ end def test_race_gets_and_close - opt = { signal: :ABRT, timeout: 200 } + opt = { signal: :ABRT, timeout: 10 } assert_separately([], "#{<<-"begin;"}\n#{<<-"end;"}", **opt) bug13076 = '[ruby-core:78845] [Bug #13076]' begin; @@ -3973,7 +4217,7 @@ __END__ assert_raise(EOFError) { f.pread(1, f.size) } end } - end if IO.method_defined?(:pread) + end def test_pwrite make_tempfile { |t| @@ -3982,7 +4226,7 @@ __END__ assert_equal("ooo", f.pread(3, 4)) end } - end if IO.method_defined?(:pread) and IO.method_defined?(:pwrite) + end def test_select_exceptfds if Etc.uname[:sysname] == 'SunOS' @@ -4002,6 +4246,23 @@ __END__ 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| @@ -4113,4 +4374,55 @@ __END__ 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 + + assert_predicate(status, :success?) + RUBY + end end |
