From 0e23fb99db43980cfcfd4201c75dea71bb4df0c5 Mon Sep 17 00:00:00 2001 From: Charles Oliver Nutter Date: Mon, 10 Dec 2018 12:25:17 -0600 Subject: [PATCH] Update tests from MRI 2.5 branch (2.4ish) --- test/mri/-ext-/debug/test_debug.rb | 16 ++ test/mri/-ext-/funcall/test_funcall.rb | 11 ++ test/mri/date/test_date_arith.rb | 13 ++ test/mri/dbm/test_dbm.rb | 2 + test/mri/erb/test_erb.rb | 7 + test/mri/gdbm/test_gdbm.rb | 4 + test/mri/irb/test_workspace.rb | 1 + test/mri/json/json_addition_test.rb | 10 - test/mri/json/test_helper.rb | 4 + test/mri/lib/envutil.rb | 2 +- test/mri/lib/test/unit/assertions.rb | 2 - test/mri/net/ftp/test_buffered_socket.rb | 6 + test/mri/net/ftp/test_ftp.rb | 2 +- test/mri/net/imap/test_imap.rb | 37 ++++ test/mri/net/pop/test_pop.rb | 29 +++ test/mri/openssl/test_cipher.rb | 3 + test/mri/openssl/test_pkey_rsa.rb | 9 +- test/mri/openssl/test_ssl.rb | 58 ++++-- test/mri/openssl/test_ssl_session.rb | 15 +- test/mri/openssl/test_x509name.rb | 23 ++- test/mri/pathname/test_pathname.rb | 1 + test/mri/rdoc/test_rdoc_options.rb | 1 + test/mri/rdoc/test_rdoc_rdoc.rb | 1 + test/mri/rdoc/test_rdoc_rubygems_hook.rb | 2 + test/mri/resolv/test_dns.rb | 38 ---- test/mri/ruby/enc/test_grapheme_breaks.rb | 92 ++++++++++ test/mri/ruby/test_array.rb | 21 +++ test/mri/ruby/test_autoload.rb | 40 ++++ test/mri/ruby/test_complex.rb | 8 +- test/mri/ruby/test_defined.rb | 4 + test/mri/ruby/test_dir.rb | 9 + test/mri/ruby/test_enum.rb | 15 ++ test/mri/ruby/test_enumerator.rb | 5 +- test/mri/ruby/test_exception.rb | 73 ++++++++ test/mri/ruby/test_file.rb | 8 + test/mri/ruby/test_file_exhaustive.rb | 2 + test/mri/ruby/test_hash.rb | 8 + test/mri/ruby/test_io.rb | 17 ++ test/mri/ruby/test_iseq.rb | 41 +++++ test/mri/ruby/test_keyword.rb | 7 +- test/mri/ruby/test_lazy_enumerator.rb | 9 + test/mri/ruby/test_marshal.rb | 7 + test/mri/ruby/test_method.rb | 18 ++ test/mri/ruby/test_mixed_unicode_escapes.rb | 8 +- test/mri/ruby/test_module.rb | 61 +++++- test/mri/ruby/test_numeric.rb | 5 + test/mri/ruby/test_optimization.rb | 44 ++++- test/mri/ruby/test_pack.rb | 19 ++ test/mri/ruby/test_parse.rb | 6 + test/mri/ruby/test_proc.rb | 2 +- test/mri/ruby/test_rational.rb | 8 +- test/mri/ruby/test_refinement.rb | 19 ++ test/mri/ruby/test_rubyoptions.rb | 10 + test/mri/ruby/test_settracefunc.rb | 15 ++ test/mri/ruby/test_signal.rb | 4 +- test/mri/ruby/test_string.rb | 26 ++- test/mri/ruby/test_super.rb | 16 ++ test/mri/ruby/test_syntax.rb | 20 ++ test/mri/ruby/test_thread.rb | 15 +- test/mri/ruby/test_time_tz.rb | 52 +++++- test/mri/ruby/test_undef.rb | 2 +- test/mri/rubygems/test_gem.rb | 16 +- .../test_gem_commands_cleanup_command.rb | 2 +- .../test_gem_commands_install_command.rb | 1 + .../test_gem_commands_owner_command.rb | 25 +++ .../test_gem_commands_push_command.rb | 10 +- .../test_gem_commands_setup_command.rb | 64 +++++-- .../test_gem_commands_uninstall_command.rb | 4 +- .../rubygems/test_gem_dependency_installer.rb | 2 +- test/mri/rubygems/test_gem_doctor.rb | 4 +- test/mri/rubygems/test_gem_ext_builder.rb | 12 +- .../rubygems/test_gem_gemcutter_utilities.rb | 8 +- test/mri/rubygems/test_gem_indexer.rb | 3 +- .../test_gem_install_update_options.rb | 2 + test/mri/rubygems/test_gem_installer.rb | 6 +- test/mri/rubygems/test_gem_package.rb | 104 +++++++++-- test/mri/rubygems/test_gem_package_old.rb | 2 +- .../rubygems/test_gem_package_tar_header.rb | 20 ++ test/mri/rubygems/test_gem_rdoc.rb | 2 + test/mri/rubygems/test_gem_remote_fetcher.rb | 2 +- test/mri/rubygems/test_gem_request_set.rb | 14 +- .../rubygems/test_gem_request_set_lockfile.rb | 8 +- .../test_gem_request_set_lockfile_parser.rb | 2 +- ...test_gem_request_set_lockfile_tokenizer.rb | 2 +- .../test_gem_resolver_git_specification.rb | 2 +- test/mri/rubygems/test_gem_server.rb | 173 +++++++++++++++++- test/mri/rubygems/test_gem_source.rb | 6 +- test/mri/rubygems/test_gem_source_git.rb | 2 +- test/mri/rubygems/test_gem_specification.rb | 52 ++++-- .../rubygems/test_gem_stub_specification.rb | 14 +- test/mri/rubygems/test_gem_util.rb | 25 +++ test/mri/rubygems/test_gem_version.rb | 11 ++ test/mri/rubygems/test_require.rb | 36 ++-- test/mri/runner.rb | 1 - test/mri/sdbm/test_sdbm.rb | 2 + test/mri/socket/test_unix.rb | 10 + test/mri/test_find.rb | 4 + test/mri/test_tempfile.rb | 28 ++- test/mri/test_time.rb | 2 + test/mri/test_tmpdir.rb | 17 ++ test/mri/webrick/test_filehandler.rb | 31 +++- test/mri/webrick/test_httpauth.rb | 90 ++++++++- test/mri/webrick/test_httpresponse.rb | 22 +++ test/mri/webrick/test_httpserver.rb | 67 +++++++ 104 files changed, 1667 insertions(+), 256 deletions(-) create mode 100644 test/mri/-ext-/funcall/test_funcall.rb create mode 100644 test/mri/ruby/enc/test_grapheme_breaks.rb diff --git a/test/mri/-ext-/debug/test_debug.rb b/test/mri/-ext-/debug/test_debug.rb index 3804714d0dc..bc41c1bb79b 100644 --- a/test/mri/-ext-/debug/test_debug.rb +++ b/test/mri/-ext-/debug/test_debug.rb @@ -56,4 +56,20 @@ def test_inspector_open_in_eval binds = inspector_in_eval binds_check binds, bug7635 end + + class MyRelation + include Enumerable + + def each + yield :each_entry + end + end + + def test_lazy_block + x = MyRelation.new.any? do + Bug::Debug.inspector + true + end + assert_equal true, x, '[Bug #15105]' + end end diff --git a/test/mri/-ext-/funcall/test_funcall.rb b/test/mri/-ext-/funcall/test_funcall.rb new file mode 100644 index 00000000000..01a03bf5eff --- /dev/null +++ b/test/mri/-ext-/funcall/test_funcall.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true +require 'test/unit' + +class TestFuncall < Test::Unit::TestCase + require '-test-/funcall' + + def test_funcall_extra_args + assert_equal 'TestFuncall', TestFuncall.extra_args_name, + '[ruby-core:85266] [Bug #14425]' + end +end diff --git a/test/mri/date/test_date_arith.rb b/test/mri/date/test_date_arith.rb index 96622ba0657..d0d27d72f74 100644 --- a/test/mri/date/test_date_arith.rb +++ b/test/mri/date/test_date_arith.rb @@ -262,4 +262,17 @@ def test_step__noblock assert_equal(8, e.to_a.size) end + def test_step__compare + o = Object.new + def o.<=>(*);end + assert_raise(ArgumentError) { + Date.new(2000, 1, 1).step(3, o).to_a + } + + o = Object.new + def o.<=>(*);2;end + a = [] + Date.new(2000, 1, 1).step(3, o) {|d| a << d} + assert_empty(a) + end end diff --git a/test/mri/dbm/test_dbm.rb b/test/mri/dbm/test_dbm.rb index 93198fdea29..874b3264044 100644 --- a/test/mri/dbm/test_dbm.rb +++ b/test/mri/dbm/test_dbm.rb @@ -47,6 +47,8 @@ def teardown end def test_delete_rdonly + skip("skipped because root can read anything") if Process.uid == 0 + if /^CYGWIN_9/ !~ SYSTEM assert_raise(DBMError) { @dbm_rdonly.delete("foo") diff --git a/test/mri/erb/test_erb.rb b/test/mri/erb/test_erb.rb index 46f81c778b0..045ce5129ba 100644 --- a/test/mri/erb/test_erb.rb +++ b/test/mri/erb/test_erb.rb @@ -622,6 +622,13 @@ def test_result_with_hash_with_invalid_keys_raises_type_error erb = @erb.new("<%= 1 %>") assert_raise(TypeError) { erb.result_with_hash({ 1 => "1" }) } end + + # Bug#14243 + def test_half_working_comment_backward_compatibility + assert_nothing_raised do + @erb.new("<% # comment %>\n").result + end + end end class TestERBCoreWOStrScan < TestERBCore diff --git a/test/mri/gdbm/test_gdbm.rb b/test/mri/gdbm/test_gdbm.rb index 1d69634f887..9e97eb3587c 100644 --- a/test/mri/gdbm/test_gdbm.rb +++ b/test/mri/gdbm/test_gdbm.rb @@ -43,6 +43,8 @@ def teardown end def test_delete_rdonly + skip("skipped because root can open anything") if Process.uid == 0 + if /^CYGWIN_9/ !~ SYSTEM assert_raise(GDBMError) { @gdbm_rdonly.delete("foo") @@ -211,6 +213,8 @@ def test_s_open_nolock end if defined? GDBM::NOLOCK # gdbm 1.8.0 specific def test_s_open_error + skip if Process.uid == 0 # because root can open anything + assert_instance_of(GDBM, gdbm = GDBM.open("#{@tmpdir}/#{@prefix}", 0)) assert_raise(Errno::EACCES, Errno::EWOULDBLOCK) { GDBM.open("#{@tmpdir}/#{@prefix}", 0) diff --git a/test/mri/irb/test_workspace.rb b/test/mri/irb/test_workspace.rb index 0dd46175c88..0795b17e097 100644 --- a/test/mri/irb/test_workspace.rb +++ b/test/mri/irb/test_workspace.rb @@ -34,6 +34,7 @@ def test_code_around_binding def test_code_around_binding_with_existing_unreadable_file skip 'chmod cannot make file unreadable on windows' if windows? + skip 'skipped in root privilege' if Process.uid == 0 Tempfile.create do |f| code = "IRB::WorkSpace.new(binding)\n" diff --git a/test/mri/json/json_addition_test.rb b/test/mri/json/json_addition_test.rb index 61625f89e25..a028e0f08a7 100644 --- a/test/mri/json/json_addition_test.rb +++ b/test/mri/json/json_addition_test.rb @@ -5,7 +5,6 @@ require 'json/add/rational' require 'json/add/bigdecimal' require 'json/add/ostruct' -require 'json/add/set' require 'date' class JSONAdditionTest < Test::Unit::TestCase @@ -191,13 +190,4 @@ def test_ostruct o.foo = { 'bar' => true } assert_equal o, parse(JSON(o), :create_additions => true) end - - def test_set - s = Set.new([:a, :b, :c, :a]) - assert_equal s, JSON.parse(JSON(s), :create_additions => true) - ss = SortedSet.new([:d, :b, :a, :c]) - ss_again = JSON.parse(JSON(ss), :create_additions => true) - assert_kind_of ss.class, ss_again - assert_equal ss, ss_again - end end diff --git a/test/mri/json/test_helper.rb b/test/mri/json/test_helper.rb index c5ec0fca7b7..7e99c29d0da 100644 --- a/test/mri/json/test_helper.rb +++ b/test/mri/json/test_helper.rb @@ -15,3 +15,7 @@ require 'byebug' rescue LoadError end +if ENV['START_SIMPLECOV'].to_i == 1 + require 'simplecov' + SimpleCov.start +end diff --git a/test/mri/lib/envutil.rb b/test/mri/lib/envutil.rb index fce0e1b3821..5d3bce99ec3 100644 --- a/test/mri/lib/envutil.rb +++ b/test/mri/lib/envutil.rb @@ -147,7 +147,7 @@ def invoke_ruby(args, stdin_data = "", capture_stdout = false, capture_stderr = stderr = stderr_filter.call(stderr) if stderr_filter if timeout_error bt = caller_locations - msg = "execution of #{bt.shift.label} expired" + msg = "execution of #{bt.shift.label} expired timeout (#{timeout} sec)" msg = Test::Unit::Assertions::FailDesc[status, msg, [stdout, stderr].join("\n")].() raise timeout_error, msg, bt.map(&:to_s) end diff --git a/test/mri/lib/test/unit/assertions.rb b/test/mri/lib/test/unit/assertions.rb index 53b10fc04ab..54d2d01d12c 100644 --- a/test/mri/lib/test/unit/assertions.rb +++ b/test/mri/lib/test/unit/assertions.rb @@ -595,8 +595,6 @@ def assert_normal_exit(testsrc, message = '', child_env: nil, **opt) def assert_in_out_err(args, test_stdin = "", test_stdout = [], test_stderr = [], message = nil, success: nil, **opt) - args = Array(args).dup - args.insert((Hash === args[0] ? 1 : 0), '--disable=gems') stdout, stderr, status = EnvUtil.invoke_ruby(args, test_stdin, true, true, **opt) if signo = status.termsig EnvUtil.diagnostic_reports(Signal.signame(signo), status.pid, Time.now) diff --git a/test/mri/net/ftp/test_buffered_socket.rb b/test/mri/net/ftp/test_buffered_socket.rb index 3cc46fa5558..875c53f4e02 100644 --- a/test/mri/net/ftp/test_buffered_socket.rb +++ b/test/mri/net/ftp/test_buffered_socket.rb @@ -33,6 +33,12 @@ def test_gets_two_lines_without_term assert_equal("bar", sock.gets) end + def test_read_nil + sock = create_buffered_socket("foo\nbar") + assert_equal("foo\nbar", sock.read) + assert_equal("", sock.read) + end + private def create_buffered_socket(s) diff --git a/test/mri/net/ftp/test_ftp.rb b/test/mri/net/ftp/test_ftp.rb index 461ba1630e2..03204ebc529 100644 --- a/test/mri/net/ftp/test_ftp.rb +++ b/test/mri/net/ftp/test_ftp.rb @@ -425,7 +425,7 @@ def test_list_read_timeout_exceeded end conn.print(l, "\r\n") end - rescue Errno::EPIPE, Errno::EPROTOTYPE + rescue Errno::EPIPE ensure assert_nil($!) conn.close diff --git a/test/mri/net/imap/test_imap.rb b/test/mri/net/imap/test_imap.rb index 38ea84cc21f..41f25fe1c78 100644 --- a/test/mri/net/imap/test_imap.rb +++ b/test/mri/net/imap/test_imap.rb @@ -530,6 +530,43 @@ def test_send_invalid_number end end + def test_send_literal + server = create_tcp_server + port = server.addr[1] + requests = [] + literal = nil + @threads << Thread.start do + sock = server.accept + begin + sock.print("* OK test server\r\n") + line = sock.gets + requests.push(line) + size = line.slice(/{(\d+)}\r\n/, 1).to_i + sock.print("+ Ready for literal data\r\n") + literal = sock.read(size) + requests.push(sock.gets) + sock.print("RUBY0001 OK TEST completed\r\n") + sock.gets + sock.print("* BYE terminating connection\r\n") + sock.print("RUBY0002 OK LOGOUT completed\r\n") + ensure + sock.close + server.close + end + end + begin + imap = Net::IMAP.new(server_addr, :port => port) + imap.send(:send_command, "TEST", ["\xDE\xAD\xBE\xEF".b]) + assert_equal(2, requests.length) + assert_equal("RUBY0001 TEST ({4}\r\n", requests[0]) + assert_equal("\xDE\xAD\xBE\xEF".b, literal) + assert_equal(")\r\n", requests[1]) + imap.logout + ensure + imap.disconnect + end + end + def test_disconnect server = create_tcp_server port = server.addr[1] diff --git a/test/mri/net/pop/test_pop.rb b/test/mri/net/pop/test_pop.rb index 666aac0db6f..f4c807a7a89 100644 --- a/test/mri/net/pop/test_pop.rb +++ b/test/mri/net/pop/test_pop.rb @@ -64,6 +64,35 @@ def test_apop_invalid_at end end + def test_popmail + # totally not representative of real messages, but + # enough to test frozen bugs + lines = [ "[ruby-core:85210]" , "[Bug #14416]" ].freeze + command = Object.new + command.instance_variable_set(:@lines, lines) + + def command.retr(n) + @lines.each { |l| yield "#{l}\r\n" } + end + + def command.top(number, nl) + @lines.each do |l| + yield "#{l}\r\n" + break if (nl -= 1) <= 0 + end + end + + net_pop = :unused + popmail = Net::POPMail.new(1, 123, net_pop, command) + res = popmail.pop + assert_equal "[ruby-core:85210]\r\n[Bug #14416]\r\n", res + assert_not_predicate res, :frozen? + + res = popmail.top(1) + assert_equal "[ruby-core:85210]\r\n", res + assert_not_predicate res, :frozen? + end + def pop_test(apop=false) host = 'localhost' server = TCPServer.new(host, 0) diff --git a/test/mri/openssl/test_cipher.rb b/test/mri/openssl/test_cipher.rb index 56061741bc8..d83fa4ec3d2 100644 --- a/test/mri/openssl/test_cipher.rb +++ b/test/mri/openssl/test_cipher.rb @@ -44,6 +44,9 @@ def test_pkcs5_keyivgen s2 = cipher.update(pt) << cipher.final assert_equal s1, s2 + + cipher2 = OpenSSL::Cipher.new("DES-EDE3-CBC").encrypt + assert_raise(ArgumentError) { cipher2.pkcs5_keyivgen(pass, salt, -1, "MD5") } end def test_info diff --git a/test/mri/openssl/test_pkey_rsa.rb b/test/mri/openssl/test_pkey_rsa.rb index d9bea1a622c..ef02717d8b2 100644 --- a/test/mri/openssl/test_pkey_rsa.rb +++ b/test/mri/openssl/test_pkey_rsa.rb @@ -60,6 +60,13 @@ def test_new_with_exponent end end + def test_generate + key = OpenSSL::PKey::RSA.generate(512, 17) + assert_equal 512, key.n.num_bits + assert_equal 17, key.e + assert_not_nil key.d + end + def test_new_break assert_nil(OpenSSL::PKey::RSA.new(1024) { break }) assert_raise(RuntimeError) do @@ -289,7 +296,7 @@ def test_pem_passwd end def test_dup - key = OpenSSL::PKey::RSA.generate(256, 17) + key = Fixtures.pkey("rsa1024") key2 = key.dup assert_equal key.params, key2.params key2.set_key(key2.n, 3, key2.d) diff --git a/test/mri/openssl/test_ssl.rb b/test/mri/openssl/test_ssl.rb index 3b063d2e11b..060c1f1cfc4 100644 --- a/test/mri/openssl/test_ssl.rb +++ b/test/mri/openssl/test_ssl.rb @@ -47,6 +47,8 @@ def test_ssl_with_server_cert assert_equal 2, ssl.peer_cert_chain.size assert_equal @svr_cert.to_der, ssl.peer_cert_chain[0].to_der assert_equal @ca_cert.to_der, ssl.peer_cert_chain[1].to_der + + ssl.puts "abc"; assert_equal "abc\n", ssl.gets ensure ssl&.close sock&.close @@ -65,6 +67,8 @@ def test_add_certificate assert_equal @svr_cert.subject, ssl.peer_cert.subject assert_equal [@svr_cert.subject, @ca_cert.subject], ssl.peer_cert_chain.map(&:subject) + + ssl.puts "abc"; assert_equal "abc\n", ssl.gets } end end @@ -157,6 +161,7 @@ def test_sync_close sock = TCPSocket.new("127.0.0.1", port) ssl = OpenSSL::SSL::SSLSocket.new(sock) ssl.connect + ssl.puts "abc"; assert_equal "abc\n", ssl.gets ssl.close assert_not_predicate sock, :closed? ensure @@ -168,6 +173,7 @@ def test_sync_close ssl = OpenSSL::SSL::SSLSocket.new(sock) ssl.sync_close = true # !! ssl.connect + ssl.puts "abc"; assert_equal "abc\n", ssl.gets ssl.close assert_predicate sock, :closed? ensure @@ -259,7 +265,10 @@ def test_client_ca client_ca_from_server = sslconn.client_ca [@cli_cert, @cli_key] end - server_connect(port, ctx) { |ssl| assert_equal([@ca], client_ca_from_server) } + server_connect(port, ctx) { |ssl| + assert_equal([@ca], client_ca_from_server) + ssl.puts "abc"; assert_equal "abc\n", ssl.gets + } } end @@ -356,21 +365,16 @@ def test_verify_result } start_server { |port| - sock = TCPSocket.new("127.0.0.1", port) ctx = OpenSSL::SSL::SSLContext.new ctx.verify_mode = OpenSSL::SSL::VERIFY_PEER ctx.verify_callback = Proc.new do |preverify_ok, store_ctx| store_ctx.error = OpenSSL::X509::V_OK true end - ssl = OpenSSL::SSL::SSLSocket.new(sock, ctx) - ssl.sync_close = true - begin - ssl.connect + server_connect(port, ctx) { |ssl| assert_equal(OpenSSL::X509::V_OK, ssl.verify_result) - ensure - ssl.close - end + ssl.puts "abc"; assert_equal "abc\n", ssl.gets + } } start_server(ignore_listener_error: true) { |port| @@ -455,6 +459,8 @@ def test_post_connection_check start_server { |port| server_connect(port) { |ssl| + ssl.puts "abc"; assert_equal "abc\n", ssl.gets + assert_raise(sslerr){ssl.post_connection_check("localhost.localdomain")} assert_raise(sslerr){ssl.post_connection_check("127.0.0.1")} assert(ssl.post_connection_check("localhost")) @@ -476,6 +482,8 @@ def test_post_connection_check @svr_cert = issue_cert(@svr, @svr_key, 4, exts, @ca_cert, @ca_key) start_server { |port| server_connect(port) { |ssl| + ssl.puts "abc"; assert_equal "abc\n", ssl.gets + assert(ssl.post_connection_check("localhost.localdomain")) assert(ssl.post_connection_check("127.0.0.1")) assert_raise(sslerr){ssl.post_connection_check("localhost")} @@ -496,6 +504,8 @@ def test_post_connection_check @svr_cert = issue_cert(@svr, @svr_key, 5, exts, @ca_cert, @ca_key) start_server { |port| server_connect(port) { |ssl| + ssl.puts "abc"; assert_equal "abc\n", ssl.gets + assert(ssl.post_connection_check("localhost.localdomain")) assert_raise(sslerr){ssl.post_connection_check("127.0.0.1")} assert_raise(sslerr){ssl.post_connection_check("localhost")} @@ -722,6 +732,8 @@ def test_tlsext_hostname ssl.connect assert_equal @cli_cert.serial, ssl.peer_cert.serial assert_predicate fooctx, :frozen? + + ssl.puts "abc"; assert_equal "abc\n", ssl.gets ensure ssl&.close sock.close @@ -733,6 +745,8 @@ def test_tlsext_hostname ssl.hostname = "bar.example.com" ssl.connect assert_equal @svr_cert.serial, ssl.peer_cert.serial + + ssl.puts "abc"; assert_equal "abc\n", ssl.gets ensure ssl&.close sock.close @@ -805,7 +819,8 @@ def test_verify_hostname_on_connect ssl = OpenSSL::SSL::SSLSocket.new(sock, ctx) ssl.hostname = name if expected_ok - assert_nothing_raised { ssl.connect } + ssl.connect + ssl.puts "abc"; assert_equal "abc\n", ssl.gets else assert_handshake_error { ssl.connect } end @@ -879,7 +894,9 @@ def check_supported_protocol_versions } start_server(ctx_proc: ctx_proc, ignore_listener_error: true) do |port| begin - server_connect(port) { } + server_connect(port) { |ssl| + ssl.puts "abc"; assert_equal "abc\n", ssl.gets + } rescue OpenSSL::SSL::SSLError, Errno::ECONNRESET else supported << ver @@ -937,6 +954,7 @@ def test_minmax_version if ver == cver server_connect(port, ctx1) { |ssl| assert_equal vmap[cver][:name], ssl.ssl_version + ssl.puts "abc"; assert_equal "abc\n", ssl.gets } else assert_handshake_error { server_connect(port, ctx1) { } } @@ -950,6 +968,7 @@ def test_minmax_version if ver == cver server_connect(port, ctx2) { |ssl| assert_equal vmap[cver][:name], ssl.ssl_version + ssl.puts "abc"; assert_equal "abc\n", ssl.gets } else assert_handshake_error { server_connect(port, ctx2) { } } @@ -962,6 +981,7 @@ def test_minmax_version ctx3.min_version = ctx3.max_version = nil server_connect(port, ctx3) { |ssl| assert_equal vmap[ver][:name], ssl.ssl_version + ssl.puts "abc"; assert_equal "abc\n", ssl.gets } } end @@ -980,6 +1000,7 @@ def test_minmax_version ctx1.min_version = cver server_connect(port, ctx1) { |ssl| assert_equal vmap[supported.last][:name], ssl.ssl_version + ssl.puts "abc"; assert_equal "abc\n", ssl.gets } # Client sets max_version @@ -988,6 +1009,7 @@ def test_minmax_version if cver >= sver server_connect(port, ctx2) { |ssl| assert_equal vmap[cver][:name], ssl.ssl_version + ssl.puts "abc"; assert_equal "abc\n", ssl.gets } else assert_handshake_error { server_connect(port, ctx2) { } } @@ -1006,6 +1028,7 @@ def test_minmax_version if cver <= sver server_connect(port, ctx1) { |ssl| assert_equal vmap[sver][:name], ssl.ssl_version + ssl.puts "abc"; assert_equal "abc\n", ssl.gets } else assert_handshake_error { server_connect(port, ctx1) { } } @@ -1020,6 +1043,7 @@ def test_minmax_version else assert_equal vmap[cver][:name], ssl.ssl_version end + ssl.puts "abc"; assert_equal "abc\n", ssl.gets } end } @@ -1086,6 +1110,7 @@ def test_renegotiation_cb start_server_version(:SSLv23, ctx_proc) { |port| server_connect(port) { |ssl| assert_equal(1, num_handshakes) + ssl.puts "abc"; assert_equal "abc\n", ssl.gets } } end @@ -1104,6 +1129,7 @@ def test_alpn_protocol_selection_ary ctx.alpn_protocols = advertised server_connect(port, ctx) { |ssl| assert_equal(advertised.first, ssl.alpn_protocol) + ssl.puts "abc"; assert_equal "abc\n", ssl.gets } } end @@ -1226,14 +1252,11 @@ def test_npn_selected_protocol_too_long end def test_close_after_socket_close - server_proc = proc { |ctx, ssl| - # Do nothing - } - start_server(server_proc: server_proc) { |port| + start_server { |port| sock = TCPSocket.new("127.0.0.1", port) ssl = OpenSSL::SSL::SSLSocket.new(sock) - ssl.sync_close = true ssl.connect + ssl.puts "abc"; assert_equal "abc\n", ssl.gets sock.close assert_nothing_raised do ssl.close @@ -1298,6 +1321,7 @@ def test_get_ephemeral_key ctx.ciphers = "DEFAULT:!kRSA:!kEDH" server_connect(port, ctx) { |ssl| assert_instance_of OpenSSL::PKey::EC, ssl.tmp_key + ssl.puts "abc"; assert_equal "abc\n", ssl.gets } end end @@ -1440,6 +1464,7 @@ def test_ecdh_curves assert_equal "secp384r1", ssl.tmp_key.group.curve_name end end + ssl.puts "abc"; assert_equal "abc\n", ssl.gets } if openssl?(1, 0, 2) || libressl?(2, 5, 1) @@ -1455,6 +1480,7 @@ def test_ecdh_curves server_connect(port, ctx) { |ssl| assert_equal "secp521r1", ssl.tmp_key.group.curve_name + ssl.puts "abc"; assert_equal "abc\n", ssl.gets } end end diff --git a/test/mri/openssl/test_ssl_session.rb b/test/mri/openssl/test_ssl_session.rb index 2cb46cd2c3d..e199f86d2b9 100644 --- a/test/mri/openssl/test_ssl_session.rb +++ b/test/mri/openssl/test_ssl_session.rb @@ -113,6 +113,7 @@ def test_resumption non_resumable = nil start_server { |port| server_connect_with_session(port, nil, nil) { |ssl| + ssl.puts "abc"; assert_equal "abc\n", ssl.gets non_resumable = ssl.session } } @@ -198,7 +199,9 @@ def test_server_session_cache first_session = nil 10.times do |i| connections = i - server_connect_with_session(port, nil, first_session) { |ssl| + cctx = OpenSSL::SSL::SSLContext.new + cctx.ssl_version = :TLSv1_2 + server_connect_with_session(port, cctx, first_session) { |ssl| ssl.puts("abc"); assert_equal "abc\n", ssl.gets first_session ||= ssl.session @@ -257,6 +260,8 @@ def test_ctx_server_session_cb connections = nil called = {} + cctx = OpenSSL::SSL::SSLContext.new + cctx.ssl_version = :TLSv1_2 sctx = nil ctx_proc = Proc.new { |ctx| sctx = ctx @@ -292,7 +297,7 @@ def test_ctx_server_session_cb } start_server(ctx_proc: ctx_proc) do |port| connections = 0 - sess0 = server_connect_with_session(port, nil, nil) { |ssl| + sess0 = server_connect_with_session(port, cctx, nil) { |ssl| ssl.puts("abc"); assert_equal "abc\n", ssl.gets assert_equal false, ssl.session_reused? ssl.session @@ -307,7 +312,7 @@ def test_ctx_server_session_cb # Internal cache hit connections = 1 - server_connect_with_session(port, nil, sess0.dup) { |ssl| + server_connect_with_session(port, cctx, sess0.dup) { |ssl| ssl.puts("abc"); assert_equal "abc\n", ssl.gets assert_equal true, ssl.session_reused? ssl.session @@ -328,7 +333,7 @@ def test_ctx_server_session_cb # External cache hit connections = 2 - sess2 = server_connect_with_session(port, nil, sess0.dup) { |ssl| + sess2 = server_connect_with_session(port, cctx, sess0.dup) { |ssl| ssl.puts("abc"); assert_equal "abc\n", ssl.gets if !ssl.session_reused? && openssl?(1, 1, 0) && !openssl?(1, 1, 0, 7) # OpenSSL >= 1.1.0, < 1.1.0g @@ -355,7 +360,7 @@ def test_ctx_server_session_cb # Cache miss connections = 3 - sess3 = server_connect_with_session(port, nil, sess0.dup) { |ssl| + sess3 = server_connect_with_session(port, cctx, sess0.dup) { |ssl| ssl.puts("abc"); assert_equal "abc\n", ssl.gets assert_equal false, ssl.session_reused? ssl.session diff --git a/test/mri/openssl/test_x509name.rb b/test/mri/openssl/test_x509name.rb index 2d92e645be6..e31b5e29fbc 100644 --- a/test/mri/openssl/test_x509name.rb +++ b/test/mri/openssl/test_x509name.rb @@ -371,6 +371,12 @@ def test_to_s assert_equal "DC = org, DC = ruby-lang, " \ "CN = \"\\E3\\83\\95\\E3\\83\\BC, \\E3\\83\\90\\E3\\83\\BC\"", name.to_s(OpenSSL::X509::Name::ONELINE) + + empty = OpenSSL::X509::Name.new + assert_equal "", empty.to_s + assert_equal "", empty.to_s(OpenSSL::X509::Name::COMPAT) + assert_equal "", empty.to_s(OpenSSL::X509::Name::RFC2253) + assert_equal "", empty.to_s(OpenSSL::X509::Name::ONELINE) end def test_to_utf8 @@ -386,6 +392,9 @@ def test_to_utf8 expected = "CN=フー\\, バー,DC=ruby-lang,DC=org".force_encoding("UTF-8") assert_equal expected, str assert_equal Encoding.find("UTF-8"), str.encoding + + empty = OpenSSL::X509::Name.new + assert_equal "", empty.to_utf8 end def test_equals2 @@ -396,10 +405,16 @@ def test_equals2 end def test_spaceship - n1 = OpenSSL::X509::Name.parse_rfc2253 'CN=a' - n2 = OpenSSL::X509::Name.parse_rfc2253 'CN=b' - - assert_equal(-1, n1 <=> n2) + n1 = OpenSSL::X509::Name.new([["CN", "a"]]) + n2 = OpenSSL::X509::Name.new([["CN", "a"]]) + n3 = OpenSSL::X509::Name.new([["CN", "ab"]]) + + assert_equal 0, n1 <=> n2 + assert_equal -1, n1 <=> n3 + assert_equal 0, n2 <=> n1 + assert_equal -1, n2 <=> n3 + assert_equal 1, n3 <=> n1 + assert_equal 1, n3 <=> n2 end def name_hash(name) diff --git a/test/mri/pathname/test_pathname.rb b/test/mri/pathname/test_pathname.rb index ad104d06df5..8a72b8026d0 100644 --- a/test/mri/pathname/test_pathname.rb +++ b/test/mri/pathname/test_pathname.rb @@ -1336,6 +1336,7 @@ def test_find assert_equal([Pathname("d"), Pathname("d/x")], a) skip "no meaning test on Windows" if /mswin|mingw/ =~ RUBY_PLATFORM + skip 'skipped in root privilege' if Process.uid == 0 a = []; assert_raise_with_message(Errno::EACCES, %r{d/x}) do Pathname(".").find(ignore_error: false) {|v| a << v } diff --git a/test/mri/rdoc/test_rdoc_options.rb b/test/mri/rdoc/test_rdoc_options.rb index 760cf9db553..400ed9a5491 100644 --- a/test/mri/rdoc/test_rdoc_options.rb +++ b/test/mri/rdoc/test_rdoc_options.rb @@ -18,6 +18,7 @@ def teardown def test_check_files skip "assumes UNIX permission model" if /mswin|mingw/ =~ RUBY_PLATFORM + skip "skipped in root privilege" if Process.uid == 0 out, err = capture_io do temp_dir do diff --git a/test/mri/rdoc/test_rdoc_rdoc.rb b/test/mri/rdoc/test_rdoc_rdoc.rb index 3244238e3c5..bd4794342c3 100644 --- a/test/mri/rdoc/test_rdoc_rdoc.rb +++ b/test/mri/rdoc/test_rdoc_rdoc.rb @@ -296,6 +296,7 @@ def test_parse_file_encoding def test_parse_file_forbidden skip 'chmod not supported' if Gem.win_platform? + skip 'skipped in root privilege' if Process.uid == 0 @rdoc.store = RDoc::Store.new diff --git a/test/mri/rdoc/test_rdoc_rubygems_hook.rb b/test/mri/rdoc/test_rdoc_rubygems_hook.rb index 2fb79ff577b..2664b35cbbb 100644 --- a/test/mri/rdoc/test_rdoc_rubygems_hook.rb +++ b/test/mri/rdoc/test_rdoc_rubygems_hook.rb @@ -200,6 +200,7 @@ def test_remove def test_remove_unwritable skip 'chmod not supported' if Gem.win_platform? + skip 'skipped in root privilege' if Process.uid == 0 FileUtils.mkdir_p @a.base_dir FileUtils.chmod 0, @a.base_dir @@ -228,6 +229,7 @@ def test_setup def test_setup_unwritable skip 'chmod not supported' if Gem.win_platform? + skip 'skipped in root privilege' if Process.uid == 0 FileUtils.mkdir_p @a.doc_dir FileUtils.chmod 0, @a.doc_dir diff --git a/test/mri/resolv/test_dns.rb b/test/mri/resolv/test_dns.rb index 68d0f83e69d..1b44f32807c 100644 --- a/test/mri/resolv/test_dns.rb +++ b/test/mri/resolv/test_dns.rb @@ -3,8 +3,6 @@ require 'resolv' require 'socket' require 'tempfile' -require 'timeout' -require 'minitest/mock' class TestResolvDNS < Test::Unit::TestCase def setup @@ -248,40 +246,4 @@ def test_too_big_label_address } assert_operator(2**14, :<, m.to_s.length) end - - def test_timeout_without_leaking_file_descriptors_connected - socket = nil - bind_random_port = lambda do |udpsock, bind_host="0.0.0.0"| - socket = udpsock - sleep 3 - end - - Resolv::DNS.stub(:bind_random_port, bind_random_port) do - r = Resolv::DNS.new(nameserver_port: [['127.0.0.1', 53]]) - begin - Timeout.timeout(0.5) { r.getname("8.8.8.8") } - rescue Timeout::Error - end - end - - assert(socket.closed?, "file descriptor leaked") - end - - def test_timeout_without_leaking_file_descriptors_unconnected - socket = nil - bind_random_port = lambda do |udpsock, bind_host="0.0.0.0"| - socket = udpsock - sleep 3 - end - - Resolv::DNS.stub(:bind_random_port, bind_random_port) do - r = Resolv::DNS.new - begin - Timeout.timeout(0.5) { r.getname("8.8.8.8") } - rescue Timeout::Error - end - end - - assert(socket.closed?, "file descriptor leaked") - end end diff --git a/test/mri/ruby/enc/test_grapheme_breaks.rb b/test/mri/ruby/enc/test_grapheme_breaks.rb new file mode 100644 index 00000000000..7f6c7761137 --- /dev/null +++ b/test/mri/ruby/enc/test_grapheme_breaks.rb @@ -0,0 +1,92 @@ +# frozen_string_literal: true +# Copyright © 2018 Martin J. Dürst (duerst@it.aoyama.ac.jp) + +require "test/unit" + +class BreakTest + attr_reader :clusters, :string, :comment, :line_number + + def initialize (line_number, data, comment) + @line_number = line_number + @comment = comment + @clusters = data.sub(/\A\s*÷\s*/, '') + .sub(/\s*÷\s*\z/, '') + .split(/\s*÷\s*/) + .map do |cl| + cl.split(/\s*×\s*/) + .map do |ch| + c = ch.to_i(16) + # eliminate cases with surrogates + raise ArgumentError if 0xD800 <= c and c <= 0xDFFF + c.chr('UTF-8') + end.join + end + @string = @clusters.join + end +end + +class TestGraphemeBreaksFromFile < Test::Unit::TestCase + UNICODE_VERSION = RbConfig::CONFIG['UNICODE_VERSION'] + path = File.expand_path("../../../enc/unicode/data/#{UNICODE_VERSION}", __dir__) + UNICODE_DATA_PATH = File.directory?("#{path}/ucd/auxiliary") ? "#{path}/ucd/auxiliary" : path + GRAPHEME_BREAK_TEST_FILE = File.expand_path("#{UNICODE_DATA_PATH}/GraphemeBreakTest.txt", __dir__) + + def self.file_available? + File.exist? GRAPHEME_BREAK_TEST_FILE + end + + def test_data_files_available + unless TestGraphemeBreaksFromFile.file_available? + skip "Unicode data file GraphemeBreakTest not available in #{UNICODE_DATA_PATH}." + end + end +end + +TestGraphemeBreaksFromFile.file_available? and class TestGraphemeBreaksFromFile + def read_data + tests = [] + IO.foreach(GRAPHEME_BREAK_TEST_FILE, encoding: Encoding::UTF_8) do |line| + if $. == 1 and not line.start_with?("# GraphemeBreakTest-#{UNICODE_VERSION}.txt") + raise "File Version Mismatch" + end + next if /\A#/.match? line + tests << BreakTest.new($., *line.chomp.split('#')) rescue 'whatever' + end + tests + end + + def all_tests + @@tests ||= read_data + rescue Errno::ENOENT + @@tests ||= [] + end + + def test_each_grapheme_cluster + all_tests.each do |test| + expected = test.clusters + actual = test.string.each_grapheme_cluster.to_a + assert_equal expected, actual, + "line #{test.line_number}, expected '#{expected}', " + + "but got '#{actual}', comment: #{test.comment}" + end + end + + def test_backslash_X + all_tests.each do |test| + clusters = test.clusters.dup + string = test.string.dup + removals = 0 + while string.sub!(/\A\X/, '') + removals += 1 + clusters.shift + expected = clusters.join + assert_equal expected, string, + "line #{test.line_number}, removals: #{removals}, expected '#{expected}', " + + "but got '#{string}', comment: #{test.comment}" + end + assert_equal expected, string, + "line #{test.line_number}, after last removal, expected '#{expected}', " + + "but got '#{string}', comment: #{test.comment}" + end + end +end diff --git a/test/mri/ruby/test_array.rb b/test/mri/ruby/test_array.rb index 7e63623b7d1..73a0bf24d03 100644 --- a/test/mri/ruby/test_array.rb +++ b/test/mri/ruby/test_array.rb @@ -534,6 +534,8 @@ def test_collect # Enumerable#collect without block returns an Enumerator. #assert_equal([1, 2, 3], @cls[1, 2, 3].collect) assert_kind_of Enumerator, @cls[1, 2, 3].collect + + assert_equal([[1, 2, 3]], [[1, 2, 3]].collect(&->(a, b, c) {[a, b, c]})) end # also update map! @@ -1631,6 +1633,12 @@ def test_min ary.min(2) {|a,b| a.length <=> b.length }) assert_equal([13, 14], [20, 32, 32, 21, 30, 25, 29, 13, 14].min(2)) assert_equal([2, 4, 6, 7], [2, 4, 8, 6, 7].min(4)) + + class << (obj = Object.new) + def <=>(x) 1 <=> x end + def coerce(x) [x, 1] end + end + assert_same(obj, [obj, 1.0].min) end def test_max @@ -1646,6 +1654,12 @@ def test_max assert_equal(%w[albatross horse], ary.max(2) {|a,b| a.length <=> b.length }) assert_equal([3, 2], [0, 0, 0, 0, 0, 0, 1, 3, 2].max(2)) + + class << (obj = Object.new) + def <=>(x) 1 <=> x end + def coerce(x) [x, 1] end + end + assert_same(obj, [obj, 1.0].max) end def test_uniq @@ -2944,6 +2958,13 @@ def test_sum assert_float_equal(large_number+(small_number*10), [large_number/1r, *[small_number]*10].sum) assert_float_equal(large_number+(small_number*11), [small_number, large_number/1r, *[small_number]*10].sum) assert_float_equal(small_number, [large_number, small_number, -large_number].sum) + assert_equal(+Float::INFINITY, [+Float::INFINITY].sum) + assert_equal(+Float::INFINITY, [0.0, +Float::INFINITY].sum) + assert_equal(+Float::INFINITY, [+Float::INFINITY, 0.0].sum) + assert_equal(-Float::INFINITY, [-Float::INFINITY].sum) + assert_equal(-Float::INFINITY, [0.0, -Float::INFINITY].sum) + assert_equal(-Float::INFINITY, [-Float::INFINITY, 0.0].sum) + assert_predicate([-Float::INFINITY, Float::INFINITY].sum, :nan?) assert_equal("abc", ["a", "b", "c"].sum("")) assert_equal([1, [2], 3], [[1], [[2]], [3]].sum([])) diff --git a/test/mri/ruby/test_autoload.rb b/test/mri/ruby/test_autoload.rb index 9d16a45e233..0220f3e27d4 100644 --- a/test/mri/ruby/test_autoload.rb +++ b/test/mri/ruby/test_autoload.rb @@ -245,6 +245,46 @@ def test_bug_13526 assert_ruby_status([script], '', '[ruby-core:81016] [Bug #13526]') end + def test_autoload_private_constant + Dir.mktmpdir('autoload') do |tmpdir| + File.write(tmpdir+"/zzz.rb", "#{<<~"begin;"}\n#{<<~'end;'}") + begin; + class AutoloadTest + ZZZ = :ZZZ + private_constant :ZZZ + end + end; + assert_separately(%W[-I #{tmpdir}], "#{<<-"begin;"}\n#{<<-'end;'}") + bug = '[ruby-core:85516] [Bug #14469]' + begin; + class AutoloadTest + autoload :ZZZ, "zzz.rb" + end + assert_raise(NameError, bug) {AutoloadTest::ZZZ} + end; + end + end + + def test_autoload_deprecate_constant + Dir.mktmpdir('autoload') do |tmpdir| + File.write(tmpdir+"/zzz.rb", "#{<<~"begin;"}\n#{<<~'end;'}") + begin; + class AutoloadTest + ZZZ = :ZZZ + deprecate_constant :ZZZ + end + end; + assert_separately(%W[-I #{tmpdir}], "#{<<-"begin;"}\n#{<<-'end;'}") + bug = '[ruby-core:85516] [Bug #14469]' + begin; + class AutoloadTest + autoload :ZZZ, "zzz.rb" + end + assert_warning(/ZZZ is deprecated/, bug) {AutoloadTest::ZZZ} + end; + end + end + def add_autoload(path) (@autoload_paths ||= []) << path ::Object.class_eval {autoload(:AutoloadTest, path)} diff --git a/test/mri/ruby/test_complex.rb b/test/mri/ruby/test_complex.rb index ded316841a8..316e3e21ff4 100644 --- a/test/mri/ruby/test_complex.rb +++ b/test/mri/ruby/test_complex.rb @@ -48,8 +48,12 @@ def test_eql_p end def test_hash - assert_kind_of(Integer, Complex(1,2).hash) - assert_kind_of(Integer, Complex(1.0,2.0).hash) + h = Complex(1,2).hash + assert_kind_of(Integer, h) + assert_nothing_raised {h.to_s} + h = Complex(1.0,2.0).hash + assert_kind_of(Integer, h) + assert_nothing_raised {h.to_s} h = {} h[Complex(0)] = 0 diff --git a/test/mri/ruby/test_defined.rb b/test/mri/ruby/test_defined.rb index 54f461ff034..9976db3b6f7 100644 --- a/test/mri/ruby/test_defined.rb +++ b/test/mri/ruby/test_defined.rb @@ -253,4 +253,8 @@ def test_method_by_respond_to_missing assert_equal(nil, obj.func_defined_non_existing_func, bug_11212) assert_equal(true, obj.called, bug_11212) end + + def test_top_level_constant_not_defined + assert_nil(defined?(TestDefined::Object)) + end end diff --git a/test/mri/ruby/test_dir.rb b/test/mri/ruby/test_dir.rb index cead4beb93d..000bc24e85b 100644 --- a/test/mri/ruby/test_dir.rb +++ b/test/mri/ruby/test_dir.rb @@ -156,6 +156,9 @@ def test_glob open(File.join(@root, "}}a"), "wb") {} assert_equal(%w(}}{} }}a).map {|f| File.join(@root, f)}, Dir.glob(File.join(@root, '}}{\{\},a}'))) assert_equal(%w(}}{} }}a b c).map {|f| File.join(@root, f)}, Dir.glob(File.join(@root, '{\}\}{\{\},a},b,c}'))) + assert_raise(ArgumentError) { + Dir.glob([[@root, File.join(@root, "*")].join("\0")]) + } end def test_glob_recursive @@ -229,18 +232,23 @@ def assert_entries(entries, children_only = false) def test_entries assert_entries(Dir.open(@root) {|dir| dir.entries}) assert_entries(Dir.entries(@root).to_a) + assert_raise(ArgumentError) {Dir.entries(@root+"\0")} end def test_foreach + assert_entries(Dir.open(@root) {|dir| dir.each.to_a}) assert_entries(Dir.foreach(@root).to_a) + assert_raise(ArgumentError) {Dir.foreach(@root+"\0").to_a} end def test_children assert_entries(Dir.children(@root), true) + assert_raise(ArgumentError) {Dir.children(@root+"\0")} end def test_each_child assert_entries(Dir.each_child(@root).to_a, true) + assert_raise(ArgumentError) {Dir.each_child(@root+"\0").to_a} end def test_dir_enc @@ -397,6 +405,7 @@ def test_empty? end assert_raise(Errno::ENOENT) {Dir.empty?(@nodir)} assert_not_send([Dir, :empty?, File.join(@root, "b")]) + assert_raise(ArgumentError) {Dir.empty?(@root+"\0")} end def test_glob_gc_for_fd diff --git a/test/mri/ruby/test_enum.rb b/test/mri/ruby/test_enum.rb index 12fc2ee66a8..21672718863 100644 --- a/test/mri/ruby/test_enum.rb +++ b/test/mri/ruby/test_enum.rb @@ -966,6 +966,21 @@ def each assert_float_equal(large_number+(small_number*11), [small_number, large_number/1r, *[small_number]*10].each.sum) assert_float_equal(small_number, [large_number, small_number, -large_number].each.sum) + k = Class.new do + include Enumerable + def initialize(*values) + @values = values + end + def each(&block) + @values.each(&block) + end + end + assert_equal(+Float::INFINITY, k.new(0.0, +Float::INFINITY).sum) + assert_equal(+Float::INFINITY, k.new(+Float::INFINITY, 0.0).sum) + assert_equal(-Float::INFINITY, k.new(0.0, -Float::INFINITY).sum) + assert_equal(-Float::INFINITY, k.new(-Float::INFINITY, 0.0).sum) + assert_predicate(k.new(-Float::INFINITY, Float::INFINITY).sum, :nan?) + assert_equal("abc", ["a", "b", "c"].each.sum("")) assert_equal([1, [2], 3], [[1], [[2]], [3]].each.sum([])) end diff --git a/test/mri/ruby/test_enumerator.rb b/test/mri/ruby/test_enumerator.rb index 235dfbfc9b4..66a45cc14e1 100644 --- a/test/mri/ruby/test_enumerator.rb +++ b/test/mri/ruby/test_enumerator.rb @@ -657,8 +657,9 @@ def test_peek_for_enumerator_objects end def test_uniq - assert_equal([1, 2, 3, 4, 5, 10], - (1..Float::INFINITY).lazy.uniq{|x| (x**2) % 10 }.first(6)) + u = [0, 1, 0, 1].to_enum.lazy.uniq + assert_equal([0, 1], u.force) + assert_equal([0, 1], u.force) end end diff --git a/test/mri/ruby/test_exception.rb b/test/mri/ruby/test_exception.rb index 3e9f4d1bc2a..605248aebdd 100644 --- a/test/mri/ruby/test_exception.rb +++ b/test/mri/ruby/test_exception.rb @@ -698,6 +698,12 @@ def test_cause_at_raised assert_same(a, e.cause.cause) end + def test_cause_at_end + assert_in_out_err([], <<-'end;', [], [/-: unexpected return\n/, /.*undefined local variable or method `n'.*\n/]) + END{n}; END{return} + end; + end + def test_raise_with_cause msg = "[Feature #8257]" cause = ArgumentError.new("foobar") @@ -1007,6 +1013,8 @@ def test_warning_warn def test_kernel_warn_uplevel warning = capture_warning_warn {warn("test warning", uplevel: 0)} assert_equal("#{__FILE__}:#{__LINE__-1}: warning: test warning\n", warning[0]) + assert_raise(ArgumentError) {warn("test warning", uplevel: -1)} + assert_in_out_err(["-e", "warn 'ok', uplevel: 1"], '', [], /warning:/) end def test_warning_warn_invalid_argument @@ -1086,6 +1094,28 @@ def backtrace end; end + def test_blocking_backtrace + assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}") + begin; + class Bug < RuntimeError + def backtrace + IO.readlines(IO::NULL) + end + end + bug = Bug.new '[ruby-core:85939] [Bug #14577]' + n = 10000 + i = 0 + n.times do + begin + raise bug + rescue Bug + i += 1 + end + end + assert_equal(n, i) + end; + end + def test_wrong_backtrace assert_separately([], "#{<<-"begin;"}\n#{<<-"end;"}") begin; @@ -1099,6 +1129,16 @@ def backtrace(a) raise RuntimeError, "hello" } end; + + error_class = Class.new(StandardError) do + def backtrace; :backtrace; end + end + begin + raise error_class + rescue error_class => e + assert_raise(TypeError) {$@} + assert_raise(TypeError) {e.full_message} + end end def test_full_message @@ -1110,5 +1150,38 @@ def test_full_message _, err2, status1 = EnvUtil.invoke_ruby(['-e', "#{test_method}; begin; foo; end"], '', true, true) assert_equal(err2, out1) + + e = RuntimeError.new("testerror") + message = e.full_message(highlight: false) + assert_not_match(/\e/, message) + + bt = ["test:100", "test:99", "test:98", "test:1"] + e = assert_raise(RuntimeError) {raise RuntimeError, "testerror", bt} + + message = e.full_message(highlight: false, order: :top) + assert_not_match(/\e/, message) + assert_operator(message.count("\n"), :>, 2) + assert_operator(message, :start_with?, "test:100: testerror (RuntimeError)\n") + assert_operator(message, :end_with?, "test:1\n") + + message = e.full_message(highlight: false, order: :bottom) + assert_not_match(/\e/, message) + assert_operator(message.count("\n"), :>, 2) + assert_operator(message, :start_with?, "Traceback (most recent call last):") + assert_operator(message, :end_with?, "test:100: testerror (RuntimeError)\n") + + message = e.full_message(highlight: true) + assert_match(/\e/, message) + end + + def test_exception_in_message + code = "#{<<~"begin;"}\n#{<<~'end;'}" + begin; + class Bug14566 < StandardError + def message; raise self.class; end + end + raise Bug14566 + end; + assert_in_out_err([], code, [], /Bug14566/, success: false, timeout: 1) end end diff --git a/test/mri/ruby/test_file.rb b/test/mri/ruby/test_file.rb index 2aa145a303e..ea03b598140 100644 --- a/test/mri/ruby/test_file.rb +++ b/test/mri/ruby/test_file.rb @@ -303,6 +303,14 @@ def test_realpath_taintedness } end + def test_realpath_special_symlink + IO.pipe do |r, w| + if File.pipe?(path = "/dev/fd/#{r.fileno}") + assert_file.identical?(File.realpath(path), path) + end + end + end + def test_realdirpath Dir.mktmpdir('rubytest-realdirpath') {|tmpdir| realdir = File.realpath(tmpdir) diff --git a/test/mri/ruby/test_file_exhaustive.rb b/test/mri/ruby/test_file_exhaustive.rb index 9e8000f41a2..817c3580d14 100644 --- a/test/mri/ruby/test_file_exhaustive.rb +++ b/test/mri/ruby/test_file_exhaustive.rb @@ -891,6 +891,8 @@ def test_expand_path_home_dir_string assert_raise(ArgumentError) { File.expand_path(".", UnknownUserHome) } assert_nothing_raised(ArgumentError) { File.expand_path("#{DRIVE}/", UnknownUserHome) } + ENV["HOME"] = "#{DRIVE}UserHome" + assert_raise(ArgumentError) { File.expand_path("~") } ensure ENV["HOME"] = home end diff --git a/test/mri/ruby/test_hash.rb b/test/mri/ruby/test_hash.rb index 4223aa81406..bdcf0226686 100644 --- a/test/mri/ruby/test_hash.rb +++ b/test/mri/ruby/test_hash.rb @@ -1573,6 +1573,14 @@ def test_transform_keys_bang x.transform_keys!.with_index {|k, i| "#{k}.#{i}" } assert_equal(%w(a!.0 b!.1 c!.2), x.keys) + + x = @cls[1 => :a, -1 => :b] + x.transform_keys! {|k| -k } + assert_equal([-1, :a, 1, :b], x.flatten) + + x = @cls[true => :a, false => :b] + x.transform_keys! {|k| !k } + assert_equal([false, :a, true, :b], x.flatten) end def test_transform_values diff --git a/test/mri/ruby/test_io.rb b/test/mri/ruby/test_io.rb index b6be8481813..54860c1671d 100644 --- a/test/mri/ruby/test_io.rb +++ b/test/mri/ruby/test_io.rb @@ -3753,4 +3753,21 @@ def test_select_exceptfds con.close end end if Socket.const_defined?(:MSG_OOB) + + def test_select_leak + assert_no_memory_leak([], <<-"end;", <<-"end;", rss: true, timeout: 240) + r, w = IO.pipe + rset = [r] + wset = [w] + Thread.new { IO.select(rset, wset, nil, 0) }.join + end; + 20_000.times do + th = Thread.new { IO.select(rset, wset) } + Thread.pass until th.stop? + th.kill + th.join + GC.start + end + end; + end end diff --git a/test/mri/ruby/test_iseq.rb b/test/mri/ruby/test_iseq.rb index 92340bd8fd8..ed88c9b43d0 100644 --- a/test/mri/ruby/test_iseq.rb +++ b/test/mri/ruby/test_iseq.rb @@ -390,4 +390,45 @@ class C # line 7 empty class end } end + + def test_to_binary_with_objects + # conceptually backport from r62856. + # ISeq binary dump doesn't consider alignment in 2.5 and older + skip "does not work on other than x86" unless /x(?:86|64)|i\d86/ =~ RUBY_PLATFORM + code = "[]"+100.times.map{|i|"< e + skip e.message if /compile with coverage/ =~ e.message + raise + end + iseq2 = RubyVM::InstructionSequence.load_from_binary(bin) + assert_equal(iseq2.to_a, iseq.to_a) + end + + def test_to_binary_tracepoint + # conceptually backport from r62856. + # ISeq binary dump doesn't consider alignment in 2.5 and older + skip "does not work on other than x86" unless /x(?:86|64)|i\d86/ =~ RUBY_PLATFORM + filename = "#{File.basename(__FILE__)}_#{__LINE__}" + iseq = RubyVM::InstructionSequence.compile("x = 1\n y = 2", filename) + # conceptually partial backport from r63103, r65567. + # All ISeq#to_binary should rescue to skip when running with coverage. + # current trunk (2.6-) has assert_iseq_to_binary. + begin + iseq_bin = iseq.to_binary + rescue RuntimeError => e + skip e.message if /compile with coverage/ =~ e.message + raise + end + ary = [] + TracePoint.new(:line){|tp| + next unless tp.path == filename + ary << [tp.path, tp.lineno] + }.enable{ + ISeq.load_from_binary(iseq_bin).eval + } + assert_equal [[filename, 1], [filename, 2]], ary, '[Bug #14702]' + end end diff --git a/test/mri/ruby/test_keyword.rb b/test/mri/ruby/test_keyword.rb index f4cc7bc9d63..8a45016c138 100644 --- a/test/mri/ruby/test_keyword.rb +++ b/test/mri/ruby/test_keyword.rb @@ -547,7 +547,8 @@ def m(a: []) def test_dynamic_symbol_keyword bug10266 = '[ruby-dev:48564] [Bug #10266]' - assert_separately(['-', bug10266], <<-'end;') # do + assert_separately(['-', bug10266], "#{<<~"begin;"}\n#{<<~'end;'}") + begin; bug = ARGV.shift "hoge".to_sym assert_nothing_raised(bug) {eval("def a(hoge:); end")} @@ -677,4 +678,8 @@ def obj.t(x:, y:, z: nil) end obj.t(42) end end + + def test_splat_empty_hash_with_block_passing + assert_valid_syntax("bug15087(**{}, &nil)") + end end diff --git a/test/mri/ruby/test_lazy_enumerator.rb b/test/mri/ruby/test_lazy_enumerator.rb index 22028283ce0..03371c912a4 100644 --- a/test/mri/ruby/test_lazy_enumerator.rb +++ b/test/mri/ruby/test_lazy_enumerator.rb @@ -569,4 +569,13 @@ def test_symbol_chain [1, 2, 3].lazy.map(&:undefined).map(&:to_s).force end end + + def test_uniq + u = (1..Float::INFINITY).lazy.uniq do |x| + raise "too big" if x > 10000 + (x**2) % 10 + end + assert_equal([1, 2, 3, 4, 5, 10], u.first(6)) + assert_equal([1, 2, 3, 4, 5, 10], u.first(6)) + end end diff --git a/test/mri/ruby/test_marshal.rb b/test/mri/ruby/test_marshal.rb index 80ec0b1684c..e269428dda3 100644 --- a/test/mri/ruby/test_marshal.rb +++ b/test/mri/ruby/test_marshal.rb @@ -772,4 +772,11 @@ def test_marshal_dump_recursion Marshal.dump(Bug12974.new) end end + + Bug14314 = Struct.new(:foo, keyword_init: true) + + def test_marshal_keyword_init_struct + obj = Bug14314.new(foo: 42) + assert_equal obj, Marshal.load(Marshal.dump(obj)) + end end diff --git a/test/mri/ruby/test_method.rb b/test/mri/ruby/test_method.rb index e0166623759..fe0171dd57d 100644 --- a/test/mri/ruby/test_method.rb +++ b/test/mri/ruby/test_method.rb @@ -782,6 +782,19 @@ def o.bar; :bar; end assert_equal(:bar, m.call, feature8391) end + def test_singleton_method_prepend + bug14658 = '[Bug #14658]' + c1 = Class.new + o = c1.new + def o.bar; :bar; end + class << o; prepend Module.new; end + m = assert_nothing_raised(NameError, bug14658) {o.singleton_method(:bar)} + assert_equal(:bar, m.call, bug14658) + + o = Object.new + assert_raise(NameError, bug14658) {o.singleton_method(:bar)} + end + Feature9783 = '[ruby-core:62212] [Feature #9783]' def assert_curry_three_args(m) @@ -926,6 +939,11 @@ def test_super_method_with_prepended_module assert_equal(m1.source_location, m2.source_location, bug) end + def test_super_method_after_bind + assert_nil String.instance_method(:length).bind(String.new).super_method, + '[ruby-core:85231] [Bug #14421]' + end + def rest_parameter(*rest) rest end diff --git a/test/mri/ruby/test_mixed_unicode_escapes.rb b/test/mri/ruby/test_mixed_unicode_escapes.rb index 09240d8ab27..f0b4cc691f0 100644 --- a/test/mri/ruby/test_mixed_unicode_escapes.rb +++ b/test/mri/ruby/test_mixed_unicode_escapes.rb @@ -13,14 +13,18 @@ def test_basic # 8-bit character escapes are okay. assert_equal("B\xFF", "\u0042\xFF") - # sjis mb chars mixed with Unicode shound not work + # sjis mb chars mixed with Unicode should not work assert_raise(SyntaxError) { eval %q("\u1234")} assert_raise(SyntaxError) { eval %q("\u{1234}")} + # also should not work for Regexp + assert_raise(SyntaxError) { eval %q(/#{"\u1234"}#{""}/)} + assert_raise(RegexpError) { eval %q(/\u{1234}#{nil}/)} + assert_raise(RegexpError) { eval %q(/#{nil}\u1234/)} + # String interpolation turns into an expression and we get # a different kind of error, but we still can't mix these assert_raise(Encoding::CompatibilityError) { eval %q("\u{1234}#{nil}")} assert_raise(Encoding::CompatibilityError) { eval %q("#{nil}\u1234")} - end end diff --git a/test/mri/ruby/test_module.rb b/test/mri/ruby/test_module.rb index 9980844eb41..cc5894433ad 100644 --- a/test/mri/ruby/test_module.rb +++ b/test/mri/ruby/test_module.rb @@ -1352,21 +1352,55 @@ def test_attr_writer_with_no_arguments assert_raise(ArgumentError, bug8540) { c.new.send :foo= } end - def test_private_constant + def test_private_constant_in_class c = Class.new c.const_set(:FOO, "foo") assert_equal("foo", c::FOO) c.private_constant(:FOO) - assert_raise(NameError) { c::FOO } + e = assert_raise(NameError) {c::FOO} + assert_equal(c, e.receiver) + assert_equal(:FOO, e.name) assert_equal("foo", c.class_eval("FOO")) assert_equal("foo", c.const_get("FOO")) $VERBOSE, verbose = nil, $VERBOSE c.const_set(:FOO, "foo") $VERBOSE = verbose - assert_raise(NameError) { c::FOO } - assert_raise_with_message(NameError, /#{c}::FOO/) do + e = assert_raise(NameError) {c::FOO} + assert_equal(c, e.receiver) + assert_equal(:FOO, e.name) + e = assert_raise_with_message(NameError, /#{c}::FOO/) do Class.new(c)::FOO end + assert_equal(c, e.receiver) + assert_equal(:FOO, e.name) + end + + def test_private_constant_in_module + m = Module.new + m.const_set(:FOO, "foo") + assert_equal("foo", m::FOO) + m.private_constant(:FOO) + e = assert_raise(NameError) {m::FOO} + assert_equal(m, e.receiver) + assert_equal(:FOO, e.name) + assert_equal("foo", m.class_eval("FOO")) + assert_equal("foo", m.const_get("FOO")) + $VERBOSE, verbose = nil, $VERBOSE + m.const_set(:FOO, "foo") + $VERBOSE = verbose + e = assert_raise(NameError) {m::FOO} + assert_equal(m, e.receiver) + assert_equal(:FOO, e.name) + e = assert_raise(NameError, /#{m}::FOO/) do + Module.new {include m}::FOO + end + assert_equal(m, e.receiver) + assert_equal(:FOO, e.name) + e = assert_raise(NameError, /#{m}::FOO/) do + Class.new {include m}::FOO + end + assert_equal(m, e.receiver) + assert_equal(:FOO, e.name) end def test_private_constant2 @@ -1876,6 +1910,25 @@ def test_prepend_module_with_no_args assert_raise(ArgumentError) { Module.new { prepend } } end + def test_prepend_private_super + wrapper = Module.new do + def wrapped + super + 1 + end + end + + klass = Class.new do + prepend wrapper + + def wrapped + 1 + end + private :wrapped + end + + assert_equal(2, klass.new.wrapped) + end + def test_class_variables m = Module.new m.class_variable_set(:@@foo, 1) diff --git a/test/mri/ruby/test_numeric.rb b/test/mri/ruby/test_numeric.rb index 02a6b8f4a8a..6efc40320a7 100644 --- a/test/mri/ruby/test_numeric.rb +++ b/test/mri/ruby/test_numeric.rb @@ -400,6 +400,11 @@ def test_pow 2120078484650058507891187874713297895455. pow(5478118174010360425845660566650432540723, 5263488859030795548286226023720904036518)) + + assert_equal(12, 12.pow(1, 10000000000), '[Bug #14259]') + assert_equal(12, 12.pow(1, 10000000001), '[Bug #14259]') + assert_equal(12, 12.pow(1, 10000000002), '[Bug #14259]') + assert_equal(17298641040, 12.pow(72387894339363242, 243682743764), '[Bug #14259]') end end diff --git a/test/mri/ruby/test_optimization.rb b/test/mri/ruby/test_optimization.rb index 11cf1ffbfb6..6b08d07fe29 100644 --- a/test/mri/ruby/test_optimization.rb +++ b/test/mri/ruby/test_optimization.rb @@ -689,7 +689,7 @@ def test_peephole_optimization_without_trace end def test_clear_unreachable_keyword_args - assert_separately [], <<-END + assert_separately [], <<-END, timeout: 15 script = <<-EOS if true else @@ -703,10 +703,26 @@ def test_clear_unreachable_keyword_args END end + def test_callinfo_unreachable_path + assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}") + begin; + iseq = RubyVM::InstructionSequence.compile("if false; foo(bar: :baz); else :ok end") + bin = iseq.to_binary + iseq = RubyVM::InstructionSequence.load_from_binary(bin) + assert_instance_of(RubyVM::InstructionSequence, iseq) + assert_equal(:ok, iseq.eval) + end; + end + def test_side_effect_in_popped_splat bug = '[ruby-core:84340] [Bug #14201]' eval("{**(bug = nil; {})};42") assert_nil(bug) + + bug = '[ruby-core:85486] [Bug #14459]' + h = {} + assert_equal(bug, eval('{ok: 42, **h}; bug')) + assert_equal(:ok, eval('{ok: bug = :ok, **h}; bug')) end def test_overwritten_blockparam @@ -718,4 +734,30 @@ def obj.a(&block) end assert_equal(:ok, obj.a()) end + + def test_unconditional_branch_to_leave_block + assert_valid_syntax("#{<<~"begin;"}\n#{<<~'end;'}") + begin; + tap {true || tap {}} + end; + end + + def test_jump_elimination_with_optimized_out_block + x = Object.new + def x.bug(obj) + if obj || obj + obj = obj + else + raise "[ruby-core:87830] [Bug #14897]" + end + obj + end + assert_equal(:ok, x.bug(:ok)) + end + + def test_peephole_jump_after_newarray + i = 0 + %w(1) || 2 while (i += 1) < 100 + assert_equal(100, i) + end end diff --git a/test/mri/ruby/test_pack.rb b/test/mri/ruby/test_pack.rb index 62a7a54e8db..aec418913e7 100644 --- a/test/mri/ruby/test_pack.rb +++ b/test/mri/ruby/test_pack.rb @@ -548,6 +548,9 @@ def test_pack_unpack_atmark assert_equal([1, 2], "\x01\x00\x00\x02".unpack("C@3C")) assert_equal([nil], "\x00".unpack("@1C")) # is it OK? assert_raise(ArgumentError) { "\x00".unpack("@2C") } + + pos = RbConfig::LIMITS["UINTPTR_MAX"] - 99 # -100 + assert_raise(RangeError) {"0123456789".unpack("@#{pos}C10")} end def test_pack_unpack_percent @@ -857,4 +860,20 @@ def test_unpack1 assert_equal "hogefuga", "aG9nZWZ1Z2E=".unpack1("m") assert_equal "01000001", "A".unpack1("B*") end + + def test_pack_infection + tainted_array_string = ["123456"] + tainted_array_string.first.taint + ['a', 'A', 'Z', 'B', 'b', 'H', 'h', 'u', 'M', 'm', 'P', 'p'].each do |f| + assert_predicate(tainted_array_string.pack(f), :tainted?) + end + end + + def test_unpack_infection + tainted_string = "123456" + tainted_string.taint + ['a', 'A', 'Z', 'B', 'b', 'H', 'h', 'u', 'M', 'm'].each do |f| + assert_predicate(tainted_string.unpack(f).first, :tainted?) + end + end end diff --git a/test/mri/ruby/test_parse.rb b/test/mri/ruby/test_parse.rb index e26bcdc07e1..15c6245bac2 100644 --- a/test/mri/ruby/test_parse.rb +++ b/test/mri/ruby/test_parse.rb @@ -746,6 +746,12 @@ def foo end END end + assert_raise(SyntaxError) do + eval "#{<<~"begin;"}\n#{<<~'end;'}", nil, __FILE__, __LINE__+1 + begin; + x, true + end; + end end def test_block_dup diff --git a/test/mri/ruby/test_proc.rb b/test/mri/ruby/test_proc.rb index 68455c1bcca..e6618745f14 100644 --- a/test/mri/ruby/test_proc.rb +++ b/test/mri/ruby/test_proc.rb @@ -1325,7 +1325,7 @@ def test_local_variable_set end def test_local_variable_set_wb - assert_ruby_status([], <<-'end;', '[Bug #13605]') + assert_ruby_status([], <<-'end;', '[Bug #13605]', timeout: 15) b = binding n = 20_000 diff --git a/test/mri/ruby/test_rational.rb b/test/mri/ruby/test_rational.rb index d94ded3fe3f..21524867425 100644 --- a/test/mri/ruby/test_rational.rb +++ b/test/mri/ruby/test_rational.rb @@ -42,7 +42,9 @@ def test_eql_p end def test_hash - assert_kind_of(Integer, Rational(1,2).hash) + h = Rational(1,2).hash + assert_kind_of(Integer, h) + assert_nothing_raised {h.to_s} h = {} h[Rational(0)] = 0 @@ -108,6 +110,10 @@ def test_conv assert_equal(Rational(3),Rational('3')) assert_equal(Rational(1),Rational('3.0','3.0')) assert_equal(Rational(1),Rational('3/3','3/3')) + assert_equal(Rational(111, 10), Rational('1.11e+1')) + assert_equal(Rational(111, 10), Rational('1.11e1')) + assert_equal(Rational(111, 100), Rational('1.11e0')) + assert_equal(Rational(111, 1000), Rational('1.11e-1')) assert_raise(TypeError){Rational(nil)} assert_raise(ArgumentError){Rational('')} assert_raise_with_message(ArgumentError, /\u{221a 2668}/) { diff --git a/test/mri/ruby/test_refinement.rb b/test/mri/ruby/test_refinement.rb index 3aab6e4f1e5..5583ce6a7a4 100644 --- a/test/mri/ruby/test_refinement.rb +++ b/test/mri/ruby/test_refinement.rb @@ -2055,6 +2055,25 @@ def foo INPUT end + def test_super_from_refined_module + a = EnvUtil.labeled_module("A") do + def foo;"[A#{super}]";end + end + b = EnvUtil.labeled_class("B") do + def foo;"[B]";end + end + c = EnvUtil.labeled_class("C", b) do + include a + def foo;"[C#{super}]";end + end + d = EnvUtil.labeled_module("D") do + refine(a) do + def foo;end + end + end + assert_equal("[C[A[B]]]", c.new.foo, '[ruby-dev:50390] [Bug #14232]') + end + private def eval_using(mod, s) diff --git a/test/mri/ruby/test_rubyoptions.rb b/test/mri/ruby/test_rubyoptions.rb index 083dcec027c..54213c4698a 100644 --- a/test/mri/ruby/test_rubyoptions.rb +++ b/test/mri/ruby/test_rubyoptions.rb @@ -344,6 +344,9 @@ def test_shebang %w[4], [], bug4118) assert_ruby_status(%w[], "#! ruby -- /", '[ruby-core:82267] [Bug #13786]') + + assert_ruby_status(%w[], "#!") + assert_in_out_err(%w[-c], "#!", ["Syntax OK"]) end def test_flag_in_shebang @@ -983,4 +986,11 @@ def test_cwd_encoding end end end + + def test_argv_tainted + assert_separately(%w[- arg], "#{<<~"begin;"}\n#{<<~'end;'}") + begin; + assert_predicate(ARGV[0], :tainted?, '[ruby-dev:50596] [Bug #14941]') + end; + end end diff --git a/test/mri/ruby/test_settracefunc.rb b/test/mri/ruby/test_settracefunc.rb index 69ddc059796..3085c0902af 100644 --- a/test/mri/ruby/test_settracefunc.rb +++ b/test/mri/ruby/test_settracefunc.rb @@ -855,6 +855,21 @@ def test_tracepoint_exception_at_return end end + def test_tracepoint_exception_at_c_return + assert_nothing_raised(Timeout::Error, 'infinite trace') do + assert_normal_exit %q{ + begin + TracePoint.new(:c_return){|tp| + raise + }.enable{ + tap{ itself } + } + rescue + end + }, '', timeout: 3 + end + end + def test_tracepoint_with_multithreads assert_nothing_raised do TracePoint.new{ diff --git a/test/mri/ruby/test_signal.rb b/test/mri/ruby/test_signal.rb index df319c91116..2d98b0b564a 100644 --- a/test/mri/ruby/test_signal.rb +++ b/test/mri/ruby/test_signal.rb @@ -11,14 +11,14 @@ def test_signal Process.kill :INT, Process.pid 10.times do break if 2 == x - sleep EnvUtil.apply_timeout_scale(0.1) + sleep 0.1 end assert_equal 2, x Signal.trap(:INT) { raise "Interrupt" } assert_raise_with_message(RuntimeError, /Interrupt/) { Process.kill :INT, Process.pid - sleep EnvUtil.apply_timeout_scale(1) + sleep 0.1 } ensure Signal.trap :INT, oldtrap if oldtrap diff --git a/test/mri/ruby/test_string.rb b/test/mri/ruby/test_string.rb index 8645acd6b2c..39558e1e461 100644 --- a/test/mri/ruby/test_string.rb +++ b/test/mri/ruby/test_string.rb @@ -970,6 +970,7 @@ def test_chars def test_each_grapheme_cluster [ + "\u{0D 0A}", "\u{20 200d}", "\u{600 600}", "\u{600 20}", @@ -982,11 +983,18 @@ def test_each_grapheme_cluster "\u{1f469 200d 2764 fe0f 200d 1f469}", ].each do |g| assert_equal [g], g.each_grapheme_cluster.to_a + assert_equal 1, g.each_grapheme_cluster.size + end + + [ + ["\u{a 308}", ["\u000A", "\u0308"]], + ["\u{d 308}", ["\u000D", "\u0308"]], + ["abc", ["a", "b", "c"]], + ].each do |str, grapheme_clusters| + assert_equal grapheme_clusters, str.each_grapheme_cluster.to_a + assert_equal grapheme_clusters.size, str.each_grapheme_cluster.size end - assert_equal ["\u000A", "\u0308"], "\u{a 308}".each_grapheme_cluster.to_a - assert_equal ["\u000D", "\u0308"], "\u{d 308}".each_grapheme_cluster.to_a - assert_equal ["a", "b", "c"], "abc".b.each_grapheme_cluster.to_a s = ("x"+"\u{10ABCD}"*250000) assert_empty(s.each_grapheme_cluster {s.clear}) end @@ -1121,6 +1129,10 @@ def test_each_line_chomp res = [] S("\r\n").each_line(chomp: true) {|x| res << x} assert_equal([S("")], res) + + res = [] + S("a\n b\n").each_line(" ", chomp: true) {|x| res << x} + assert_equal([S("a\n"), S("b\n")], res) end def test_lines @@ -1528,6 +1540,8 @@ def test_scan assert_nil($~) assert_equal(3, S("hello hello hello").scan("hello".taint).count(&:tainted?)) + + assert_equal(%w[1 2 3], S("a1 a2 a3").scan(/a\K./)) end def test_size @@ -3085,6 +3099,12 @@ def test_chr assert_equal("\u3042", "\u3042\u3043".chr) assert_equal('', ''.chr) end + + def test_substr_code_range + data = "\xff" + "a"*200 + assert_not_predicate(data, :valid_encoding?) + assert_predicate(data[100..-1], :valid_encoding?) + end end class TestString2 < TestString diff --git a/test/mri/ruby/test_super.rb b/test/mri/ruby/test_super.rb index 9691116fb40..cf7580ab00d 100644 --- a/test/mri/ruby/test_super.rb +++ b/test/mri/ruby/test_super.rb @@ -544,4 +544,20 @@ def test_public_zsuper_with_prepend c.new } end + + class TestFor_super_with_modified_rest_parameter_base + def foo *args + args + end + end + + class TestFor_super_with_modified_rest_parameter < TestFor_super_with_modified_rest_parameter_base + def foo *args + args = 13 + super + end + end + def test_super_with_modified_rest_parameter + assert_equal [13], TestFor_super_with_modified_rest_parameter.new.foo + end end diff --git a/test/mri/ruby/test_syntax.rb b/test/mri/ruby/test_syntax.rb index aa82f529879..7e25d965dfe 100644 --- a/test/mri/ruby/test_syntax.rb +++ b/test/mri/ruby/test_syntax.rb @@ -179,6 +179,11 @@ def test_keyword_empty_splat bug13756 = '[ruby-core:82113] [Bug #13756]' assert_valid_syntax("defined? foo(**{})", bug13756) end; + assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}") + begin; + bug15271 = '[ruby-core:89648] [Bug #15271]' + assert_valid_syntax("a **{}", bug15271) + end; end def test_keyword_self_reference @@ -667,6 +672,12 @@ def test_dedented_heredoc_expr_at_beginning assert_dedented_heredoc(expected, result) end + def test_dedented_heredoc_expr_string + result = ' one#{" two "}'"\n" + expected = 'one#{" two "}'"\n" + assert_dedented_heredoc(expected, result) + end + def test_lineno_after_heredoc bug7559 = '[ruby-dev:46737]' expected, _, actual = __LINE__, < 0 - assert Gem::Specification.find_all_by_name('x').length == 0 + assert Gem::Specification.find_all_by_name('x').length.zero? end def test_execute_all diff --git a/test/mri/rubygems/test_gem_dependency_installer.rb b/test/mri/rubygems/test_gem_dependency_installer.rb index e55cc75682b..3d762916685 100644 --- a/test/mri/rubygems/test_gem_dependency_installer.rb +++ b/test/mri/rubygems/test_gem_dependency_installer.rb @@ -424,7 +424,7 @@ def test_install_dependency_existing_extension extconf_rb = File.join @gemhome, 'gems', 'e-1', 'extconf.rb' FileUtils.mkdir_p File.dirname extconf_rb - open extconf_rb, 'w' do |io| + File.open extconf_rb, 'w' do |io| io.write <<-EXTCONF_RB require 'mkmf' create_makefile 'e' diff --git a/test/mri/rubygems/test_gem_doctor.rb b/test/mri/rubygems/test_gem_doctor.rb index 39b8a116928..8db65d70cef 100644 --- a/test/mri/rubygems/test_gem_doctor.rb +++ b/test/mri/rubygems/test_gem_doctor.rb @@ -24,7 +24,7 @@ def test_doctor FileUtils.rm b.spec_file - open c.spec_file, 'w' do |io| + File.open c.spec_file, 'w' do |io| io.write 'this will raise an exception when evaluated.' end @@ -77,7 +77,7 @@ def test_doctor_dry_run FileUtils.rm b.spec_file - open c.spec_file, 'w' do |io| + File.open c.spec_file, 'w' do |io| io.write 'this will raise an exception when evaluated.' end diff --git a/test/mri/rubygems/test_gem_ext_builder.rb b/test/mri/rubygems/test_gem_ext_builder.rb index d142ef28daa..3dabd3e350f 100644 --- a/test/mri/rubygems/test_gem_ext_builder.rb +++ b/test/mri/rubygems/test_gem_ext_builder.rb @@ -32,7 +32,7 @@ def test_class_make results = [] Dir.chdir @ext do - open 'Makefile', 'w' do |io| + File.open 'Makefile', 'w' do |io| io.puts <<-MAKEFILE all: \t@#{Gem.ruby} -e "puts %Q{all: \#{ENV['DESTDIR']}}" @@ -72,7 +72,7 @@ def test_class_make_no_clean results = [] Dir.chdir @ext do - open 'Makefile', 'w' do |io| + File.open 'Makefile', 'w' do |io| io.puts <<-MAKEFILE all: \t@#{Gem.ruby} -e "puts %Q{all: \#{ENV['DESTDIR']}}" @@ -107,7 +107,7 @@ def test_build_extensions extconf_rb = File.join ext_dir, 'extconf.rb' - open extconf_rb, 'w' do |f| + File.open extconf_rb, 'w' do |f| f.write <<-'RUBY' require 'mkmf' @@ -168,7 +168,7 @@ def Gem.install_extension_in_lib extconf_rb = File.join ext_dir, 'extconf.rb' - open extconf_rb, 'w' do |f| + File.open extconf_rb, 'w' do |f| f.write <<-'RUBY' require 'mkmf' @@ -290,7 +290,7 @@ def test_build_extensions_with_build_args FileUtils.mkdir_p @spec.gem_dir - open File.join(@spec.gem_dir, "extconf.rb"), "w" do |f| + File.open File.join(@spec.gem_dir, "extconf.rb"), "w" do |f| f.write <<-'RUBY' puts "IN EXTCONF" extconf_args = File.join File.dirname(__FILE__), 'extconf_args' @@ -323,7 +323,7 @@ def test_initialize build_info_file = File.join build_info_dir, "#{@spec.full_name}.info" - open build_info_file, 'w' do |io| + File.open build_info_file, 'w' do |io| io.puts '--with-foo-dir=/nonexistent' end diff --git a/test/mri/rubygems/test_gem_gemcutter_utilities.rb b/test/mri/rubygems/test_gem_gemcutter_utilities.rb index c3f9a7ea184..286d59a7870 100644 --- a/test/mri/rubygems/test_gem_gemcutter_utilities.rb +++ b/test/mri/rubygems/test_gem_gemcutter_utilities.rb @@ -31,7 +31,7 @@ def test_alternate_key_alternate_host FileUtils.mkdir_p File.dirname Gem.configuration.credentials_path - open Gem.configuration.credentials_path, 'w' do |f| + File.open Gem.configuration.credentials_path, 'w' do |f| f.write keys.to_yaml end @@ -46,7 +46,7 @@ def test_api_key keys = { :rubygems_api_key => 'KEY' } FileUtils.mkdir_p File.dirname Gem.configuration.credentials_path - open Gem.configuration.credentials_path, 'w' do |f| + File.open Gem.configuration.credentials_path, 'w' do |f| f.write keys.to_yaml end @@ -59,7 +59,7 @@ def test_api_key_override keys = { :rubygems_api_key => 'KEY', :other => 'OTHER' } FileUtils.mkdir_p File.dirname Gem.configuration.credentials_path - open Gem.configuration.credentials_path, 'w' do |f| + File.open Gem.configuration.credentials_path, 'w' do |f| f.write keys.to_yaml end @@ -163,7 +163,7 @@ def test_sign_in_with_other_credentials_doesnt_overwrite_other_keys other_api_key = 'f46dbb18bb6a9c97cdc61b5b85c186a17403cdcbf' FileUtils.mkdir_p File.dirname(Gem.configuration.credentials_path) - open Gem.configuration.credentials_path, 'w' do |f| + File.open Gem.configuration.credentials_path, 'w' do |f| f.write Hash[:other_api_key, other_api_key].to_yaml end util_sign_in [api_key, 200, 'OK'] diff --git a/test/mri/rubygems/test_gem_indexer.rb b/test/mri/rubygems/test_gem_indexer.rb index a4a966e8de3..5a9075e676a 100644 --- a/test/mri/rubygems/test_gem_indexer.rb +++ b/test/mri/rubygems/test_gem_indexer.rb @@ -39,8 +39,7 @@ def setup def test_initialize assert_equal @tempdir, @indexer.dest_directory - assert_equal File.join(Dir.tmpdir, "gem_generate_index_#{$$}"), - @indexer.directory + assert_match %r{#{Dir.mktmpdir('gem_generate_index').match(/.*-/)}}, @indexer.directory indexer = Gem::Indexer.new @tempdir assert indexer.build_modern diff --git a/test/mri/rubygems/test_gem_install_update_options.rb b/test/mri/rubygems/test_gem_install_update_options.rb index e2d546307d1..371e408d274 100644 --- a/test/mri/rubygems/test_gem_install_update_options.rb +++ b/test/mri/rubygems/test_gem_install_update_options.rb @@ -141,6 +141,8 @@ def test_user_install_enabled def test_user_install_disabled_read_only if win_platform? skip('test_user_install_disabled_read_only test skipped on MS Windows') + elsif Process.uid.zero? + skip('test_user_install_disabled_read_only test skipped in root privilege') else @cmd.handle_options %w[--no-user-install] diff --git a/test/mri/rubygems/test_gem_installer.rb b/test/mri/rubygems/test_gem_installer.rb index 39095c7deef..93b04824071 100644 --- a/test/mri/rubygems/test_gem_installer.rb +++ b/test/mri/rubygems/test_gem_installer.rb @@ -140,7 +140,7 @@ def test_check_executable_overwrite_format_executable s.require_path = 'lib' end - open File.join(util_inst_bindir, 'executable'), 'w' do |io| + File.open File.join(util_inst_bindir, 'executable'), 'w' do |io| io.write <<-EXEC #!/usr/local/bin/ruby # @@ -437,6 +437,8 @@ def test_generate_bin_script_no_perms if win_platform? skip('test_generate_bin_script_no_perms skipped on MS Windows') + elsif Process.uid.zero? + skip('test_generate_bin_script_no_perms skipped in root privilege') else FileUtils.chmod 0000, util_inst_bindir @@ -529,6 +531,8 @@ def test_generate_bin_symlink_no_perms if win_platform? skip('test_generate_bin_symlink_no_perms skipped on MS Windows') + elsif Process.uid.zero? + skip('test_user_install_disabled_read_only test skipped in root privilege') else FileUtils.chmod 0000, util_inst_bindir diff --git a/test/mri/rubygems/test_gem_package.rb b/test/mri/rubygems/test_gem_package.rb index cec1981c4c1..d1664cf2858 100644 --- a/test/mri/rubygems/test_gem_package.rb +++ b/test/mri/rubygems/test_gem_package.rb @@ -24,7 +24,7 @@ def setup end def test_class_new_old_format - open 'old_format.gem', 'wb' do |io| + File.open 'old_format.gem', 'wb' do |io| io.write SIMPLE_GEM end @@ -45,7 +45,7 @@ def test_add_checksums FileUtils.mkdir 'lib' - open 'lib/code.rb', 'w' do |io| + File.open 'lib/code.rb', 'w' do |io| io.write '# lib/code.rb' end @@ -110,8 +110,8 @@ def test_add_files FileUtils.mkdir_p 'lib/empty' - open 'lib/code.rb', 'w' do |io| io.write '# lib/code.rb' end - open 'lib/extra.rb', 'w' do |io| io.write '# lib/extra.rb' end + File.open 'lib/code.rb', 'w' do |io| io.write '# lib/code.rb' end + File.open 'lib/extra.rb', 'w' do |io| io.write '# lib/extra.rb' end package = Gem::Package.new 'bogus.gem' package.spec = spec @@ -140,7 +140,7 @@ def test_add_files_symlink spec.files = %w[lib/code.rb lib/code_sym.rb] FileUtils.mkdir_p 'lib' - open 'lib/code.rb', 'w' do |io| io.write '# lib/code.rb' end + File.open 'lib/code.rb', 'w' do |io| io.write '# lib/code.rb' end # NOTE: 'code.rb' is correct, because it's relative to lib/code_sym.rb File.symlink('code.rb', 'lib/code_sym.rb') @@ -179,7 +179,7 @@ def test_build FileUtils.mkdir 'lib' - open 'lib/code.rb', 'w' do |io| + File.open 'lib/code.rb', 'w' do |io| io.write '# lib/code.rb' end @@ -218,7 +218,7 @@ def test_build_auto_signed FileUtils.mkdir 'lib' - open 'lib/code.rb', 'w' do |io| + File.open 'lib/code.rb', 'w' do |io| io.write '# lib/code.rb' end @@ -261,7 +261,7 @@ def test_build_auto_signed_encrypted_key FileUtils.mkdir 'lib' - open 'lib/code.rb', 'w' do |io| + File.open 'lib/code.rb', 'w' do |io| io.write '# lib/code.rb' end @@ -311,7 +311,7 @@ def test_build_signed FileUtils.mkdir 'lib' - open 'lib/code.rb', 'w' do |io| + File.open 'lib/code.rb', 'w' do |io| io.write '# lib/code.rb' end @@ -348,7 +348,7 @@ def test_build_signed_encrypted_key FileUtils.mkdir 'lib' - open 'lib/code.rb', 'w' do |io| + File.open 'lib/code.rb', 'w' do |io| io.write '# lib/code.rb' end @@ -408,7 +408,7 @@ def test_extract_files_empty end end - open 'empty.gem', 'wb' do |io| + File.open 'empty.gem', 'wb' do |io| io.write gem.string end @@ -455,6 +455,31 @@ def test_extract_tar_gz_symlink_relative_path File.read(extracted) end + def test_extract_symlink_parent + skip 'symlink not supported' if Gem.win_platform? + + package = Gem::Package.new @gem + + tgz_io = util_tar_gz do |tar| + tar.mkdir 'lib', 0755 + tar.add_symlink 'lib/link', '../..', 0644 + tar.add_file 'lib/link/outside.txt', 0644 do |io| io.write 'hi' end + end + + # Extract into a subdirectory of @destination; if this test fails it writes + # a file outside destination_subdir, but we want the file to remain inside + # @destination so it will be cleaned up. + destination_subdir = File.join @destination, 'subdir' + FileUtils.mkdir_p destination_subdir + + e = assert_raises Gem::Package::PathError do + package.extract_tar_gz tgz_io, destination_subdir + end + + assert_equal("installing into parent path lib/link/outside.txt of " + + "#{destination_subdir} is not allowed", e.message) + end + def test_extract_tar_gz_directory package = Gem::Package.new @gem @@ -566,6 +591,21 @@ def test_install_location_relative "#{@destination} is not allowed", e.message) end + def test_install_location_suffix + package = Gem::Package.new @gem + + filename = "../#{File.basename(@destination)}suffix.rb" + + e = assert_raises Gem::Package::PathError do + package.install_location filename, @destination + end + + parent = File.expand_path File.join @destination, filename + + assert_equal("installing into parent path #{parent} of " + + "#{@destination} is not allowed", e.message) + end + def test_load_spec entry = StringIO.new Gem.gzip @spec.to_yaml def entry.full_name() 'metadata.gz' end @@ -620,7 +660,7 @@ def test_verify_checksum_bad end end - open 'mismatch.gem', 'wb' do |io| + File.open 'mismatch.gem', 'wb' do |io| io.write gem.string end @@ -670,7 +710,7 @@ def test_verify_checksum_missing end end - open 'data_checksum_missing.gem', 'wb' do |io| + File.open 'data_checksum_missing.gem', 'wb' do |io| io.write gem.string end @@ -723,6 +763,32 @@ def test_verify_nonexistent assert_match %r%nonexistent.gem$%, e.message end + def test_verify_duplicate_file + FileUtils.mkdir_p 'lib' + FileUtils.touch 'lib/code.rb' + + build = Gem::Package.new @gem + build.spec = @spec + build.setup_signer + open @gem, 'wb' do |gem_io| + Gem::Package::TarWriter.new gem_io do |gem| + build.add_metadata gem + build.add_contents gem + + gem.add_file_simple 'a.sig', 0444, 0 + gem.add_file_simple 'a.sig', 0444, 0 + end + end + + package = Gem::Package.new @gem + + e = assert_raises Gem::Security::Exception do + package.verify + end + + assert_equal 'duplicate files in the package: ("a.sig")', e.message + end + def test_verify_security_policy skip 'openssl is missing' unless defined?(OpenSSL::SSL) @@ -773,14 +839,20 @@ def test_verify_security_policy_checksum_missing FileUtils.mkdir 'lib' FileUtils.touch 'lib/code.rb' - open @gem, 'wb' do |gem_io| + File.open @gem, 'wb' do |gem_io| Gem::Package::TarWriter.new gem_io do |gem| build.add_metadata gem build.add_contents gem # write bogus data.tar.gz to foil signature bogus_data = Gem.gzip 'hello' - gem.add_file_simple 'data.tar.gz', 0444, bogus_data.length do |io| + fake_signer = Class.new do + def digest_name; 'SHA512'; end + def digest_algorithm; Digest(:SHA512); end + def key; 'key'; end + def sign(*); 'fake_sig'; end + end + gem.add_file_signed 'data2.tar.gz', 0444, fake_signer.new do |io| io.write bogus_data end @@ -804,7 +876,7 @@ def test_verify_security_policy_checksum_missing end def test_verify_truncate - open 'bad.gem', 'wb' do |io| + File.open 'bad.gem', 'wb' do |io| io.write File.read(@gem, 1024) # don't care about newlines end diff --git a/test/mri/rubygems/test_gem_package_old.rb b/test/mri/rubygems/test_gem_package_old.rb index c15475b0c7d..604981b3c1f 100644 --- a/test/mri/rubygems/test_gem_package_old.rb +++ b/test/mri/rubygems/test_gem_package_old.rb @@ -7,7 +7,7 @@ class TestGemPackageOld < Gem::TestCase def setup super - open 'old_format.gem', 'wb' do |io| + File.open 'old_format.gem', 'wb' do |io| io.write SIMPLE_GEM end diff --git a/test/mri/rubygems/test_gem_package_tar_header.rb b/test/mri/rubygems/test_gem_package_tar_header.rb index d33877057d0..a0719a75316 100644 --- a/test/mri/rubygems/test_gem_package_tar_header.rb +++ b/test/mri/rubygems/test_gem_package_tar_header.rb @@ -143,5 +143,25 @@ def test_update_checksum assert_equal '012467', @tar_header.checksum end + def test_from_bad_octal + test_cases = [ + "00000006,44\000", # bogus character + "00000006789\000", # non-octal digit + "+0000001234\000", # positive sign + "-0000001000\000", # negative sign + "0x000123abc\000", # radix prefix + ] + + test_cases.each do |val| + header_s = @tar_header.to_s + # overwrite the size field + header_s[124, 12] = val + io = TempIO.new header_s + assert_raises ArgumentError do + new_header = Gem::Package::TarHeader.from io + end + end + end + end diff --git a/test/mri/rubygems/test_gem_rdoc.rb b/test/mri/rubygems/test_gem_rdoc.rb index 76ca8c45a94..0355883cb3c 100644 --- a/test/mri/rubygems/test_gem_rdoc.rb +++ b/test/mri/rubygems/test_gem_rdoc.rb @@ -223,6 +223,7 @@ def test_remove def test_remove_unwritable skip 'chmod not supported' if Gem.win_platform? + skip 'skipped in root privilege' if Process.uid.zero? FileUtils.mkdir_p @a.base_dir FileUtils.chmod 0, @a.base_dir @@ -251,6 +252,7 @@ def test_setup def test_setup_unwritable skip 'chmod not supported' if Gem.win_platform? + skip 'skipped in root privilege' if Process.uid.zero? FileUtils.mkdir_p @a.doc_dir FileUtils.chmod 0, @a.doc_dir diff --git a/test/mri/rubygems/test_gem_remote_fetcher.rb b/test/mri/rubygems/test_gem_remote_fetcher.rb index ee5ac777175..20e34e84e11 100644 --- a/test/mri/rubygems/test_gem_remote_fetcher.rb +++ b/test/mri/rubygems/test_gem_remote_fetcher.rb @@ -431,7 +431,7 @@ def test_download_install_dir assert File.exist?(a1_cache_gem) end - unless win_platform? # File.chmod doesn't work + unless win_platform? || Process.uid.zero? # File.chmod doesn't work def test_download_local_read_only FileUtils.mv @a1_gem, @tempdir local_path = File.join @tempdir, @a1.file_name diff --git a/test/mri/rubygems/test_gem_request_set.rb b/test/mri/rubygems/test_gem_request_set.rb index 3a48827481a..5dc6c1518de 100644 --- a/test/mri/rubygems/test_gem_request_set.rb +++ b/test/mri/rubygems/test_gem_request_set.rb @@ -52,7 +52,7 @@ def test_install_from_gemdeps rs = Gem::RequestSet.new installed = [] - open 'gem.deps.rb', 'w' do |io| + File.open 'gem.deps.rb', 'w' do |io| io.puts 'gem "a"' io.flush @@ -78,7 +78,7 @@ def test_install_from_gemdeps_explain rs = Gem::RequestSet.new - open 'gem.deps.rb', 'w' do |io| + File.open 'gem.deps.rb', 'w' do |io| io.puts 'gem "a"' io.flush @@ -104,7 +104,7 @@ def test_install_from_gemdeps_install_dir rs = Gem::RequestSet.new installed = [] - open 'gem.deps.rb', 'w' do |io| + File.open 'gem.deps.rb', 'w' do |io| io.puts 'gem "a"' end @@ -128,7 +128,7 @@ def test_install_from_gemdeps_local rs = Gem::RequestSet.new - open 'gem.deps.rb', 'w' do |io| + File.open 'gem.deps.rb', 'w' do |io| io.puts 'gem "a"' io.flush @@ -150,7 +150,7 @@ def test_install_from_gemdeps_lockfile rs = Gem::RequestSet.new installed = [] - open 'gem.deps.rb.lock', 'w' do |io| + File.open 'gem.deps.rb.lock', 'w' do |io| io.puts <<-LOCKFILE GEM remote: #{@gem_repo} @@ -167,7 +167,7 @@ def test_install_from_gemdeps_lockfile LOCKFILE end - open 'gem.deps.rb', 'w' do |io| + File.open 'gem.deps.rb', 'w' do |io| io.puts 'gem "b"' end @@ -190,7 +190,7 @@ def test_install_from_gemdeps_version_mismatch rs = Gem::RequestSet.new installed = [] - open 'gem.deps.rb', 'w' do |io| + File.open 'gem.deps.rb', 'w' do |io| io.puts <<-GEM_DEPS gem "a" ruby "0" diff --git a/test/mri/rubygems/test_gem_request_set_lockfile.rb b/test/mri/rubygems/test_gem_request_set_lockfile.rb index 908f97303e7..7460b7efad2 100644 --- a/test/mri/rubygems/test_gem_request_set_lockfile.rb +++ b/test/mri/rubygems/test_gem_request_set_lockfile.rb @@ -31,7 +31,7 @@ def lockfile def write_lockfile lockfile @lock_file = File.expand_path "#{@gem_deps_file}.lock" - open @lock_file, 'w' do |io| + File.open @lock_file, 'w' do |io| io.write lockfile end end @@ -387,7 +387,7 @@ def test_to_s_git s.add_dependency 'c', '~> 1.0' end - open 'b.gemspec', 'w' do |io| + File.open 'b.gemspec', 'w' do |io| io.write b.to_ruby end @@ -400,7 +400,7 @@ def test_to_s_git Dir.chdir 'c' do c = Gem::Specification.new 'c', 1 - open 'c.gemspec', 'w' do |io| + File.open 'c.gemspec', 'w' do |io| io.write c.to_ruby end @@ -455,7 +455,7 @@ def test_write_error gem_deps_lock_file = "#{@gem_deps_file}.lock" - open gem_deps_lock_file, 'w' do |io| + File.open gem_deps_lock_file, 'w' do |io| io.write 'hello' end diff --git a/test/mri/rubygems/test_gem_request_set_lockfile_parser.rb b/test/mri/rubygems/test_gem_request_set_lockfile_parser.rb index 9946c522d9d..f3517da43a7 100644 --- a/test/mri/rubygems/test_gem_request_set_lockfile_parser.rb +++ b/test/mri/rubygems/test_gem_request_set_lockfile_parser.rb @@ -536,7 +536,7 @@ def test_parse_missing end def write_lockfile lockfile - open @lock_file, 'w' do |io| + File.open @lock_file, 'w' do |io| io.write lockfile end end diff --git a/test/mri/rubygems/test_gem_request_set_lockfile_tokenizer.rb b/test/mri/rubygems/test_gem_request_set_lockfile_tokenizer.rb index ab506a14e69..f4aba6d94ac 100644 --- a/test/mri/rubygems/test_gem_request_set_lockfile_tokenizer.rb +++ b/test/mri/rubygems/test_gem_request_set_lockfile_tokenizer.rb @@ -295,7 +295,7 @@ def test_unget end def write_lockfile lockfile - open @lock_file, 'w' do |io| + File.open @lock_file, 'w' do |io| io.write lockfile end end diff --git a/test/mri/rubygems/test_gem_resolver_git_specification.rb b/test/mri/rubygems/test_gem_resolver_git_specification.rb index 9e8e2c57153..211757eb204 100644 --- a/test/mri/rubygems/test_gem_resolver_git_specification.rb +++ b/test/mri/rubygems/test_gem_resolver_git_specification.rb @@ -70,7 +70,7 @@ def test_install_extension Dir.chdir 'git/a' do FileUtils.mkdir_p 'ext/lib' - open 'ext/extconf.rb', 'w' do |io| + File.open 'ext/extconf.rb', 'w' do |io| io.puts 'require "mkmf"' io.puts 'create_makefile "a"' end diff --git a/test/mri/rubygems/test_gem_server.rb b/test/mri/rubygems/test_gem_server.rb index 6fe02e480f0..a583bd7b359 100644 --- a/test/mri/rubygems/test_gem_server.rb +++ b/test/mri/rubygems/test_gem_server.rb @@ -100,7 +100,7 @@ def test_latest_specs_gemdirs specs_dir = File.join dir, 'specifications' FileUtils.mkdir_p specs_dir - open File.join(specs_dir, spec.spec_name), 'w' do |io| + File.open File.join(specs_dir, spec.spec_name), 'w' do |io| io.write spec.to_ruby end @@ -198,7 +198,7 @@ def test_quick_gemdirs FileUtils.mkdir_p specs_dir - open File.join(specs_dir, spec.spec_name), 'w' do |io| + File.open File.join(specs_dir, spec.spec_name), 'w' do |io| io.write spec.to_ruby end @@ -339,7 +339,7 @@ def test_root_gemdirs specs_dir = File.join dir, 'specifications' FileUtils.mkdir_p specs_dir - open File.join(specs_dir, spec.spec_name), 'w' do |io| + File.open File.join(specs_dir, spec.spec_name), 'w' do |io| io.write spec.to_ruby end @@ -353,6 +353,171 @@ def test_root_gemdirs assert_match 'z 9', @res.body end + + def test_xss_homepage_fix_289313 + data = StringIO.new "GET / HTTP/1.0\r\n\r\n" + dir = "#{@gemhome}2" + + spec = util_spec 'xsshomepagegem', 1 + spec.homepage = "javascript:confirm(document.domain)" + + specs_dir = File.join dir, 'specifications' + FileUtils.mkdir_p specs_dir + + open File.join(specs_dir, spec.spec_name), 'w' do |io| + io.write spec.to_ruby + end + + server = Gem::Server.new dir, process_based_port, false + + @req.parse data + + server.root @req, @res + + assert_equal 200, @res.status + assert_match 'xsshomepagegem 1', @res.body + + # This verifies that the homepage for this spec is not displayed and is set to ".", because it's not a + # valid HTTP/HTTPS URL and could be unsafe in an HTML context. We would prefer to throw an exception here, + # but spec.homepage is currently free form and not currently required to be a URL, this behavior may be + # validated in future versions of Gem::Specification. + # + # There are two variant we're checking here, one where rdoc is not present, and one where rdoc is present in the same regex: + # + # Variant #1 - rdoc not installed + # + # xsshomepagegem 1 + # + # + # [rdoc] + # + # + # + # [www] + # + # Variant #2 - rdoc installed + # + # xsshomepagegem 1 + # + # + # \[rdoc\]<\/a> + # + # + # + # [www] + regex_match = /xsshomepagegem 1<\/b>\s+(\[rdoc\]<\/span>|\[rdoc\]<\/a>)\s+\[www\]<\/a>/ + assert_match regex_match, @res.body + end + + def test_invalid_homepage + data = StringIO.new "GET / HTTP/1.0\r\n\r\n" + dir = "#{@gemhome}2" + + spec = util_spec 'invalidhomepagegem', 1 + spec.homepage = "notavalidhomepageurl" + + specs_dir = File.join dir, 'specifications' + FileUtils.mkdir_p specs_dir + + open File.join(specs_dir, spec.spec_name), 'w' do |io| + io.write spec.to_ruby + end + + server = Gem::Server.new dir, process_based_port, false + + @req.parse data + + server.root @req, @res + + assert_equal 200, @res.status + assert_match 'invalidhomepagegem 1', @res.body + + # This verifies that the homepage for this spec is not displayed and is set to ".", because it's not a + # valid HTTP/HTTPS URL and could be unsafe in an HTML context. We would prefer to throw an exception here, + # but spec.homepage is currently free form and not currently required to be a URL, this behavior may be + # validated in future versions of Gem::Specification. + # + # There are two variant we're checking here, one where rdoc is not present, and one where rdoc is present in the same regex: + # + # Variant #1 - rdoc not installed + # + # invalidhomepagegem 1 + # + # + # [rdoc] + # + # + # + # [www] + # + # Variant #2 - rdoc installed + # + # invalidhomepagegem 1 + # + # + # \[rdoc\]<\/a> + # + # + # + # [www] + regex_match = /invalidhomepagegem 1<\/b>\s+(\[rdoc\]<\/span>|\[rdoc\]<\/a>)\s+\[www\]<\/a>/ + assert_match regex_match, @res.body + end + + def test_valid_homepage_http + data = StringIO.new "GET / HTTP/1.0\r\n\r\n" + dir = "#{@gemhome}2" + + spec = util_spec 'validhomepagegemhttp', 1 + spec.homepage = "http://rubygems.org" + + specs_dir = File.join dir, 'specifications' + FileUtils.mkdir_p specs_dir + + open File.join(specs_dir, spec.spec_name), 'w' do |io| + io.write spec.to_ruby + end + + server = Gem::Server.new dir, process_based_port, false + + @req.parse data + + server.root @req, @res + + assert_equal 200, @res.status + assert_match 'validhomepagegemhttp 1', @res.body + + regex_match = /validhomepagegemhttp 1<\/b>\s+(\[rdoc\]<\/span>|\[rdoc\]<\/a>)\s+\[www\]<\/a>/ + assert_match regex_match, @res.body + end + + def test_valid_homepage_https + data = StringIO.new "GET / HTTP/1.0\r\n\r\n" + dir = "#{@gemhome}2" + + spec = util_spec 'validhomepagegemhttps', 1 + spec.homepage = "https://rubygems.org" + + specs_dir = File.join dir, 'specifications' + FileUtils.mkdir_p specs_dir + + open File.join(specs_dir, spec.spec_name), 'w' do |io| + io.write spec.to_ruby + end + + server = Gem::Server.new dir, process_based_port, false + + @req.parse data + + server.root @req, @res + + assert_equal 200, @res.status + assert_match 'validhomepagegemhttps 1', @res.body + + regex_match = /validhomepagegemhttps 1<\/b>\s+(\[rdoc\]<\/span>|\[rdoc\]<\/a>)\s+\[www\]<\/a>/ + assert_match regex_match, @res.body + end + def test_specs data = StringIO.new "GET /specs.#{Gem.marshal_version} HTTP/1.0\r\n\r\n" @req.parse data @@ -378,7 +543,7 @@ def test_specs_gemdirs specs_dir = File.join dir, 'specifications' FileUtils.mkdir_p specs_dir - open File.join(specs_dir, spec.spec_name), 'w' do |io| + File.open File.join(specs_dir, spec.spec_name), 'w' do |io| io.write spec.to_ruby end diff --git a/test/mri/rubygems/test_gem_source.rb b/test/mri/rubygems/test_gem_source.rb index 4a93e222f8e..8805a9b4045 100644 --- a/test/mri/rubygems/test_gem_source.rb +++ b/test/mri/rubygems/test_gem_source.rb @@ -110,7 +110,7 @@ def test_fetch_spec_cached cache_file = File.join cache_dir, a1.spec_name - open cache_file, 'wb' do |io| + File.open cache_file, 'wb' do |io| Marshal.dump a1, io end @@ -163,7 +163,7 @@ def test_load_specs_cached cache_file = File.join cache_dir, "latest_specs.#{Gem.marshal_version}" - open cache_file, 'wb' do |io| + File.open cache_file, 'wb' do |io| Marshal.dump latest_specs, io end @@ -187,7 +187,7 @@ def test_load_specs_cached_empty cache_file = File.join cache_dir, "latest_specs.#{Gem.marshal_version}" - open cache_file, 'wb' do |io| + File.open cache_file, 'wb' do |io| # Setup invalid data in the cache: io.write Marshal.dump(latest_specs)[0, 10] end diff --git a/test/mri/rubygems/test_gem_source_git.rb b/test/mri/rubygems/test_gem_source_git.rb index 0e13a11e7ea..8f5d3ee7457 100644 --- a/test/mri/rubygems/test_gem_source_git.rb +++ b/test/mri/rubygems/test_gem_source_git.rb @@ -229,7 +229,7 @@ def test_specs Dir.chdir 'b' do b = Gem::Specification.new 'b', 1 - open 'b.gemspec', 'w' do |io| + File.open 'b.gemspec', 'w' do |io| io.write b.to_ruby end diff --git a/test/mri/rubygems/test_gem_specification.rb b/test/mri/rubygems/test_gem_specification.rb index bb6acbc7dea..9d89ecca690 100644 --- a/test/mri/rubygems/test_gem_specification.rb +++ b/test/mri/rubygems/test_gem_specification.rb @@ -922,7 +922,7 @@ def test_self_load end def test_self_load_relative - open 'a-2.gemspec', 'w' do |io| + File.open 'a-2.gemspec', 'w' do |io| io.write @a2.to_ruby_for_cache end @@ -1111,7 +1111,7 @@ def test_self_remove_spec end def test_self_remove_spec_removed - open @a1.spec_file, 'w' do |io| + File.open @a1.spec_file, 'w' do |io| io.write @a1.to_ruby end @@ -1363,13 +1363,13 @@ def test_build_args assert_empty @ext.build_args - open @ext.build_info_file, 'w' do |io| + File.open @ext.build_info_file, 'w' do |io| io.puts end assert_empty @ext.build_args - open @ext.build_info_file, 'w' do |io| + File.open @ext.build_info_file, 'w' do |io| io.puts '--with-foo-dir=wherever' end @@ -1385,9 +1385,9 @@ def test_build_extensions extconf_rb = File.join @ext.gem_dir, @ext.extensions.first FileUtils.mkdir_p File.dirname extconf_rb - open extconf_rb, 'w' do |f| + File.open extconf_rb, 'w' do |f| f.write <<-'RUBY' - open 'Makefile', 'w' do |f| + File.open 'Makefile', 'w' do |f| f.puts "clean:\n\techo clean" f.puts "default:\n\techo built" f.puts "install:\n\techo installed" @@ -1435,9 +1435,9 @@ def test_build_extensions_default_gem extconf_rb = File.join spec.gem_dir, spec.extensions.first FileUtils.mkdir_p File.dirname extconf_rb - open extconf_rb, 'w' do |f| + File.open extconf_rb, 'w' do |f| f.write <<-'RUBY' - open 'Makefile', 'w' do |f| + File.open 'Makefile', 'w' do |f| f.puts "default:\n\techo built" f.puts "install:\n\techo installed" end @@ -1461,6 +1461,7 @@ def test_build_extensions_error def test_build_extensions_extensions_dir_unwritable skip 'chmod not supported' if Gem.win_platform? + skip 'skipped in root privilege' if Process.uid.zero? ext_spec @@ -1469,9 +1470,9 @@ def test_build_extensions_extensions_dir_unwritable extconf_rb = File.join @ext.gem_dir, @ext.extensions.first FileUtils.mkdir_p File.dirname extconf_rb - open extconf_rb, 'w' do |f| + File.open extconf_rb, 'w' do |f| f.write <<-'RUBY' - open 'Makefile', 'w' do |f| + File.open 'Makefile', 'w' do |f| f.puts "clean:\n\techo clean" f.puts "default:\n\techo built" f.puts "install:\n\techo installed" @@ -1486,7 +1487,7 @@ def test_build_extensions_extensions_dir_unwritable @ext.build_extensions refute_path_exists @ext.extension_dir ensure - unless ($DEBUG or win_platform?) then + unless ($DEBUG or win_platform? or Process.uid.zero?) then FileUtils.chmod 0755, File.join(@ext.base_dir, 'extensions') FileUtils.chmod 0755, @ext.base_dir end @@ -1502,9 +1503,9 @@ def test_build_extensions_no_extensions_dir_unwritable extconf_rb = File.join @ext.gem_dir, @ext.extensions.first FileUtils.mkdir_p File.dirname extconf_rb - open extconf_rb, 'w' do |f| + File.open extconf_rb, 'w' do |f| f.write <<-'RUBY' - open 'Makefile', 'w' do |f| + File.open 'Makefile', 'w' do |f| f.puts "clean:\n\techo clean" f.puts "default:\n\techo built" f.puts "install:\n\techo installed" @@ -1551,9 +1552,9 @@ def test_build_extensions_preview extconf_rb = File.join @ext.gem_dir, @ext.extensions.first FileUtils.mkdir_p File.dirname extconf_rb - open extconf_rb, 'w' do |f| + File.open extconf_rb, 'w' do |f| f.write <<-'RUBY' - open 'Makefile', 'w' do |f| + File.open 'Makefile', 'w' do |f| f.puts "clean:\n\techo clean" f.puts "default:\n\techo built" f.puts "install:\n\techo installed" @@ -2882,7 +2883,22 @@ def test_validate_homepage @a1.validate end - assert_equal '"over at my cool site" is not a URI', e.message + assert_equal '"over at my cool site" is not a valid HTTP URI', e.message + + @a1.homepage = 'ftp://rubygems.org' + + e = assert_raises Gem::InvalidSpecificationException do + @a1.validate + end + + assert_equal '"ftp://rubygems.org" is not a valid HTTP URI', e.message + + @a1.homepage = 'http://rubygems.org' + assert_equal true, @a1.validate + + @a1.homepage = 'https://rubygems.org' + assert_equal true, @a1.validate + end end @@ -3418,9 +3434,9 @@ def test_missing_extensions_eh extconf_rb = File.join @ext.gem_dir, @ext.extensions.first FileUtils.mkdir_p File.dirname extconf_rb - open extconf_rb, 'w' do |f| + File.open extconf_rb, 'w' do |f| f.write <<-'RUBY' - open 'Makefile', 'w' do |f| + File.open 'Makefile', 'w' do |f| f.puts "clean:\n\techo clean" f.puts "default:\n\techo built" f.puts "install:\n\techo installed" diff --git a/test/mri/rubygems/test_gem_stub_specification.rb b/test/mri/rubygems/test_gem_stub_specification.rb index 43680265c73..f9a3a236c08 100644 --- a/test/mri/rubygems/test_gem_stub_specification.rb +++ b/test/mri/rubygems/test_gem_stub_specification.rb @@ -127,9 +127,9 @@ def test_missing_extensions_eh extconf_rb = File.join s.gem_dir, s.extensions.first FileUtils.mkdir_p File.dirname extconf_rb - open extconf_rb, 'w' do |f| + File.open extconf_rb, 'w' do |f| f.write <<-'RUBY' - open 'Makefile', 'w' do |f| + File.open 'Makefile', 'w' do |f| f.puts "clean:\n\techo clean" f.puts "default:\n\techo built" f.puts "install:\n\techo installed" @@ -149,7 +149,7 @@ def test_missing_extensions_eh_default_gem spec = new_default_spec 'default', 1 spec.extensions << 'extconf.rb' - open spec.loaded_from, 'w' do |io| + File.open spec.loaded_from, 'w' do |io| io.write spec.to_ruby_for_cache end @@ -198,7 +198,7 @@ def test_to_spec_missing_extensions def stub_with_version spec = File.join @gemhome, 'specifications', 'stub_e-2.gemspec' - open spec, 'w' do |io| + File.open spec, 'w' do |io| io.write <<-STUB # -*- encoding: utf-8 -*- # stub: stub_v 2 ruby lib @@ -221,7 +221,7 @@ def stub_with_version def stub_without_version spec = File.join @gemhome, 'specifications', 'stub-2.gemspec' - open spec, 'w' do |io| + File.open spec, 'w' do |io| io.write <<-STUB # -*- encoding: utf-8 -*- # stub: stub_v ruby lib @@ -245,7 +245,7 @@ def stub_without_version def stub_with_extension spec = File.join @gemhome, 'specifications', 'stub_e-2.gemspec' - open spec, 'w' do |io| + File.open spec, 'w' do |io| io.write <<-STUB # -*- encoding: utf-8 -*- # stub: stub_e 2 ruby lib @@ -271,7 +271,7 @@ def stub_with_extension def stub_without_extension spec = File.join @gemhome, 'specifications', 'stub-2.gemspec' - open spec, 'w' do |io| + File.open spec, 'w' do |io| io.write <<-STUB # -*- encoding: utf-8 -*- # stub: stub 2 ruby lib diff --git a/test/mri/rubygems/test_gem_util.rb b/test/mri/rubygems/test_gem_util.rb index b85db44d514..3b7887d9319 100644 --- a/test/mri/rubygems/test_gem_util.rb +++ b/test/mri/rubygems/test_gem_util.rb @@ -5,6 +5,7 @@ class TestGemUtil < Gem::TestCase def test_class_popen + skip "MJIT executes process and it's caught by Process.wait(-1)" if defined?(RubyVM::MJIT) && RubyVM::MJIT.enabled? assert_equal "0\n", Gem::Util.popen(Gem.ruby, '-e', 'p 0') assert_raises Errno::ECHILD do @@ -29,6 +30,30 @@ def test_traverse_parents loop { break if enum.next.nil? } # exhaust the enumerator end + def test_traverse_parents_does_not_crash_on_permissions_error + skip 'skipped on MS Windows (chmod has no effect)' if win_platform? + + FileUtils.mkdir_p 'd/e/f' + # remove 'execute' permission from "e" directory and make it + # impossible to cd into it and its children + FileUtils.chmod(0666, 'd/e') + + paths = Gem::Util.traverse_parents('d/e/f').to_a + + assert_equal File.join(@tempdir, 'd'), paths[0] + assert_equal @tempdir, paths[1] + if File.respond_to?(:realpath) + assert_equal File.realpath(Dir.tmpdir), paths[2] + assert_equal File.realpath("..", Dir.tmpdir), paths[3] + elsif RUBY_PLATFORM !~ /darwin/ + assert_equal Dir.tmpdir, paths[2] + assert_equal '/', paths[3] + end + ensure + # restore default permissions, allow the directory to be removed + FileUtils.chmod(0775, 'd/e') unless win_platform? + end + def test_linked_list_find list = [1,2,3,4,5].inject(Gem::List.new(0)) { |m,o| Gem::List.new o, m diff --git a/test/mri/rubygems/test_gem_version.rb b/test/mri/rubygems/test_gem_version.rb index 56c818663e5..792ad5f0841 100644 --- a/test/mri/rubygems/test_gem_version.rb +++ b/test/mri/rubygems/test_gem_version.rb @@ -2,6 +2,8 @@ require 'rubygems/test_case' require "rubygems/version" +require "minitest/benchmark" + class TestGemVersion < Gem::TestCase class V < ::Gem::Version @@ -102,6 +104,15 @@ def test_initialize_invalid end end + def bench_anchored_version_pattern + assert_performance_linear 0.5 do |count| + version_string = count.times.map {|i| "0" * i.succ }.join(".") << "." + version_string =~ Gem::Version::ANCHORED_VERSION_PATTERN + end + rescue RegexpError + skip "It fails to allocate the memory for regex pattern of Gem::Version::ANCHORED_VERSION_PATTERN" + end + def test_empty_version ["", " ", " "].each do |empty| assert_equal "0", Gem::Version.new(empty).version diff --git a/test/mri/rubygems/test_require.rb b/test/mri/rubygems/test_require.rb index a846f46833c..e292ce226d1 100644 --- a/test/mri/rubygems/test_require.rb +++ b/test/mri/rubygems/test_require.rb @@ -38,18 +38,6 @@ def assert_require(path) assert require(path), "'#{path}' was already required" end - def append_latch spec - dir = spec.gem_dir - Dir.chdir dir do - spec.files.each do |file| - File.open file, 'a' do |fp| - fp.puts "FILE_ENTERED_LATCH.release" - fp.puts "FILE_EXIT_LATCH.await" - end - end - end - end - # Providing -I on the commandline should always beat gems def test_dash_i_beats_gems a1 = new_spec "a", "1", {"b" => "= 1"}, "lib/test_gem_require_a.rb" @@ -80,6 +68,17 @@ def test_dash_i_beats_gems Object.send :remove_const, :HELLO if Object.const_defined? :HELLO end + def create_sync_thread + Thread.new do + begin + yield + ensure + FILE_ENTERED_LATCH.release + FILE_EXIT_LATCH.await + end + end + end + def test_concurrent_require skip 'deadlock' if /^1\.8\./ =~ RUBY_VERSION @@ -91,11 +90,8 @@ def test_concurrent_require install_specs a1, b1 - append_latch a1 - append_latch b1 - - t1 = Thread.new { assert_require 'a' } - t2 = Thread.new { assert_require 'b' } + t1 = create_sync_thread{ assert_require 'a' } + t2 = create_sync_thread{ assert_require 'b' } # wait until both files are waiting on the exit latch FILE_ENTERED_LATCH.await @@ -106,10 +102,8 @@ def test_concurrent_require assert t1.join, "thread 1 should exit" assert t2.join, "thread 2 should exit" ensure - return if $! # skipping - - Object.send :remove_const, :FILE_ENTERED_LATCH - Object.send :remove_const, :FILE_EXIT_LATCH + Object.send :remove_const, :FILE_ENTERED_LATCH if Object.const_defined? :FILE_ENTERED_LATCH + Object.send :remove_const, :FILE_EXIT_LATCH if Object.const_defined? :FILE_EXIT_LATCH end def test_require_is_not_lazy_with_exact_req diff --git a/test/mri/runner.rb b/test/mri/runner.rb index 96103ebceac..404504aa389 100755 --- a/test/mri/runner.rb +++ b/test/mri/runner.rb @@ -1,4 +1,3 @@ -#!/usr/bin/env ruby # frozen_string_literal: false require 'rbconfig' diff --git a/test/mri/sdbm/test_sdbm.rb b/test/mri/sdbm/test_sdbm.rb index 7b2d39c9a19..6cea36d3a59 100644 --- a/test/mri/sdbm/test_sdbm.rb +++ b/test/mri/sdbm/test_sdbm.rb @@ -108,6 +108,7 @@ def test_s_open_nolock def test_s_open_error skip "doesn't support to avoid read access by owner on Windows" if /mswin|mingw/ =~ RUBY_PLATFORM + skip "skipped because root can open anything" if Process.uid == 0 assert_instance_of(SDBM, sdbm = SDBM.open("#{@tmpdir}/#{@prefix}", 0)) assert_raise(Errno::EACCES) { SDBM.open("#{@tmpdir}/#{@prefix}", 0) @@ -519,6 +520,7 @@ def test_closed end def test_readonly + skip "skipped because root can read anything" if /mswin|mingw/ !~ RUBY_PLATFORM && Process.uid == 0 @sdbm["bar"] = "baz" @sdbm.close File.chmod(0444, @path + ".dir") diff --git a/test/mri/socket/test_unix.rb b/test/mri/socket/test_unix.rb index 36f48c35ed7..6efb1d60ee8 100644 --- a/test/mri/socket/test_unix.rb +++ b/test/mri/socket/test_unix.rb @@ -284,6 +284,16 @@ def bound_unix_socket(klass) File.unlink path if path && File.socket?(path) end + def test_open_nul_byte + tmpfile = Tempfile.new("s") + path = tmpfile.path + tmpfile.close(true) + assert_raise(ArgumentError) {UNIXServer.open(path+"\0")} + assert_raise(ArgumentError) {UNIXSocket.open(path+"\0")} + ensure + File.unlink path if path && File.socket?(path) + end + def test_addr bound_unix_socket(UNIXServer) {|serv, path| UNIXSocket.open(path) {|c| diff --git a/test/mri/test_find.rb b/test/mri/test_find.rb index d4d958859da..01b1fcc1212 100644 --- a/test/mri/test_find.rb +++ b/test/mri/test_find.rb @@ -104,6 +104,8 @@ def test_countdown3 def test_unreadable_dir skip "no meaning test on Windows" if /mswin|mingw/ =~ RUBY_PLATFORM + skip if Process.uid == 0 # because root can read anything + Dir.mktmpdir {|d| Dir.mkdir(dir = "#{d}/dir") File.open("#{dir}/foo", "w"){} @@ -157,6 +159,8 @@ def test_unsearchable_dir assert_equal([d, dir, file], a) skip "no meaning test on Windows" if /mswin|mingw/ =~ RUBY_PLATFORM + skip "skipped because root can read anything" if Process.uid == 0 + a = [] assert_raise_with_message(Errno::EACCES, /#{Regexp.quote(file)}/) do Find.find(d, ignore_error: false) {|f| a << f } diff --git a/test/mri/test_tempfile.rb b/test/mri/test_tempfile.rb index 465ce42e36e..6b7860fe20c 100644 --- a/test/mri/test_tempfile.rb +++ b/test/mri/test_tempfile.rb @@ -371,5 +371,31 @@ def test_create_default_basename } assert_file.not_exist?(path) end -end + TRAVERSAL_PATH = Array.new(Dir.pwd.split('/').count, '..').join('/') + Dir.pwd + '/' + + def test_open_traversal_dir + expect = Dir.glob(TRAVERSAL_PATH + '*').count + t = Tempfile.open([TRAVERSAL_PATH, 'foo']) + actual = Dir.glob(TRAVERSAL_PATH + '*').count + assert_equal expect, actual + ensure + t.close! + end + + def test_new_traversal_dir + expect = Dir.glob(TRAVERSAL_PATH + '*').count + t = Tempfile.new(TRAVERSAL_PATH + 'foo') + actual = Dir.glob(TRAVERSAL_PATH + '*').count + assert_equal expect, actual + ensure + t.close! + end + + def test_create_traversal_dir + expect = Dir.glob(TRAVERSAL_PATH + '*').count + Tempfile.create(TRAVERSAL_PATH + 'foo') + actual = Dir.glob(TRAVERSAL_PATH + '*').count + assert_equal expect, actual + end +end diff --git a/test/mri/test_time.rb b/test/mri/test_time.rb index 9d7c976abf0..26a7daaf2ab 100644 --- a/test/mri/test_time.rb +++ b/test/mri/test_time.rb @@ -105,6 +105,8 @@ def subtest_xmlschema_alias(method) t = Time.utc(1996, 12, 20, 0, 39, 57) s = "1996-12-19T16:39:57-08:00" assert_equal(t, Time.__send__(method, s)) + assert_equal(t, Time.__send__(method, s.sub(/:(?=00\z)/, ''))) + assert_equal(t, Time.__send__(method, s.sub(/:00\z/, ''))) # There is no way to generate time string with arbitrary timezone. s = "1996-12-20T00:39:57Z" assert_equal(t, Time.__send__(method, s)) diff --git a/test/mri/test_tmpdir.rb b/test/mri/test_tmpdir.rb index 691d52f3cb4..7cdf2bba3a5 100644 --- a/test/mri/test_tmpdir.rb +++ b/test/mri/test_tmpdir.rb @@ -56,4 +56,21 @@ def test_mktmpdir_nil assert_kind_of(String, d) } end + + TRAVERSAL_PATH = Array.new(Dir.pwd.split('/').count, '..').join('/') + Dir.pwd + '/' + TRAVERSAL_PATH.delete!(':') if /mswin|mingw/ =~ RUBY_PLATFORM + + def test_mktmpdir_traversal + expect = Dir.glob(TRAVERSAL_PATH + '*').count + Dir.mktmpdir(TRAVERSAL_PATH + 'foo') + actual = Dir.glob(TRAVERSAL_PATH + '*').count + assert_equal expect, actual + end + + def test_mktmpdir_traversal_array + expect = Dir.glob(TRAVERSAL_PATH + '*').count + Dir.mktmpdir([TRAVERSAL_PATH, 'foo']) + actual = Dir.glob(TRAVERSAL_PATH + '*').count + assert_equal expect, actual + end end diff --git a/test/mri/webrick/test_filehandler.rb b/test/mri/webrick/test_filehandler.rb index 3fba1ec5e26..99bc1420b8e 100644 --- a/test/mri/webrick/test_filehandler.rb +++ b/test/mri/webrick/test_filehandler.rb @@ -20,16 +20,10 @@ def windows? end def get_res_body(res) - body = res.body - if defined? body.read - begin - body.read - ensure - body.close - end - else - body - end + sio = StringIO.new + sio.binmode + res.send_body(sio) + sio.string end def make_range_request(range_spec) @@ -81,6 +75,23 @@ def test_make_partial_content res = make_range_response(filename, "bytes=0-0, -2") assert_match(%r{^multipart/byteranges}, res["content-type"]) + body = get_res_body(res) + boundary = /; boundary=(.+)/.match(res['content-type'])[1] + off = filesize - 2 + last = filesize - 1 + + exp = "--#{boundary}\r\n" \ + "Content-Type: text/plain\r\n" \ + "Content-Range: bytes 0-0/#{filesize}\r\n" \ + "\r\n" \ + "#{IO.read(__FILE__, 1)}\r\n" \ + "--#{boundary}\r\n" \ + "Content-Type: text/plain\r\n" \ + "Content-Range: bytes #{off}-#{last}/#{filesize}\r\n" \ + "\r\n" \ + "#{IO.read(__FILE__, 2, off)}\r\n" \ + "--#{boundary}--\r\n" + assert_equal exp, body end def test_filehandler diff --git a/test/mri/webrick/test_httpauth.rb b/test/mri/webrick/test_httpauth.rb index 2ee492db7a7..ff539f06c78 100644 --- a/test/mri/webrick/test_httpauth.rb +++ b/test/mri/webrick/test_httpauth.rb @@ -4,6 +4,7 @@ require "tempfile" require "webrick" require "webrick/httpauth/basicauth" +require "stringio" require_relative "utils" class TestWEBrickHTTPAuth < Test::Unit::TestCase @@ -216,12 +217,97 @@ def test_digest_auth } end + def test_digest_auth_int + log_tester = lambda {|log, access_log| + log.reject! {|line| /\A\s*\z/ =~ line } + pats = [ + /ERROR Digest wb auth-int realm: no credentials in the request\./, + /ERROR WEBrick::HTTPStatus::Unauthorized/, + /ERROR Digest wb auth-int realm: foo: digest unmatch\./ + ] + pats.each {|pat| + assert(!log.grep(pat).empty?, "webrick log doesn't have expected error: #{pat.inspect}") + log.reject! {|line| pat =~ line } + } + assert_equal([], log) + } + TestWEBrick.start_httpserver({}, log_tester) {|server, addr, port, log| + realm = "wb auth-int realm" + path = "/digest_auth_int" + + Tempfile.create("test_webrick_auth_int") {|tmpfile| + tmpfile.close + tmp_pass = WEBrick::HTTPAuth::Htdigest.new(tmpfile.path) + tmp_pass.set_passwd(realm, "foo", "Hunter2") + tmp_pass.flush + + htdigest = WEBrick::HTTPAuth::Htdigest.new(tmpfile.path) + users = [] + htdigest.each{|user, pass| users << user } + assert_equal %w(foo), users + + auth = WEBrick::HTTPAuth::DigestAuth.new( + :Realm => realm, :UserDB => htdigest, + :Algorithm => 'MD5', + :Logger => server.logger, + :Qop => %w(auth-int), + ) + server.mount_proc(path){|req, res| + auth.authenticate(req, res) + res.body = "bbb" + } + Net::HTTP.start(addr, port) do |http| + post = Net::HTTP::Post.new(path) + params = {} + data = 'hello=world' + body = StringIO.new(data) + post.content_length = data.bytesize + post['Content-Type'] = 'application/x-www-form-urlencoded' + post.body_stream = body + + http.request(post) do |res| + assert_equal('401', res.code, log.call) + res["www-authenticate"].scan(DIGESTRES_) do |key, quoted, token| + params[key.downcase] = token || quoted.delete('\\') + end + params['uri'] = "http://#{addr}:#{port}#{path}" + end + + body.rewind + cred = credentials_for_request('foo', 'Hunter3', params, body) + post['Authorization'] = cred + post.body_stream = body + http.request(post){|res| + assert_equal('401', res.code, log.call) + assert_not_equal("bbb", res.body, log.call) + } + + body.rewind + cred = credentials_for_request('foo', 'Hunter2', params, body) + post['Authorization'] = cred + post.body_stream = body + http.request(post){|res| assert_equal("bbb", res.body, log.call)} + end + } + } + end + private - def credentials_for_request(user, password, params) + def credentials_for_request(user, password, params, body = nil) cnonce = "hoge" nonce_count = 1 ha1 = "#{user}:#{params['realm']}:#{password}" - ha2 = "GET:#{params['uri']}" + if body + dig = Digest::MD5.new + while buf = body.read(16384) + dig.update(buf) + end + body.rewind + ha2 = "POST:#{params['uri']}:#{dig.hexdigest}" + else + ha2 = "GET:#{params['uri']}" + end + request_digest = "#{Digest::MD5.hexdigest(ha1)}:" \ "#{params['nonce']}:#{'%08x' % nonce_count}:#{cnonce}:#{params['qop']}:" \ diff --git a/test/mri/webrick/test_httpresponse.rb b/test/mri/webrick/test_httpresponse.rb index b06cf925a2f..6263e0a7104 100644 --- a/test/mri/webrick/test_httpresponse.rb +++ b/test/mri/webrick/test_httpresponse.rb @@ -2,6 +2,7 @@ require "webrick" require "minitest/autorun" require "stringio" +require "net/http" module WEBrick class TestHTTPResponse < MiniTest::Unit::TestCase @@ -28,6 +29,27 @@ def setup @res.keep_alive = true end + def test_prevent_response_splitting_headers + res['X-header'] = "malicious\r\nCookie: hack" + io = StringIO.new + res.send_response io + io.rewind + res = Net::HTTPResponse.read_new(Net::BufferedIO.new(io)) + assert_equal '500', res.code + refute_match 'hack', io.string + end + + def test_prevent_response_splitting_cookie_headers + user_input = "malicious\r\nCookie: hack" + res.cookies << WEBrick::Cookie.new('author', user_input) + io = StringIO.new + res.send_response io + io.rewind + res = Net::HTTPResponse.read_new(Net::BufferedIO.new(io)) + assert_equal '500', res.code + refute_match 'hack', io.string + end + def test_304_does_not_log_warning res.status = 304 res.setup_header diff --git a/test/mri/webrick/test_httpserver.rb b/test/mri/webrick/test_httpserver.rb index daeb7b955e4..024c0c510fb 100644 --- a/test/mri/webrick/test_httpserver.rb +++ b/test/mri/webrick/test_httpserver.rb @@ -440,4 +440,71 @@ def test_cntrl_in_path s&.shutdown th&.join end + + def test_gigantic_request_header + log_tester = lambda {|log, access_log| + assert_equal 1, log.size + assert log[0].include?('ERROR headers too large') + } + TestWEBrick.start_httpserver({}, log_tester){|server, addr, port, log| + server.mount('/', WEBrick::HTTPServlet::FileHandler, __FILE__) + TCPSocket.open(addr, port) do |c| + c.write("GET / HTTP/1.0\r\n") + junk = -"X-Junk: #{' ' * 1024}\r\n" + assert_raise(Errno::ECONNRESET, Errno::EPIPE) do + loop { c.write(junk) } + end + end + } + end + + def test_eof_in_chunk + log_tester = lambda do |log, access_log| + assert_equal 1, log.size + assert log[0].include?('ERROR bad chunk data size') + end + TestWEBrick.start_httpserver({}, log_tester){|server, addr, port, log| + server.mount_proc('/', ->(req, res) { res.body = req.body }) + TCPSocket.open(addr, port) do |c| + c.write("POST / HTTP/1.1\r\nHost: example.com\r\n" \ + "Transfer-Encoding: chunked\r\n\r\n5\r\na") + c.shutdown(Socket::SHUT_WR) # trigger EOF in server + res = c.read + assert_match %r{\AHTTP/1\.1 400 }, res + end + } + end + + def test_big_chunks + nr_out = 3 + buf = 'big' # 3 bytes is bigger than 2! + config = { :InputBufferSize => 2 }.freeze + total = 0 + all = '' + TestWEBrick.start_httpserver(config){|server, addr, port, log| + server.mount_proc('/', ->(req, res) { + err = [] + ret = req.body do |chunk| + n = chunk.bytesize + n > config[:InputBufferSize] and err << "#{n} > :InputBufferSize" + total += n + all << chunk + end + ret.nil? or err << 'req.body should return nil' + (buf * nr_out) == all or err << 'input body does not match expected' + res.header['connection'] = 'close' + res.body = err.join("\n") + }) + TCPSocket.open(addr, port) do |c| + c.write("POST / HTTP/1.1\r\nHost: example.com\r\n" \ + "Transfer-Encoding: chunked\r\n\r\n") + chunk = "#{buf.bytesize.to_s(16)}\r\n#{buf}\r\n" + nr_out.times { c.write(chunk) } + c.write("0\r\n\r\n") + head, body = c.read.split("\r\n\r\n") + assert_match %r{\AHTTP/1\.1 200 OK}, head + assert_nil body + end + } + end end