diff options
Diffstat (limited to 'test')
27 files changed, 570 insertions, 36 deletions
diff --git a/test/-ext-/test_printf.rb b/test/-ext-/test_printf.rb index c2b50285b9..aec28f75ba 100644 --- a/test/-ext-/test_printf.rb +++ b/test/-ext-/test_printf.rb @@ -181,4 +181,23 @@ class Test_SPrintf < Test::Unit::TestCase def test_snprintf_count assert_equal(3, Bug::Printf.sncount("foo")) end + + def test_ascii_incompatible_result_encoding + assert_raise(Encoding::CompatibilityError) { + Bug::Printf.enc_sprintf(Encoding::UTF_16LE) + } + end + + def test_ascii_incompatible_catf_receiver + assert_raise(Encoding::CompatibilityError) { + Bug::Printf.catf("".encode(Encoding::UTF_16LE)) + } + end + + def test_ascii_incompatible_encoding + s = ("\x41\x01" * 61).force_encoding(Encoding::UTF_16LE) + result = Bug::Printf.value(s) + assert_equal(s.encode(Encoding::US_ASCII, invalid: :replace, undef: :replace), result) + assert_equal(Encoding::US_ASCII, result.encoding) + end end diff --git a/test/-ext-/typeddata/test_typeddata.rb b/test/-ext-/typeddata/test_typeddata.rb index e32b030a35..36b2f9bac2 100644 --- a/test/-ext-/typeddata/test_typeddata.rb +++ b/test/-ext-/typeddata/test_typeddata.rb @@ -26,4 +26,13 @@ class Test_TypedData < Test::Unit::TestCase Bug::TypedData.make(n) end; end + + def test_dynamic_data_type_free + assert_ruby_status([], "#{<<-"begin;"}\n#{<<-"end;"}") + require "-test-/typeddata" + begin; + 100.times { Bug::TypedData.dynamic_type } + GC.start + end; + end end diff --git a/test/digest/test_digest.rb b/test/digest/test_digest.rb index c9b2c68051..084eda8b1b 100644 --- a/test/digest/test_digest.rb +++ b/test/digest/test_digest.rb @@ -6,7 +6,7 @@ require 'test/unit' require 'tempfile' require 'digest' -%w[digest/md5 digest/rmd160 digest/sha1 digest/sha2 digest/bubblebabble].each do |lib| +%w[digest/md5 digest/rmd160 digest/sha1 digest/sha2 digest/bubblebabble digest/crc32].each do |lib| begin require lib rescue LoadError @@ -197,6 +197,15 @@ module TestDigest } end if defined?(Digest::RMD160) + class TestCRC32 < Test::Unit::TestCase + include TestDigest + ALGO = Digest::CRC32 + DATA = { + Data1 => "352441c2", + Data2 => "171a3f5f", + } + end + class TestBase < Test::Unit::TestCase def test_base bug3810 = '[ruby-core:32231]' diff --git a/test/dtrace/helper.rb b/test/dtrace/helper.rb index 9e8c7ecd52..4fd218e539 100644 --- a/test/dtrace/helper.rb +++ b/test/dtrace/helper.rb @@ -115,7 +115,7 @@ module DTrace IO.popen(cmd, err: [:child, :out], &:readlines) end - def trap_probe d_program, ruby_program + def trap_probe d_program, ruby_program, env: {} if Hash === d_program d_program = d_program[IMPL] or omit "#{d_program} not implemented for #{IMPL}" @@ -132,15 +132,17 @@ module DTrace d_path = d.path rb_path = rb.path - cmd = "#{RUBYBIN} --disable=gems -I#{INCLUDE} #{rb_path}" + ruby_env = env + env_prefix = ruby_env.map {|key, val| "#{key}=#{val}" } + cmd = [*env_prefix, "#{RUBYBIN} --disable=gems -I#{INCLUDE} #{rb_path}"].join(" ") if IMPL == :stap cmd = %W(stap #{d_path} -c #{cmd}) else cmd = [*DTRACE_CMD, "-q", "-s", d_path, "-c", cmd ] end if sudo = @@sudo - NEEDED_ENVS.each do |name| - if val = ENV[name] + (NEEDED_ENVS + ruby_env.keys).uniq.each do |name| + if val = ruby_env[name] || ENV[name] cmd.unshift("#{name}=#{val}") end end diff --git a/test/dtrace/test_gc_modular.rb b/test/dtrace/test_gc_modular.rb new file mode 100644 index 0000000000..68665c7813 --- /dev/null +++ b/test/dtrace/test_gc_modular.rb @@ -0,0 +1,37 @@ +# frozen_string_literal: false +require_relative 'helper' + +module DTrace + class TestModularGC < TestCase + MODULAR_GC_DIR = RbConfig::CONFIG["modular_gc_dir"] + DLEXT = RbConfig::CONFIG["DLEXT"] + DEFAULT_GC = MODULAR_GC_DIR && File.join(MODULAR_GC_DIR, "librubygc.default.#{DLEXT}") + + def setup + super + omit "Ruby was not configured with --with-modular-gc" if MODULAR_GC_DIR.nil? || MODULAR_GC_DIR.empty? + omit "default modular GC is not installed" unless DEFAULT_GC && File.file?(DEFAULT_GC) + end + + %w[ + gc-mark-begin + gc-mark-end + gc-sweep-begin + gc-sweep-end + ].each do |probe_name| + define_method(:"test_modular_#{probe_name.gsub(/-/, '_')}") do + probe = "ruby$target:::#{probe_name} { printf(\"#{probe_name}\\n\"); }" + + trap_probe(probe, ruby_program, env: {"RUBY_GC_LIBRARY" => "default"}) do |_, _, saw| + assert_operator saw.length, :>, 0 + end + end + end + + private + + def ruby_program + "100000.times { Object.new }" + end + end +end if defined?(DTrace::TestCase) diff --git a/test/net/http/test_http.rb b/test/net/http/test_http.rb index 4e7fa22756..477c3415b4 100644 --- a/test/net/http/test_http.rb +++ b/test/net/http/test_http.rb @@ -931,6 +931,24 @@ __EOM__ } } end + + def test_set_form_multipart_crlf_injection + build = ->(data, opt = {}) { + req = Net::HTTP::Post.new('/') + req.set_form(data, 'multipart/form-data') + out = +'' + req.send(:encode_multipart_form_data, out, req.instance_variable_get(:@body_data), opt) + } + assert_raise(ArgumentError) { build.call([["foo\r\nX-Injected: 1", 'v']]) } + assert_raise(ArgumentError) { build.call([['f', 'v']], boundary: "abc\r\nX-Injected: 1") } + assert_raise(ArgumentError) { build.call([['f', 'v', {filename: "a\r\nX-Injected: 1"}]]) } + assert_raise(ArgumentError) do + build.call([['f', 'v', {filename: 'a', content_type: "text/plain\r\nX-Injected: 1"}]]) + end + assert_nothing_raised do + build.call([['f', 'v', {filename: 'a', content_type: :"text/plain"}]]) + end + end end class TestNetHTTP_v1_2 < Test::Unit::TestCase diff --git a/test/net/http/test_httpheader.rb b/test/net/http/test_httpheader.rb index 69563168db..f4b786037f 100644 --- a/test/net/http/test_httpheader.rb +++ b/test/net/http/test_httpheader.rb @@ -30,6 +30,19 @@ class HTTPHeaderTest < Test::Unit::TestCase assert_raise(ArgumentError){ @c.initialize_http_header("foo"=>"a\rb") } end + def test_invalid_field_name + assert_raise(ArgumentError){ @c.initialize_http_header("foo\nbar"=>"abc") } + assert_raise(ArgumentError){ @c.initialize_http_header("foo\rbar"=>"abc") } + assert_raise(ArgumentError){ @c.initialize_http_header("foo:bar"=>"abc") } + assert_raise(ArgumentError){ @c.initialize_http_header("foo\x00bar"=>"abc") } + assert_raise(ArgumentError){ @c['foo'.b << 0x0a << 'bar'] = 'abc' } + assert_raise(ArgumentError){ @c["foo\rbar"] = 'abc' } + assert_raise(ArgumentError){ @c["foo:bar"] = 'abc' } + assert_raise(ArgumentError){ @c["foo\x7fbar"] = 'abc' } + assert_raise(ArgumentError){ @c.add_field "foo\nbar", 'abc' } + assert_raise(ArgumentError){ @c.add_field "foo\nbar", ['abc'] } + end + def test_initialize_with_broken_coderange error = RUBY_VERSION >= "3.2" ? Encoding::CompatibilityError : ArgumentError assert_raise(error){ @c.initialize_http_header("foo"=>"a\xff") } @@ -76,6 +89,24 @@ class HTTPHeaderTest < Test::Unit::TestCase assert_raise(ArgumentError){ @c['foo'] = ["a\nb"] } end + def test_set_field_too_long_key + assert_raise(ArgumentError){ @c['x' * (Net::HTTPHeader::MAX_KEY_LENGTH + 1)] = 'a' } + assert_nothing_raised{ @c['x' * Net::HTTPHeader::MAX_KEY_LENGTH] = 'a' } + end + + def test_set_field_too_long_value + long = 'a' * (Net::HTTPHeader::MAX_FIELD_LENGTH + 1) + assert_raise(ArgumentError){ @c['foo'] = long } + assert_raise(ArgumentError){ @c['foo'] = [long] } + assert_raise(ArgumentError){ @c.add_field 'foo', long } + + # the error message names the key and the limit on every path + @c['foo'] = 'ok' + e = assert_raise(ArgumentError){ @c.add_field 'foo', long } + assert_match(/foo/, e.message) + assert_match(/#{Net::HTTPHeader::MAX_FIELD_LENGTH}/, e.message) + end + def test_AREF @c['My-Header'] = 'test string' assert_equal 'test string', @c['my-header'] @@ -438,6 +469,11 @@ class HTTPHeaderTest < Test::Unit::TestCase end def test_set_content_type + @c.set_content_type 'text/html', {'charset' => 'utf-8'} + assert_equal 'text/html; charset=utf-8', @c['content-type'] + assert_raise(ArgumentError){ @c.set_content_type "text/html\r\nFoo: bar" } + assert_raise(ArgumentError){ @c.set_content_type 'text/html', {'charset' => "x\r\nFoo: bar"} } + assert_raise(ArgumentError){ @c.set_content_type 'text/html', {"x\nFoo: bar" => 'utf-8'} } end def test_form_data= diff --git a/test/net/http/test_httpresponse.rb b/test/net/http/test_httpresponse.rb index 01281063cd..7e7ae8a8e2 100644 --- a/test/net/http/test_httpresponse.rb +++ b/test/net/http/test_httpresponse.rb @@ -18,6 +18,30 @@ EOS assert_equal('close', res['connection']) end + def test_response_header_too_large + big_value = 'a' * (Net::HTTPHeader::MAX_FIELD_LENGTH - 100) + count = (Net::HTTPResponse::MAX_RESPONSE_HEADER_LENGTH / big_value.bytesize) + 2 + headers = +"HTTP/1.1 200 OK\n" + count.times { |i| headers << "X-Pad-#{i}: #{big_value}\n" } + headers << "\nhello\n" + io = dummy_io(headers) + assert_raise(Net::HTTPBadResponse) do + Net::HTTPResponse.read_new(io) + end + end + + def test_response_header_within_limit + big_value = 'a' * (Net::HTTPHeader::MAX_FIELD_LENGTH - 100) + count = (Net::HTTPResponse::MAX_RESPONSE_HEADER_LENGTH / big_value.bytesize) - 1 + headers = +"HTTP/1.1 200 OK\n" + count.times { |i| headers << "X-Pad-#{i}: #{big_value}\n" } + headers << "\nhello\n" + io = dummy_io(headers) + assert_nothing_raised do + Net::HTTPResponse.read_new(io) + end + end + def test_multiline_header io = dummy_io(<<EOS) HTTP/1.1 200 OK diff --git a/test/objspace/test_objspace.rb b/test/objspace/test_objspace.rb index a9b902ed45..aacfba987d 100644 --- a/test/objspace/test_objspace.rb +++ b/test/objspace/test_objspace.rb @@ -351,14 +351,14 @@ class TestObjSpace < Test::Unit::TestCase end def test_trace_object_allocations_does_not_reuse_freed_allocation_info - assert_separately(%w(-robjspace), <<~RUBY) + assert_separately(%w(-robjspace), <<~RUBY, timeout: 60) ObjectSpace.trace_object_allocations do - 1_000_000.times.map { Object.new } + 100_000.times.map { Object.new } end GC.start - objs = 1_000_000.times.map { Object.new } + objs = 100_000.times.map { Object.new } leaked = objs.count { |obj| ObjectSpace.allocation_sourcefile(obj) } assert_equal 0, leaked diff --git a/test/prism/errors/alnum_delimiters_10.txt b/test/prism/errors/alnum_delimiters_10.txt new file mode 100644 index 0000000000..288f10d59f --- /dev/null +++ b/test/prism/errors/alnum_delimiters_10.txt @@ -0,0 +1,4 @@ +%Γ’ +^ unknown type of %string +Γ’ + diff --git a/test/prism/errors/alnum_delimiters_11.txt b/test/prism/errors/alnum_delimiters_11.txt new file mode 100644 index 0000000000..c52a5a9a1a --- /dev/null +++ b/test/prism/errors/alnum_delimiters_11.txt @@ -0,0 +1,4 @@ +%π +^ unknown type of %string +π + diff --git a/test/psych/test_array.rb b/test/psych/test_array.rb index 0dc82439d4..064ace4659 100644 --- a/test/psych/test_array.rb +++ b/test/psych/test_array.rb @@ -52,6 +52,18 @@ module Psych assert_equal X, x.class end + class NotAnArray + end + + def test_seq_tag_rejects_non_array_class + assert_raise(ArgumentError) do + Psych.unsafe_load "--- !seq:#{NotAnArray} []\n" + end + assert_raise(ArgumentError) do + Psych.unsafe_load "--- !ruby/array:#{NotAnArray} []\n" + end + end + def test_self_referential @list << @list assert_cycle(@list) diff --git a/test/psych/test_hash.rb b/test/psych/test_hash.rb index 31eba8580b..7f46c551f4 100644 --- a/test/psych/test_hash.rb +++ b/test/psych/test_hash.rb @@ -92,6 +92,31 @@ module Psych assert_equal X, x.class end + class NotAHash + def init_with(coder) + @string = coder.map["string"].to_s + end + end + + def test_hash_with_ivars_rejects_non_hash_class + assert_raise(ArgumentError) do + Psych.unsafe_load <<~eoyml + --- !ruby/hash-with-ivars:#{NotAHash} + ivars: + '@string': ["a surprise array!"] + eoyml + end + end + + def test_hash_tag_rejects_non_hash_class + assert_raise(ArgumentError) do + Psych.unsafe_load "--- !ruby/hash:#{NotAHash} {}\n" + end + assert_raise(ArgumentError) do + Psych.unsafe_load "--- !map:#{NotAHash} {}\n" + end + end + def test_self_referential @hash['self'] = @hash assert_cycle(@hash) diff --git a/test/psych/test_parser.rb b/test/psych/test_parser.rb index 4ca4d63d80..c175b8a1eb 100644 --- a/test/psych/test_parser.rb +++ b/test/psych/test_parser.rb @@ -173,6 +173,45 @@ module Psych assert_equal tadpole, @parser.handler.calls.find { |method, args| method == :scalar }[1].first end + # BOM + multi-line mapping used to lose every line after the first one + # https://github.com/ruby/psych/issues/331 + def test_bom_multiline_utf8 + @parser.parse "\uFEFFa: b\nc: d\n" + assert_equal %w[a b c d], scalars(@parser.handler) + end + + def test_bom_multiline_utf16 + %w[UTF-16LE UTF-16BE].each do |enc| + handler = EventCatcher.new + Psych::Parser.new(handler).parse "\uFEFFa: b\nc: d\n".encode(enc) + assert_equal %w[a b c d], scalars(handler), enc + end + end + + def test_bom_multiline_utf32 + %w[UTF-32LE UTF-32BE].each do |enc| + handler = EventCatcher.new + Psych::Parser.new(handler).parse "\uFEFFa: b\nc: d\n".encode(enc) + assert_equal %w[a b c d], scalars(handler), enc + end + end + + def test_bom_multiline_io + @parser.parse StringIO.new("\uFEFFa: b\nc: d\n") + assert_equal %w[a b c d], scalars(@parser.handler) + end + + def test_bom_only + @parser.parse "\uFEFF" + assert_equal [], scalars(@parser.handler) + end + + def test_io_without_bom_is_not_modified + io = StringIO.new "a: b\nc: d\n".freeze + @parser.parse io + assert_equal %w[a b c d], scalars(@parser.handler) + end + def test_external_encoding tadpole = 'γγγΎγγγγ' @@ -445,6 +484,10 @@ module Psych end end + def scalars handler + handler.calls.select { |method, _| method == :scalar }.map { |_, args| args.first } + end + def assert_called call, with = nil, parser = @parser if with call = parser.handler.calls.find { |x| diff --git a/test/psych/test_psych.rb b/test/psych/test_psych.rb index 4455c471e7..8e5ec9419e 100644 --- a/test/psych/test_psych.rb +++ b/test/psych/test_psych.rb @@ -158,6 +158,16 @@ class TestPsych < Psych::TestCase assert_equal(%w[foo bar], docs.children.map(&:transform)) end + # https://github.com/ruby/psych/issues/331 + def test_load_with_leading_bom + assert_equal({ "a" => "b", "c" => "d" }, Psych.load("\uFEFFa: b\nc: d")) + end + + def test_parse_stream_with_leading_bom + docs = Psych.parse_stream("\uFEFFa: b\nc: d") + assert_equal [{ "a" => "b", "c" => "d" }], docs.children.map(&:to_ruby) + end + def test_parse_stream_with_block docs = [] Psych.parse_stream("--- foo\n...\n--- bar\n...") do |node| diff --git a/test/psych/test_safe_load.rb b/test/psych/test_safe_load.rb index e6ca1e142b..ac62df3be1 100644 --- a/test/psych/test_safe_load.rb +++ b/test/psych/test_safe_load.rb @@ -74,6 +74,19 @@ module Psych assert_equal :foo, Psych.safe_load('--- !ruby/symbol foo', permitted_classes: [Symbol]) end + def test_encoding + yaml = "--- !ruby/encoding UTF-8\n" + assert_raise(Psych::DisallowedClass) do + Psych.safe_load yaml + end + assert_raise(Psych::DisallowedClass) do + Psych.safe_load yaml, permitted_classes: [] + end + + assert_equal Encoding::UTF_8, Psych.safe_load(yaml, permitted_classes: [Encoding]) + assert_equal Encoding::UTF_8, Psych.safe_load(yaml, permitted_classes: %w{ Encoding }) + end + def test_foo assert_raise(Psych::DisallowedClass) do Psych.safe_load '--- !ruby/object:Foo {}', permitted_classes: [Foo] diff --git a/test/psych/test_string.rb b/test/psych/test_string.rb index cfd235a519..1621f0604a 100644 --- a/test/psych/test_string.rb +++ b/test/psych/test_string.rb @@ -60,6 +60,17 @@ module Psych RUBY end + def test_datetime_string_with_tab_separator + str = "2023-12-31\t12:00:00" + assert_cycle str + end + + def test_datetime_string_with_whitespace_separators + ["\v", "\r", "\f", " \t", "\t "].each do |sep| + assert_cycle "2023-12-31#{sep}12:00:00" + end + end + def test_plain_when_shorten_than_line_width_and_no_final_line_break str = "Lorem ipsum" yaml = Psych.dump str, line_width: 12 @@ -182,6 +193,18 @@ string: &70121654388580 !ruby/string assert_equal 1, y.val end + class NotAString + end + + def test_string_tag_rejects_non_string_class + assert_raise(ArgumentError) do + Psych.unsafe_load "--- !ruby/string:#{NotAString} foo\n" + end + assert_raise(ArgumentError) do + Psych.unsafe_load "--- !ruby/string:#{NotAString}\nstr: foo\n" + end + end + def test_string_with_base_60 yaml = Psych.dump '01:03:05' assert_match "'01:03:05'", yaml diff --git a/test/ruby/test_array.rb b/test/ruby/test_array.rb index 76455187a5..36f8801614 100644 --- a/test/ruby/test_array.rb +++ b/test/ruby/test_array.rb @@ -2812,6 +2812,60 @@ class TestArray < Test::Unit::TestCase assert_equal("b", x.ary.join("")) end + def test_join_encoding + # Multibyte UTF-8 elements: result is UTF-8, valid, not ASCII-only. + r = @cls["caf\u00e9", "na\u00efve"].join(" ") + assert_equal("caf\u00e9 na\u00efve", r) + assert_equal(Encoding::UTF_8, r.encoding) + assert_equal(true, r.valid_encoding?) + assert_equal(false, r.ascii_only?) + + # All 7-bit content stays ASCII-only. + r = @cls["abc", "def"].join(",") + assert_equal("abc,def", r) + assert_equal(true, r.ascii_only?) + + # Multibyte separator (same encoding as the elements). + r = @cls["a", "b", "c"].join("\u2014") + assert_equal("a\u2014b\u2014c", r) + assert_equal(Encoding::UTF_8, r.encoding) + assert_equal(false, r.ascii_only?) + assert_equal(true, r.valid_encoding?) + + # Mixed ASCII-compatible encodings, all 7-bit: result takes element 0's encoding. + r = @cls["abc".dup.force_encoding("US-ASCII"), "def".dup.force_encoding("UTF-8")].join(" ") + assert_equal("abc def", r) + assert_equal(Encoding::US_ASCII, r.encoding) + assert_equal(true, r.ascii_only?) + + # 7-bit content in a non-ASCII encoding (Shift_JIS). + r = @cls["abc".encode("Shift_JIS"), "def".encode("Shift_JIS")].join(" ") + assert_equal(Encoding::Shift_JIS, r.encoding) + assert_equal("abc def".b, r.b) + + # Same-encoding multibyte (Shift_JIS): byte-for-byte concatenation. + s = "\u65e5\u672c".encode("Shift_JIS") + sep = "/".encode("Shift_JIS") + r = @cls[s, s].join(sep) + assert_equal(Encoding::Shift_JIS, r.encoding) + assert_equal((s + sep + s).b, r.b) + + # Broken bytes: result keeps them and reports invalid. + bad = "\xff\xfe".dup.force_encoding("UTF-8") + r = @cls[bad, bad].join(" ") + assert_equal((bad + " " + bad).b, r.b) + assert_equal(false, r.valid_encoding?) + + # Incompatible element/separator encodings still raise. + assert_raise(Encoding::CompatibilityError) do + @cls["a".dup.force_encoding("UTF-8"), "b".encode("UTF-16LE")].join(" ") + end + + # Bulk copy: large array, verified against a non-join oracle. + big = @cls.new(500) { "ab" } + assert_equal(("ab," * 500).chomp(","), big.join(",")) + end + def test_to_a2 klass = Class.new(Array) a = klass.new.to_a diff --git a/test/ruby/test_call.rb b/test/ruby/test_call.rb index dd1936c4e2..26c8b581c4 100644 --- a/test/ruby/test_call.rb +++ b/test/ruby/test_call.rb @@ -180,7 +180,6 @@ class TestCall < Test::Unit::TestCase assert_syntax_error(%q{h[0, *a, **kw] += 1}, message) assert_syntax_error(%q{h[kw: 5] += 1}, message) assert_syntax_error(%q{h[kw: 5, a: 2] += 1}, message) - assert_syntax_error(%q{h[kw: 5, a: 2] += 1}, message) assert_syntax_error(%q{h[0, kw: 5, a: 2] += 1}, message) assert_syntax_error(%q{h[0, *a, kw: 5, a: 2, nil: 3] += 1}, message) @@ -200,7 +199,6 @@ class TestCall < Test::Unit::TestCase assert_syntax_error(%q{h[0, *a, **kw] += 1; nil}, message) assert_syntax_error(%q{h[kw: 5] += 1; nil}, message) assert_syntax_error(%q{h[kw: 5, a: 2] += 1; nil}, message) - assert_syntax_error(%q{h[kw: 5, a: 2] += 1; nil}, message) assert_syntax_error(%q{h[0, kw: 5, a: 2] += 1; nil}, message) assert_syntax_error(%q{h[0, *a, kw: 5, a: 2, nil: 3] += 1; nil}, message) @@ -220,7 +218,6 @@ class TestCall < Test::Unit::TestCase assert_syntax_error(%q{h[0, *a, **kw] &&= 1}, message) assert_syntax_error(%q{h[kw: 5] &&= 1}, message) assert_syntax_error(%q{h[kw: 5, a: 2] &&= 1}, message) - assert_syntax_error(%q{h[kw: 5, a: 2] &&= 1}, message) assert_syntax_error(%q{h[0, kw: 5, a: 2] &&= 1}, message) assert_syntax_error(%q{h[0, *a, kw: 5, a: 2, nil: 3] &&= 1}, message) @@ -240,7 +237,6 @@ class TestCall < Test::Unit::TestCase assert_syntax_error(%q{h[0, *a, **kw] &&= 1; nil}, message) assert_syntax_error(%q{h[kw: 5] &&= 1; nil}, message) assert_syntax_error(%q{h[kw: 5, a: 2] &&= 1; nil}, message) - assert_syntax_error(%q{h[kw: 5, a: 2] &&= 1; nil}, message) assert_syntax_error(%q{h[0, kw: 5, a: 2] &&= 1; nil}, message) assert_syntax_error(%q{h[0, *a, kw: 5, a: 2, nil: 3] &&= 1; nil}, message) @@ -260,7 +256,6 @@ class TestCall < Test::Unit::TestCase assert_syntax_error(%q{h[0, *a, **kw] ||= 1}, message) assert_syntax_error(%q{h[kw: 5] ||= 1}, message) assert_syntax_error(%q{h[kw: 5, a: 2] ||= 1}, message) - assert_syntax_error(%q{h[kw: 5, a: 2] ||= 1}, message) assert_syntax_error(%q{h[0, kw: 5, a: 2] ||= 1}, message) assert_syntax_error(%q{h[0, *a, kw: 5, a: 2, nil: 3] ||= 1}, message) @@ -280,7 +275,6 @@ class TestCall < Test::Unit::TestCase assert_syntax_error(%q{h[0, *a, **kw] ||= 1; nil}, message) assert_syntax_error(%q{h[kw: 5] ||= 1; nil}, message) assert_syntax_error(%q{h[kw: 5, a: 2] ||= 1; nil}, message) - assert_syntax_error(%q{h[kw: 5, a: 2] ||= 1; nil}, message) assert_syntax_error(%q{h[0, kw: 5, a: 2] ||= 1; nil}, message) assert_syntax_error(%q{h[0, *a, kw: 5, a: 2, nil: 3] ||= 1; nil}, message) diff --git a/test/ruby/test_io_buffer.rb b/test/ruby/test_io_buffer.rb index fdf99589ef..7d8583d492 100644 --- a/test/ruby/test_io_buffer.rb +++ b/test/ruby/test_io_buffer.rb @@ -91,11 +91,11 @@ class TestIOBuffer < Test::Unit::TestCase end def test_file_mapped_size_too_large - assert_raise ArgumentError do - File.open(__FILE__) {|file| IO::Buffer.map(file, 200_000, 0, IO::Buffer::READONLY)} - end - assert_raise ArgumentError do - File.open(__FILE__) {|file| IO::Buffer.map(file, File.size(__FILE__) + 1, 0, IO::Buffer::READONLY)} + size = File.size(__FILE__) + 1 + file_size = File.size(__FILE__) + message = "Size (#{size}) can't be larger than file size (#{file_size})" + assert_raise_with_message ArgumentError, message do + File.open(__FILE__) {|file| IO::Buffer.map(file, size, 0, IO::Buffer::READONLY)} end end @@ -105,12 +105,34 @@ class TestIOBuffer < Test::Unit::TestCase } end + def test_file_mapped_offset_negative + offset = -1 + message = "Offset (#{offset}) can't be negative!" + assert_raise_with_message ArgumentError, message do + File.open(__FILE__) {|file| IO::Buffer.map(file, nil, offset, IO::Buffer::READONLY)} + end + end + def test_file_mapped_offset_too_large - assert_raise ArgumentError do - File.open(__FILE__) {|file| IO::Buffer.map(file, nil, IO::Buffer::PAGE_SIZE * 100, IO::Buffer::READONLY)} + file_size = File.size(__FILE__) + page_count = file_size / IO::Buffer::PAGE_SIZE + offset = IO::Buffer::PAGE_SIZE * (page_count + 1) + message = "Offset (#{offset}) can't be larger than file size (#{file_size})" + assert_raise_with_message ArgumentError, message do + File.open(__FILE__) {|file| IO::Buffer.map(file, nil, offset, IO::Buffer::READONLY)} end - assert_raise ArgumentError do - File.open(__FILE__) {|file| IO::Buffer.map(file, 20, IO::Buffer::PAGE_SIZE * 100, IO::Buffer::READONLY)} + + if page_count > 0 + offset = IO::Buffer::PAGE_SIZE * page_count + available_size = file_size - offset + size = available_size + 1 + maximum_page_count = (file_size - size) / IO::Buffer::PAGE_SIZE + maximum_offset = IO::Buffer::PAGE_SIZE * maximum_page_count + message = "Offset (#{offset}) can't be larger than #{maximum_offset} " + + "for requested size (#{size})" + assert_raise_with_message ArgumentError, message do + File.open(__FILE__) {|file| IO::Buffer.map(file, size, offset, IO::Buffer::READONLY)} + end end end diff --git a/test/ruby/test_rational.rb b/test/ruby/test_rational.rb index a02e11acc5..77dd7ffc64 100644 --- a/test/ruby/test_rational.rb +++ b/test/ruby/test_rational.rb @@ -701,7 +701,6 @@ class Rational_Test < Test::Unit::TestCase assert_equal('-1/2', Rational(-1,2).to_s) assert_equal('1/2', Rational(-1,-2).to_s) assert_equal('-1/2', Rational(1,-2).to_s) - assert_equal('1/2', Rational(-1,-2).to_s) end def test_inspect diff --git a/test/ruby/test_regexp.rb b/test/ruby/test_regexp.rb index 805c57b472..cf989615e0 100644 --- a/test/ruby/test_regexp.rb +++ b/test/ruby/test_regexp.rb @@ -1876,6 +1876,21 @@ class TestRegexp < Test::Unit::TestCase assert_equal(/0/, pr1.call(2)) end + def test_once_constant_interpolation + # A /o regexp whose interpolation folds to a constant must build a Regexp, + # not resurrect it as a String. The peephole optimizer rewrites + # "dupstring str; toregexp" into a single literal-push instruction; that + # instruction must become putobject (not keep dupstring). + assert_separately([], <<~'RUBY') + def m; /#{"a"}#{"b"}/o; end + assert_equal(/ab/, m) + assert_equal(/ab/, m) + assert_predicate(m, :frozen?) + assert_equal(/a/i, eval('/#{"a"}/io')) + assert_equal(0, ("ab" =~ /#{"a"}/o)) + RUBY + end + def test_once_recursive pr2 = proc{|i| if i > 0 diff --git a/test/ruby/test_settracefunc.rb b/test/ruby/test_settracefunc.rb index 8b0e08fc97..e2cda4dfb8 100644 --- a/test/ruby/test_settracefunc.rb +++ b/test/ruby/test_settracefunc.rb @@ -1904,6 +1904,14 @@ CODE events = [] capture_events = Proc.new{|tp| next unless target_thread? + # Skip events from other code interrupting this thread, such as + # finalizers of objects left by other tests (e.g. Tempfile's), + # whose frames are pushed on top of the interrupted frame of this + # file. The event is ours iff the innermost frame, ignoring core + # methods written in Ruby (e.g. Kernel#tap in <internal:kernel>), + # belongs to this file. + innermost = caller_locations.drop_while{|loc| loc.path.start_with?("<internal:")}.first + next unless innermost&.path == __FILE__ events << [tp.event, tp.method_id, tp.callee_id] } diff --git a/test/ruby/test_shapes.rb b/test/ruby/test_shapes.rb index bace69658a..cbf4eebfa3 100644 --- a/test/ruby/test_shapes.rb +++ b/test/ruby/test_shapes.rb @@ -724,6 +724,26 @@ class TestShapes < Test::Unit::TestCase end; end + def test_object_id_when_fields_are_full + # Adding object_id to an object with exactly SHAPE_MAX_FIELDS fields must + # transition to COMPLEX instead of asserting. + assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}") + begin; + obj = Object.new + max = RubyVM::Shape::SHAPE_MAX_FIELDS + max.times { |i| obj.instance_variable_set(:"@v#{i}", i) } + refute_predicate RubyVM::Shape.of(obj), :complex? + + id = obj.object_id + assert_kind_of Integer, id + assert_equal id, obj.object_id + assert_predicate RubyVM::Shape.of(obj), :complex? + + max.times { |i| assert_equal i, obj.instance_variable_get(:"@v#{i}") } + assert_equal max, obj.instance_variables.size + end; + end + def test_complex_and_frozen_and_object_id assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}") begin; diff --git a/test/ruby/test_string.rb b/test/ruby/test_string.rb index aedfc93e5d..d6d4ba40de 100644 --- a/test/ruby/test_string.rb +++ b/test/ruby/test_string.rb @@ -405,6 +405,9 @@ CODE assert_equal(S(" hello "), S("hello").center(11)) assert_equal(S("ababaababa"), S("").center(10, "ab"), Bug2463) assert_equal(S("ababaababab"), S("").center(11, "ab"), Bug2463) + r = S("").force_encoding(Encoding::UTF_16BE).center(1000, "a") + assert_equal(Encoding::UTF_8, r.encoding) + assert_equal("a" * 1000, r.b) end def test_chomp @@ -1498,6 +1501,9 @@ CODE assert_equal(S("hello "), S("hello").ljust(11)) assert_equal(S("ababababab"), S("").ljust(10, "ab"), Bug2463) assert_equal(S("abababababa"), S("").ljust(11, "ab"), Bug2463) + r = S("").force_encoding(Encoding::UTF_16BE).ljust(1000, "a") + assert_equal(Encoding::UTF_8, r.encoding) + assert_equal("a" * 1000, r.b) end def test_next @@ -1675,6 +1681,9 @@ CODE assert_equal(S(" hello"), S("hello").rjust(11)) assert_equal(S("ababababab"), S("").rjust(10, "ab"), Bug2463) assert_equal(S("abababababa"), S("").rjust(11, "ab"), Bug2463) + r = S("").force_encoding(Encoding::UTF_16BE).rjust(1000, "a") + assert_equal(Encoding::UTF_8, r.encoding) + assert_equal("a" * 1000, r.b) end def test_scan diff --git a/test/rubygems/test_gem_package.rb b/test/rubygems/test_gem_package.rb index 0014c20737..b69002486d 100644 --- a/test/rubygems/test_gem_package.rb +++ b/test/rubygems/test_gem_package.rb @@ -33,6 +33,25 @@ class TestGemPackage < Gem::Package::TarTestCase assert package.spec end + def test_class_new_old_format_forwards_security_policy + pend "jruby can't require the simple_gem file" if Gem.java_platform? + pend "openssl is missing" unless Gem::HAVE_OPENSSL + require_relative "simple_gem" + File.open "old_format.gem", "wb" do |io| + io.write SIMPLE_GEM + end + + package = Gem::Package.new "old_format.gem", Gem::Security::HighSecurity + + e = assert_raise Gem::Security::Exception do + package.verify + end + + assert_equal "old format gems do not contain signatures " \ + "and cannot be verified", + e.message + end + def test_add_checksums gem_io = StringIO.new diff --git a/test/rubygems/test_gem_safe_yaml.rb b/test/rubygems/test_gem_safe_yaml.rb index 8d0ac63c41..5bbfa57a95 100644 --- a/test/rubygems/test_gem_safe_yaml.rb +++ b/test/rubygems/test_gem_safe_yaml.rb @@ -709,6 +709,16 @@ class TestGemSafeYAML < Gem::TestCase end def test_roundtrip_specification_with_metadata + metadata = { + "changelog_uri" => "https://example.com/CHANGELOG.md", + "source_code_uri" => "https://github.com/example/metadata-test", + "bug_tracker_uri" => "https://github.com/example/metadata-test/issues", + "allowed_push_host" => "https://rubygems.org", + "\"double_quoted\"" => "\"quoted_value\"", + "'single_quoted'" => "'quoted_value'", + "have:colon" => "value:colon", + "have space" => "value space", + } spec = Gem::Specification.new do |s| s.name = "metadata-test" s.version = "1.0.0" @@ -716,24 +726,120 @@ class TestGemSafeYAML < Gem::TestCase s.summary = "A gem with metadata" s.files = ["lib/foo.rb"] s.require_paths = ["lib"] - s.metadata = { - "changelog_uri" => "https://example.com/CHANGELOG.md", - "source_code_uri" => "https://github.com/example/metadata-test", - "bug_tracker_uri" => "https://github.com/example/metadata-test/issues", - "allowed_push_host" => "https://rubygems.org", - } + s.metadata = metadata end yaml = yaml_dump(spec) loaded = Gem::SafeYAML.safe_load(yaml) assert_kind_of Gem::Specification, loaded - assert_kind_of Hash, loaded.metadata - assert_equal 4, loaded.metadata.size - assert_equal "https://example.com/CHANGELOG.md", loaded.metadata["changelog_uri"] - assert_equal "https://github.com/example/metadata-test", loaded.metadata["source_code_uri"] - assert_equal "https://github.com/example/metadata-test/issues", loaded.metadata["bug_tracker_uri"] - assert_equal "https://rubygems.org", loaded.metadata["allowed_push_host"] + assert_equal metadata, loaded.metadata + end + + def test_roundtrip_specification_with_quoted_first_metadata_key + metadata = { + "\"double_quoted\"" => "\"quoted_value\"", + "'single_quoted'" => "'quoted_value'", + "have:colon" => "value:colon", + } + spec = Gem::Specification.new do |s| + s.name = "metadata-test" + s.version = "1.0.0" + s.authors = ["Test"] + s.summary = "A gem with metadata" + s.metadata = metadata + end + + loaded = Gem::SafeYAML.safe_load(yaml_dump(spec)) + + assert_kind_of Gem::Specification, loaded + assert_equal "metadata-test", loaded.name + assert_equal metadata, loaded.metadata + end + + def test_roundtrip_specification_with_special_metadata_keys + metadata = { + "have: colon-space" => "value: colon-space", + "have#hash" => "value#hash", + "padded" => " padded value ", + "looks_null" => "null", + } + spec = Gem::Specification.new do |s| + s.name = "metadata-test" + s.version = "1.0.0" + s.authors = ["Test"] + s.summary = "A gem with metadata" + s.metadata = metadata + end + + loaded = Gem::SafeYAML.safe_load(yaml_dump(spec)) + + assert_kind_of Gem::Specification, loaded + assert_equal metadata, loaded.metadata + end + + def test_roundtrip_specification_with_control_character_metadata + metadata = { + "bell" => "bell\a", + "escape" => "esc\e[0m", + "control" => "soh\x01del\x7F", + "tab" => "tab\tinside", + } + spec = Gem::Specification.new do |s| + s.name = "metadata-test" + s.version = "1.0.0" + s.authors = ["Test"] + s.summary = "A gem with metadata" + s.metadata = metadata + end + + loaded = Gem::SafeYAML.safe_load(yaml_dump(spec)) + + assert_kind_of Gem::Specification, loaded + assert_equal metadata, loaded.metadata + end + + def test_load_escaped_control_characters + yaml = <<~YAML + --- + key: "bell\\aesc\\enull\\0soh\\x01del\\x7F" + YAML + + assert_equal({ "key" => "bell\aesc\enull\0soh\x01del\x7F" }, yaml_load(yaml)) + end + + def test_load_comment_only_mapping_value + yaml = <<~YAML + --- + commented: # comment + plain: value # trailing comment + quoted: "kept # inside quotes" + YAML + + expected = { + "commented" => nil, + "plain" => "value", + "quoted" => "kept # inside quotes", + } + assert_equal expected, yaml_load(yaml) + end + + def test_load_psych_style_quoted_mapping_keys + yaml = <<~YAML + --- + '"double_quoted"': '"quoted_value"' + "'single_quoted'": "'quoted_value'" + 'have: colon-space': v + key#hash: value#hash + YAML + + expected = { + "\"double_quoted\"" => "\"quoted_value\"", + "'single_quoted'" => "'quoted_value'", + "have: colon-space" => "v", + "key#hash" => "value#hash", + } + assert_equal expected, yaml_load(yaml) end def test_roundtrip_version |
