summaryrefslogtreecommitdiff
path: root/test
diff options
context:
space:
mode:
Diffstat (limited to 'test')
-rw-r--r--test/-ext-/test_printf.rb19
-rw-r--r--test/-ext-/typeddata/test_typeddata.rb9
-rw-r--r--test/digest/test_digest.rb11
-rw-r--r--test/dtrace/helper.rb10
-rw-r--r--test/dtrace/test_gc_modular.rb37
-rw-r--r--test/net/http/test_http.rb18
-rw-r--r--test/net/http/test_httpheader.rb36
-rw-r--r--test/net/http/test_httpresponse.rb24
-rw-r--r--test/objspace/test_objspace.rb6
-rw-r--r--test/prism/errors/alnum_delimiters_10.txt4
-rw-r--r--test/prism/errors/alnum_delimiters_11.txt4
-rw-r--r--test/psych/test_array.rb12
-rw-r--r--test/psych/test_hash.rb25
-rw-r--r--test/psych/test_parser.rb43
-rw-r--r--test/psych/test_psych.rb10
-rw-r--r--test/psych/test_safe_load.rb13
-rw-r--r--test/psych/test_string.rb23
-rw-r--r--test/ruby/test_array.rb54
-rw-r--r--test/ruby/test_call.rb6
-rw-r--r--test/ruby/test_io_buffer.rb40
-rw-r--r--test/ruby/test_rational.rb1
-rw-r--r--test/ruby/test_regexp.rb15
-rw-r--r--test/ruby/test_settracefunc.rb8
-rw-r--r--test/ruby/test_shapes.rb20
-rw-r--r--test/ruby/test_string.rb9
-rw-r--r--test/rubygems/test_gem_package.rb19
-rw-r--r--test/rubygems/test_gem_safe_yaml.rb130
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