diff --git a/ChangeLog b/ChangeLog index 2ae4b7fb14..90d80a5420 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,18 @@ +Tue Aug 27 07:35:05 2013 Aaron Patterson + + * io.c (io_read_nonblock): support non-blocking reads without raising + exceptions. As in: `io.read_nonblock(size, exception: false)` + [ruby-core:38666] [Feature #5138] + * ext/openssl/ossl_ssl.c (ossl_ssl_read_internal): ditto + * ext/stringio/stringio.c (strio_sysread): ditto + * io.c (rb_io_write_nonblock): support non-blocking writes without + raising an exception. + * ext/openssl/ossl_ssl.c (ossl_ssl_write_internal): ditto + * test/openssl/test_pair.rb (class OpenSSL): tests + * test/ruby/test_io.rb (class TestIO): ditto + * test/socket/test_nonblock.rb (class TestSocketNonblock): ditto + * test/stringio/test_stringio.rb (class TestStringIO): ditto + Tue Aug 27 05:24:34 2013 Eric Hodel * lib/rubygems: Import RubyGems 2.1.0 Release Candidate diff --git a/ext/openssl/lib/openssl/buffering.rb b/ext/openssl/lib/openssl/buffering.rb index 51bc968e3a..e40dfee667 100644 --- a/ext/openssl/lib/openssl/buffering.rb +++ b/ext/openssl/lib/openssl/buffering.rb @@ -161,7 +161,7 @@ module OpenSSL::Buffering # when the peer requests a new TLS/SSL handshake. See openssl the FAQ for # more details. http://www.openssl.org/support/faq.html - def read_nonblock(maxlen, buf=nil) + def read_nonblock(maxlen, buf=nil, exception: true) if maxlen == 0 if buf buf.clear @@ -171,7 +171,7 @@ module OpenSSL::Buffering end end if @rbuffer.empty? - return sysread_nonblock(maxlen, buf) + return sysread_nonblock(maxlen, buf, exception: exception) end ret = consume_rbuff(maxlen) if buf @@ -370,9 +370,9 @@ module OpenSSL::Buffering # is when the peer requests a new TLS/SSL handshake. See the openssl FAQ # for more details. http://www.openssl.org/support/faq.html - def write_nonblock(s) + def write_nonblock(s, exception: true) flush - syswrite_nonblock(s) + syswrite_nonblock(s, exception: exception) end ## diff --git a/ext/openssl/ossl_ssl.c b/ext/openssl/ossl_ssl.c index fde5ac4fe5..ccbb793cd6 100644 --- a/ext/openssl/ossl_ssl.c +++ b/ext/openssl/ossl_ssl.c @@ -103,6 +103,8 @@ static const char *ossl_ssl_attrs[] = { ID ID_callback_state; +static VALUE sym_exception; + /* * SSLContext class */ @@ -1373,10 +1375,16 @@ ossl_ssl_read_internal(int argc, VALUE *argv, VALUE self, int nonblock) { SSL *ssl; int ilen, nread = 0; + int no_exception = 0; VALUE len, str; rb_io_t *fptr; + VALUE opts = Qnil; + + rb_scan_args(argc, argv, "11:", &len, &str, &opts); + + if (!NIL_P(opts) && Qfalse == rb_hash_aref(opts, sym_exception)) + no_exception = 1; - rb_scan_args(argc, argv, "11", &len, &str); ilen = NUM2INT(len); if(NIL_P(str)) str = rb_str_new(0, ilen); else{ @@ -1397,17 +1405,23 @@ ossl_ssl_read_internal(int argc, VALUE *argv, VALUE self, int nonblock) case SSL_ERROR_NONE: goto end; case SSL_ERROR_ZERO_RETURN: + if (no_exception) { return Qnil; } rb_eof_error(); case SSL_ERROR_WANT_WRITE: + if (no_exception) { return ID2SYM(rb_intern("wait_writable")); } write_would_block(nonblock); rb_io_wait_writable(FPTR_TO_FD(fptr)); continue; case SSL_ERROR_WANT_READ: + if (no_exception) { return ID2SYM(rb_intern("wait_readable")); } read_would_block(nonblock); rb_io_wait_readable(FPTR_TO_FD(fptr)); continue; case SSL_ERROR_SYSCALL: - if(ERR_peek_error() == 0 && nread == 0) rb_eof_error(); + if(ERR_peek_error() == 0 && nread == 0) { + if (no_exception) { return Qnil; } + rb_eof_error(); + } rb_sys_fail(0); default: ossl_raise(eSSLError, "SSL_read"); @@ -1445,9 +1459,11 @@ ossl_ssl_read(int argc, VALUE *argv, VALUE self) * call-seq: * ssl.sysread_nonblock(length) => string * ssl.sysread_nonblock(length, buffer) => buffer + * ssl.sysread_nonblock(length[, buffer [, opts]) => buffer * * A non-blocking version of #sysread. Raises an SSLError if reading would - * block. + * block. If "exception: false" is passed, this method returns a symbol of + * :wait_writable, :wait_writable, or nil, rather than raising an exception. * * Reads +length+ bytes from the SSL connection. If a pre-allocated +buffer+ * is provided the data will be written into it. @@ -1459,7 +1475,7 @@ ossl_ssl_read_nonblock(int argc, VALUE *argv, VALUE self) } static VALUE -ossl_ssl_write_internal(VALUE self, VALUE str, int nonblock) +ossl_ssl_write_internal(VALUE self, VALUE str, int nonblock, int no_exception) { SSL *ssl; int nwrite = 0; @@ -1476,10 +1492,12 @@ ossl_ssl_write_internal(VALUE self, VALUE str, int nonblock) case SSL_ERROR_NONE: goto end; case SSL_ERROR_WANT_WRITE: + if (no_exception) { return ID2SYM(rb_intern("wait_writable")); } write_would_block(nonblock); rb_io_wait_writable(FPTR_TO_FD(fptr)); continue; case SSL_ERROR_WANT_READ: + if (no_exception) { return ID2SYM(rb_intern("wait_readable")); } read_would_block(nonblock); rb_io_wait_readable(FPTR_TO_FD(fptr)); continue; @@ -1509,7 +1527,7 @@ ossl_ssl_write_internal(VALUE self, VALUE str, int nonblock) static VALUE ossl_ssl_write(VALUE self, VALUE str) { - return ossl_ssl_write_internal(self, str, 0); + return ossl_ssl_write_internal(self, str, 0, 0); } /* @@ -1520,9 +1538,18 @@ ossl_ssl_write(VALUE self, VALUE str) * SSLError if writing would block. */ static VALUE -ossl_ssl_write_nonblock(VALUE self, VALUE str) +ossl_ssl_write_nonblock(int argc, VALUE *argv, VALUE self) { - return ossl_ssl_write_internal(self, str, 1); + VALUE str; + VALUE opts = Qnil; + int no_exception = 0; + + rb_scan_args(argc, argv, "1:", &str, &opts); + + if (!NIL_P(opts) && Qfalse == rb_hash_aref(opts, sym_exception)) + no_exception = 1; + + return ossl_ssl_write_internal(self, str, 1, no_exception); } /* @@ -2168,7 +2195,7 @@ Init_ossl_ssl() rb_define_method(cSSLSocket, "sysread", ossl_ssl_read, -1); rb_define_private_method(cSSLSocket, "sysread_nonblock", ossl_ssl_read_nonblock, -1); rb_define_method(cSSLSocket, "syswrite", ossl_ssl_write, 1); - rb_define_private_method(cSSLSocket, "syswrite_nonblock", ossl_ssl_write_nonblock, 1); + rb_define_private_method(cSSLSocket, "syswrite_nonblock", ossl_ssl_write_nonblock, -1); rb_define_method(cSSLSocket, "sysclose", ossl_ssl_close, 0); rb_define_method(cSSLSocket, "cert", ossl_ssl_get_cert, 0); rb_define_method(cSSLSocket, "peer_cert", ossl_ssl_get_peer_cert, 0); @@ -2239,4 +2266,6 @@ Init_ossl_ssl() ossl_ssl_def_const(OP_PKCS1_CHECK_2); ossl_ssl_def_const(OP_NETSCAPE_CA_DN_BUG); ossl_ssl_def_const(OP_NETSCAPE_DEMO_CIPHER_CHANGE_BUG); + + sym_exception = ID2SYM(rb_intern("exception")); } diff --git a/ext/stringio/stringio.c b/ext/stringio/stringio.c index ef3df832b8..5c2c64ca03 100644 --- a/ext/stringio/stringio.c +++ b/ext/stringio/stringio.c @@ -119,6 +119,8 @@ typedef char strio_flags_check[(STRIO_READABLE/FMODE_READABLE == STRIO_WRITABLE/ #define READABLE(strio) STRIO_MODE_SET_P(strio, READABLE) #define WRITABLE(strio) STRIO_MODE_SET_P(strio, WRITABLE) +static VALUE sym_exception; + static struct StringIO* readable(VALUE strio) { @@ -1327,7 +1329,6 @@ strio_read(int argc, VALUE *argv, VALUE self) * call-seq: * strio.sysread(integer[, outbuf]) -> string * strio.readpartial(integer[, outbuf]) -> string - * strio.read_nonblock(integer[, outbuf]) -> string * * Similar to #read, but raises +EOFError+ at end of string instead of * returning +nil+, as well as IO#sysread does. @@ -1342,8 +1343,50 @@ strio_sysread(int argc, VALUE *argv, VALUE self) return val; } +/* + * call-seq: + * strio.read_nonblock(integer[, outbuf [, opts]]) -> string + * + * Similar to #read, but raises +EOFError+ at end of string unless the + * +exception: false+ option is passed in. + */ +static VALUE +strio_read_nonblock(int argc, VALUE *argv, VALUE self) +{ + VALUE opts = Qnil; + int no_exception = 0; + + rb_scan_args(argc, argv, "11:", NULL, NULL, &opts); + + if (!NIL_P(opts)) { + argc--; + + if (Qfalse == rb_hash_aref(opts, sym_exception)) + no_exception = 1; + } + + VALUE val = strio_read(argc, argv, self); + if (NIL_P(val)) { + if (no_exception) + return Qnil; + else + rb_eof_error(); + } + + return val; +} + #define strio_syswrite rb_io_write +static VALUE +strio_syswrite_nonblock(int argc, VALUE *argv, VALUE self) +{ + VALUE str; + + rb_scan_args(argc, argv, "10:", &str, NULL); + return strio_syswrite(self, str); +} + #define strio_isatty strio_false #define strio_pid strio_nil @@ -1542,7 +1585,7 @@ Init_stringio() rb_define_method(mReadable, "readline", strio_readline, -1); rb_define_method(mReadable, "sysread", strio_sysread, -1); rb_define_method(mReadable, "readpartial", strio_sysread, -1); - rb_define_method(mReadable, "read_nonblock", strio_sysread, -1); + rb_define_method(mReadable, "read_nonblock", strio_read_nonblock, -1); rb_include_module(StringIO, mReadable); } { @@ -1552,7 +1595,9 @@ Init_stringio() rb_define_method(mWritable, "printf", strio_printf, -1); rb_define_method(mWritable, "puts", strio_puts, -1); rb_define_method(mWritable, "syswrite", strio_syswrite, 1); - rb_define_method(mWritable, "write_nonblock", strio_syswrite, 1); + rb_define_method(mWritable, "write_nonblock", strio_syswrite_nonblock, -1); rb_include_module(StringIO, mWritable); } + + sym_exception = ID2SYM(rb_intern("exception")); } diff --git a/io.c b/io.c index e1f0942d32..d4d6642002 100644 --- a/io.c +++ b/io.c @@ -159,7 +159,7 @@ static VALUE argf; static ID id_write, id_read, id_getc, id_flush, id_readpartial, id_set_encoding; static VALUE sym_mode, sym_perm, sym_extenc, sym_intenc, sym_encoding, sym_open_args; -static VALUE sym_textmode, sym_binmode, sym_autoclose; +static VALUE sym_textmode, sym_binmode, sym_autoclose, sym_exception; static VALUE sym_SET, sym_CUR, sym_END; #ifdef SEEK_DATA static VALUE sym_DATA; @@ -2413,14 +2413,14 @@ read_internal_call(VALUE arg) } static VALUE -io_getpartial(int argc, VALUE *argv, VALUE io, int nonblock) +io_getpartial(int argc, VALUE *argv, VALUE io, int nonblock, int no_exception) { rb_io_t *fptr; VALUE length, str; long n, len; struct read_internal_arg arg; - rb_scan_args(argc, argv, "11", &length, &str); + rb_scan_args(argc, argv, "11:", &length, &str, NULL); if ((len = NUM2LONG(length)) < 0) { rb_raise(rb_eArgError, "negative length %ld given", len); @@ -2452,8 +2452,12 @@ io_getpartial(int argc, VALUE *argv, VALUE io, int nonblock) if (n < 0) { if (!nonblock && rb_io_wait_readable(fptr->fd)) goto again; - if (nonblock && (errno == EWOULDBLOCK || errno == EAGAIN)) - rb_readwrite_sys_fail(RB_IO_WAIT_READABLE, "read would block"); + if (nonblock && (errno == EWOULDBLOCK || errno == EAGAIN)) { + if (no_exception) + return ID2SYM(rb_intern("wait_readable")); + else + rb_readwrite_sys_fail(RB_IO_WAIT_READABLE, "read would block"); + } rb_sys_fail_path(fptr->pathv); } } @@ -2529,7 +2533,7 @@ io_readpartial(int argc, VALUE *argv, VALUE io) { VALUE ret; - ret = io_getpartial(argc, argv, io, 0); + ret = io_getpartial(argc, argv, io, 0, 0); if (NIL_P(ret)) rb_eof_error(); return ret; @@ -2590,16 +2594,62 @@ static VALUE io_read_nonblock(int argc, VALUE *argv, VALUE io) { VALUE ret; + VALUE opts = Qnil; + int no_exception = 0; - ret = io_getpartial(argc, argv, io, 1); - if (NIL_P(ret)) - rb_eof_error(); + rb_scan_args(argc, argv, "11:", NULL, NULL, &opts); + + if (!NIL_P(opts) && Qfalse == rb_hash_aref(opts, sym_exception)) + no_exception = 1; + + ret = io_getpartial(argc, argv, io, 1, no_exception); + + if (NIL_P(ret)) { + if (no_exception) + return Qnil; + else + rb_eof_error(); + } return ret; } +static VALUE +io_write_nonblock(VALUE io, VALUE str, int no_exception) +{ + rb_io_t *fptr; + long n; + + if (!RB_TYPE_P(str, T_STRING)) + str = rb_obj_as_string(str); + + io = GetWriteIO(io); + GetOpenFile(io, fptr); + rb_io_check_writable(fptr); + + if (io_fflush(fptr) < 0) + rb_sys_fail(0); + + rb_io_set_nonblock(fptr); + n = write(fptr->fd, RSTRING_PTR(str), RSTRING_LEN(str)); + + if (n == -1) { + if (errno == EWOULDBLOCK || errno == EAGAIN) { + if (no_exception) { + return ID2SYM(rb_intern("wait_writable")); + } else { + rb_readwrite_sys_fail(RB_IO_WAIT_WRITABLE, "write would block"); + } + } + rb_sys_fail_path(fptr->pathv); + } + + return LONG2FIX(n); +} + /* * call-seq: * ios.write_nonblock(string) -> integer + * ios.write_nonblock(string [, options]) -> integer * * Writes the given string to ios using * the write(2) system call after O_NONBLOCK is set for @@ -2648,34 +2698,25 @@ io_read_nonblock(int argc, VALUE *argv, VALUE io) * according to the kind of the IO object. * In such cases, write_nonblock raises Errno::EBADF. * + * By specifying `exception: false`, the options hash allows you to indicate + * that write_nonblock should not raise an IO::WaitWritable exception, but + * return the symbol :wait_writable instead. + * */ static VALUE -rb_io_write_nonblock(VALUE io, VALUE str) +rb_io_write_nonblock(int argc, VALUE *argv, VALUE io) { - rb_io_t *fptr; - long n; + VALUE str; + VALUE opts = Qnil; + int no_exceptions = 0; - if (!RB_TYPE_P(str, T_STRING)) - str = rb_obj_as_string(str); + rb_scan_args(argc, argv, "10:", &str, &opts); - io = GetWriteIO(io); - GetOpenFile(io, fptr); - rb_io_check_writable(fptr); + if (!NIL_P(opts) && Qfalse == rb_hash_aref(opts, sym_exception)) + no_exceptions = 1; - if (io_fflush(fptr) < 0) - rb_sys_fail(0); - - rb_io_set_nonblock(fptr); - n = write(fptr->fd, RSTRING_PTR(str), RSTRING_LEN(str)); - - if (n == -1) { - if (errno == EWOULDBLOCK || errno == EAGAIN) - rb_readwrite_sys_fail(RB_IO_WAIT_WRITABLE, "write would block"); - rb_sys_fail_path(fptr->pathv); - } - - return LONG2FIX(n); + return io_write_nonblock(io, str, no_exceptions); } /* @@ -10809,7 +10850,7 @@ argf_getpartial(int argc, VALUE *argv, VALUE argf, int nonblock) RUBY_METHOD_FUNC(0), Qnil, rb_eEOFError, (VALUE)0); } else { - tmp = io_getpartial(argc, argv, ARGF.current_file, nonblock); + tmp = io_getpartial(argc, argv, ARGF.current_file, nonblock, 0); } if (NIL_P(tmp)) { if (ARGF.next_p == -1) { @@ -11851,7 +11892,7 @@ Init_IO(void) rb_define_method(rb_cIO, "readlines", rb_io_readlines, -1); rb_define_method(rb_cIO, "read_nonblock", io_read_nonblock, -1); - rb_define_method(rb_cIO, "write_nonblock", rb_io_write_nonblock, 1); + rb_define_method(rb_cIO, "write_nonblock", rb_io_write_nonblock, -1); rb_define_method(rb_cIO, "readpartial", io_readpartial, -1); rb_define_method(rb_cIO, "read", io_read, -1); rb_define_method(rb_cIO, "write", io_write_m, 1); @@ -12056,4 +12097,5 @@ Init_IO(void) #ifdef SEEK_HOLE sym_HOLE = ID2SYM(rb_intern("HOLE")); #endif + sym_exception = ID2SYM(rb_intern("exception")); } diff --git a/test/openssl/test_pair.rb b/test/openssl/test_pair.rb index fa36725b58..e6b495f669 100644 --- a/test/openssl/test_pair.rb +++ b/test/openssl/test_pair.rb @@ -156,19 +156,46 @@ class OpenSSL::TestPair < Test::Unit::TestCase ret = nil assert_nothing_raised("[ruby-core:20298]") { ret = s2.read_nonblock(10) } assert_equal("def\n", ret) + s1.close + assert_raise(EOFError) { s2.read_nonblock(10) } } end + def test_read_nonblock_no_exception + ssl_pair {|s1, s2| + assert_equal :wait_readable, s2.read_nonblock(10, exception: false) + s1.write "abc\ndef\n" + IO.select([s2]) + assert_equal("ab", s2.read_nonblock(2, exception: false)) + assert_equal("c\n", s2.gets) + ret = nil + assert_nothing_raised("[ruby-core:20298]") { ret = s2.read_nonblock(10, exception: false) } + assert_equal("def\n", ret) + s1.close + assert_equal(nil, s2.read_nonblock(10, exception: false)) + } + end + + def write_nonblock(socket, meth, str) + ret = socket.send(meth, str) + ret.is_a?(Symbol) ? 0 : ret + end + + def write_nonblock_no_ex(socket, str) + ret = socket.write_nonblock str, exception: false + ret.is_a?(Symbol) ? 0 : ret + end + def test_write_nonblock ssl_pair {|s1, s2| n = 0 begin - n += s1.write_nonblock("a" * 100000) - n += s1.write_nonblock("b" * 100000) - n += s1.write_nonblock("c" * 100000) - n += s1.write_nonblock("d" * 100000) - n += s1.write_nonblock("e" * 100000) - n += s1.write_nonblock("f" * 100000) + n += write_nonblock s1, :write_nonblock, "a" * 100000 + n += write_nonblock s1, :write_nonblock, "b" * 100000 + n += write_nonblock s1, :write_nonblock, "c" * 100000 + n += write_nonblock s1, :write_nonblock, "d" * 100000 + n += write_nonblock s1, :write_nonblock, "e" * 100000 + n += write_nonblock s1, :write_nonblock, "f" * 100000 rescue IO::WaitWritable end s1.close @@ -176,6 +203,20 @@ class OpenSSL::TestPair < Test::Unit::TestCase } end + def test_write_nonblock_no_exceptions + ssl_pair {|s1, s2| + n = 0 + n += write_nonblock_no_ex s1, "a" * 100000 + n += write_nonblock_no_ex s1, "b" * 100000 + n += write_nonblock_no_ex s1, "c" * 100000 + n += write_nonblock_no_ex s1, "d" * 100000 + n += write_nonblock_no_ex s1, "e" * 100000 + n += write_nonblock_no_ex s1, "f" * 100000 + s1.close + assert_equal(n, s2.read.length) + } + end + def test_write_nonblock_with_buffered_data ssl_pair {|s1, s2| s1.write "foo" @@ -186,6 +227,16 @@ class OpenSSL::TestPair < Test::Unit::TestCase } end + def test_write_nonblock_with_buffered_data_no_exceptions + ssl_pair {|s1, s2| + s1.write "foo" + s1.write_nonblock("bar", exception: false) + s1.write "baz" + s1.close + assert_equal("foobarbaz", s2.read) + } + end + def test_connect_accept_nonblock host = "127.0.0.1" port = 0 diff --git a/test/ruby/test_io.rb b/test/ruby/test_io.rb index 5cda6c9e5a..4f002e8b3d 100644 --- a/test/ruby/test_io.rb +++ b/test/ruby/test_io.rb @@ -1205,6 +1205,16 @@ class TestIO < Test::Unit::TestCase } end + def test_write_nonblock_simple_no_exceptions + skip "IO#write_nonblock is not supported on file/pipe." if /mswin|bccwin|mingw/ =~ RUBY_PLATFORM + 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 @@ -1215,6 +1225,41 @@ class TestIO < Test::Unit::TestCase assert_kind_of(IO::WaitReadable, $!) end } + + with_pipe {|r, w| + begin + r.read_nonblock 4096, "" + rescue Errno::EWOULDBLOCK + assert_kind_of(IO::WaitReadable, $!) + end + } + end + + def test_read_nonblock_no_exceptions + return if !have_nonblock? + skip "IO#read_nonblock is not supported on file/pipe." if /mswin|bccwin|mingw/ =~ RUBY_PLATFORM + 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 + + def test_read_nonblock_with_buffer_no_exceptions + return if !have_nonblock? + skip "IO#read_nonblock is not supported on file/pipe." if /mswin|bccwin|mingw/ =~ RUBY_PLATFORM + 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 buf.equal?(value) + w.close + assert_equal nil, r.read_nonblock(4096, "", exception: false) + } end def test_write_nonblock_error @@ -1231,6 +1276,20 @@ class TestIO < Test::Unit::TestCase } end + def test_write_nonblock_no_exceptions + return if !have_nonblock? + skip "IO#write_nonblock is not supported on file/pipe." if /mswin|bccwin|mingw/ =~ RUBY_PLATFORM + 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 + def test_gets pipe(proc do |w| w.write "foobarbaz" diff --git a/test/socket/test_nonblock.rb b/test/socket/test_nonblock.rb index d494f91c41..e395a0ad31 100644 --- a/test/socket/test_nonblock.rb +++ b/test/socket/test_nonblock.rb @@ -190,6 +190,20 @@ class TestSocketNonblock < Test::Unit::TestCase s.close if s end + def test_read_nonblock_no_exception + c, s = tcp_pair + assert_equal :wait_readable, c.read_nonblock(100, exception: false) + assert_equal :wait_readable, s.read_nonblock(100, exception: false) + c.write("abc") + IO.select [s] + assert_equal("a", s.read_nonblock(1, exception: false)) + assert_equal("bc", s.read_nonblock(100, exception: false)) + assert_equal :wait_readable, s.read_nonblock(100, exception: false) + ensure + c.close if c + s.close if s + end + =begin def test_write_nonblock c, s = tcp_pair diff --git a/test/stringio/test_stringio.rb b/test/stringio/test_stringio.rb index 0eceeba894..f29322b393 100644 --- a/test/stringio/test_stringio.rb +++ b/test/stringio/test_stringio.rb @@ -89,6 +89,14 @@ class TestStringIO < Test::Unit::TestCase f.close unless f.closed? end + def test_write_nonblock_no_exceptions + s = "" + f = StringIO.new(s, "w") + f.write_nonblock("foo", exception: false) + f.close + assert_equal("foo", s) + end + def test_write_nonblock s = "" f = StringIO.new(s, "w") @@ -437,7 +445,7 @@ class TestStringIO < Test::Unit::TestCase f = StringIO.new("\u3042\u3044") assert_raise(ArgumentError) { f.readpartial(-1) } assert_raise(ArgumentError) { f.readpartial(1, 2, 3) } - assert_equal("\u3042\u3044", f.readpartial) + assert_equal("\u3042\u3044".force_encoding(Encoding::ASCII_8BIT), f.readpartial(100)) f.rewind assert_equal("\u3042\u3044".force_encoding(Encoding::ASCII_8BIT), f.readpartial(f.size)) f.rewind @@ -450,7 +458,19 @@ class TestStringIO < Test::Unit::TestCase f = StringIO.new("\u3042\u3044") assert_raise(ArgumentError) { f.read_nonblock(-1) } assert_raise(ArgumentError) { f.read_nonblock(1, 2, 3) } - assert_equal("\u3042\u3044", f.read_nonblock) + assert_equal("\u3042\u3044".force_encoding("BINARY"), f.read_nonblock(100)) + assert_raise(EOFError) { f.read_nonblock(10) } + f.rewind + assert_equal("\u3042\u3044".force_encoding(Encoding::ASCII_8BIT), f.read_nonblock(f.size)) + end + + def test_read_nonblock_no_exceptions + f = StringIO.new("\u3042\u3044") + assert_raise(ArgumentError) { f.read_nonblock(-1, exception: false) } + assert_raise(ArgumentError) { f.read_nonblock(1, 2, 3, exception: false) } + assert_raise(ArgumentError) { f.read_nonblock } + assert_equal("\u3042\u3044".force_encoding(Encoding::ASCII_8BIT), f.read_nonblock(100, exception: false)) + assert_equal(nil, f.read_nonblock(10, exception: false)) f.rewind assert_equal("\u3042\u3044".force_encoding(Encoding::ASCII_8BIT), f.read_nonblock(f.size)) f.rewind